Pester 5 для инфраструктурного тестирования: от acceptance-тестов до операционной валидации
Я Семёнов Евгений Сергеевич, директор АйТи Фреш. За 15+ лет работы с Windows-инфраструктурами пришёл к железному правилу: если не могу проверить работу сервера одной командой, я не закрываю тикет. Pester — наш рабочий инструмент для этого: написали тест, запустили на сервере, получили зелёные галочки или красные строки с указанием, что именно сломалось. Его используем и после установки нового Exchange, и после миграции AD, и для ежедневной регресс-проверки критичных сервисов. В этой статье — как внедрить Pester 5 в инфраструктурную практику.
Что такое Pester и зачем он админам
Pester — фреймворк модульного тестирования для PowerShell. Исторически создан для разработчиков скриптов и модулей: чтобы проверять функции, моки, покрытие кода. Но начиная с версии 3 он стал стандартом для инфраструктурных тестов. Причины просты:
- Готовый язык Describe / Context / It, BDD-стиль.
- Набор утверждений Should -Be, Should -Exist, Should -Contain и т.д.
- Генерация NUnit XML для CI/CD.
- Встроенная параллельность в v5, BeforeAll / BeforeEach.
Установка последней версии:
Install-Module Pester -MinimumVersion 5.5.0 -Force -SkipPublisherCheck
Import-Module Pester -MinimumVersion 5.5.0
Get-Module Pester
Первый тест: проверка роли сервера
Задача — убедиться, что на сервере установлен IIS, запущена служба W3SVC и порт 80 слушается. Файл IIS.Tests.ps1:
Describe 'IIS base configuration on srv-web01' {
BeforeAll {
$target = 'srv-web01'
$features = Invoke-Command -ComputerName $target { Get-WindowsFeature Web-* }
$services = Invoke-Command -ComputerName $target { Get-Service W3SVC,WAS,WinRM }
}
Context 'Roles and features' {
It 'Web-Server role is installed' {
($features | Where-Object Name -eq 'Web-Server').Installed | Should -BeTrue
}
It 'ASP.NET 4.5 is installed' {
($features | Where-Object Name -eq 'Web-Asp-Net45').Installed | Should -BeTrue
}
}
Context 'Services' {
It 'W3SVC is running' { ($services | Where-Object Name -eq 'W3SVC').Status | Should -Be 'Running' }
It 'WAS is running' { ($services | Where-Object Name -eq 'WAS').Status | Should -Be 'Running' }
}
Context 'Network' {
It 'TCP port 80 is listening' {
(Test-NetConnection -ComputerName $target -Port 80).TcpTestSucceeded | Should -BeTrue
}
It 'TCP port 443 is listening' {
(Test-NetConnection -ComputerName $target -Port 443).TcpTestSucceeded | Should -BeTrue
}
}
}
Запуск: Invoke-Pester -Path .\IIS.Tests.ps1. Вывод цветной: зелёное — прошло, красное — сломано с указанием, чего не хватает.
Discovery и Run фазы в Pester 5
Pester 5 работает в две фазы:
- Discovery — проходит по всем файлам, строит дерево Describe/Context/It. В этот момент выполняется только код, собирающий структуру: BeforeDiscovery, параметры, -ForEach. Любые тяжёлые операции (Invoke-Command, запросы к AD) на этой фазе делать нельзя — сломается параметризация.
- Run — выполняет BeforeAll, BeforeEach, It-блоки. Здесь уже реальные запросы.
Это принципиально отличает v5 от v4. Если хотите параметризовать тесты списком серверов — используйте BeforeDiscovery:
BeforeDiscovery {
$servers = Get-Content .\servers.txt
}
Describe 'Base server health' -ForEach $servers {
BeforeAll {
$target = $_
$os = Get-CimInstance Win32_OperatingSystem -ComputerName $target
}
It 'Uptime less than 90 days on <_>' {
((Get-Date) - $os.LastBootUpTime).TotalDays | Should -BeLessThan 90
}
It 'C: free space > 10% on <_>' {
$c = Get-CimInstance Win32_LogicalDisk -ComputerName $target -Filter "DeviceID='C:'"
(100 * $c.FreeSpace / $c.Size) | Should -BeGreaterThan 10
}
}
Полный набор тестов для Windows Server baseline
Describe 'Baseline Windows Server compliance' -ForEach (Get-Content .\servers.txt) {
BeforeAll { $t = $_ }
Context 'Security' {
It 'Windows Defender real-time enabled on <_>' {
(Invoke-Command -ComputerName $t { (Get-MpComputerStatus).RealTimeProtectionEnabled }) |
Should -BeTrue
}
It 'RDP requires NLA on <_>' {
(Invoke-Command -ComputerName $t {
(Get-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp').UserAuthentication
}) | Should -Be 1
}
It 'Firewall is enabled on <_>' {
(Invoke-Command -ComputerName $t { (Get-NetFirewallProfile).Enabled }) |
Should -Not -Contain 'False'
}
}
Context 'Updates' {
It 'No pending reboot on <_>' {
(Invoke-Command -ComputerName $t {
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
}) | Should -BeFalse
}
}
Context 'Time' {
It 'NTP sync is healthy on <_>' {
(Invoke-Command -ComputerName $t { w32tm /query /status | Select-String 'Source' }) |
Should -Match 'corp\.ntp'
}
}
}
Pester Configuration и запуск
$config = New-PesterConfiguration
$config.Run.Path = '.\tests'
$config.Run.PassThru = $true
$config.Output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
$config.TestResult.OutputPath = '.\reports\pester-result.xml'
$config.CodeCoverage.Enabled = $false
$config.Filter.Tag = @('Security','Network') # только нужные теги
$result = Invoke-Pester -Configuration $config
Write-Host "Passed: $($result.PassedCount), Failed: $($result.FailedCount)"
exit [int]($result.FailedCount -gt 0)
Выход с ненулевым кодом при failed-тестах — стандартное условие для CI/CD: если тесты не прошли, pipeline красный.
Operational Validation Framework
У Microsoft есть пакет OperationValidation, который задаёт структуру директорий:
MyApp\
├── Diagnostics\
│ ├── Simple\ # быстрые тесты (минуты)
│ │ └── HealthCheck.Tests.ps1
│ └── Comprehensive\ # долгие (десятки минут, аудит)
│ └── FullCompliance.Tests.ps1
└── MyApp.psd1
Install-Module OperationValidation -Force
Invoke-OperationValidation -ModuleName 'MyApp' -TestType Simple
Это удобно для коллег: приходит новый админ, запускает одну команду — получает статус системы.
Мини-кейс: приёмка после миграции AD
В марте 2025 мы мигрировали клиента (розничная сеть, 180 РМ, 6 DC) с Windows Server 2012 R2 на 2022. После переноса нужно было быстро убедиться, что всё работает: функциональные уровни поднялись, SYSVOL работает на DFSR, FSMO на нужных DC, GPO на старом и новом среде совпадают, репликация здорова. Раньше это делали руками часа за 4, ночью, со страданиями.
Написали Pester-набор из 87 тестов (FSMO, репликация, SYSVOL, GPO-суммы, CNAME PDC, LDAP-поисковые атрибуты, группа Schema Admins пустая, DSRM-пароль одинаковый). Запустили сразу после миграции на виртуалке с Xeon Platinum 8280, 64 ГБ RAM, 40G Mellanox на дата-центре МТС — отработало за 6 минут. 3 теста красные: на одном DC не мигрировал SYSVOL на DFSR, на втором — неправильный DNS forwarder. Починили за 40 минут до утра понедельника, пользователи ничего не заметили. С тех пор одна и та же связка AD-тестов валидирует каждый проект миграции. Стоимость разработки базового набора — 60 тыс. руб.
Интеграция с GitLab CI
stages: [test]
pester:
stage: test
tags: [windows]
script:
- pwsh -c "Import-Module Pester; Invoke-Pester -Configuration (Import-PowerShellDataFile .\PesterConfig.psd1)"
artifacts:
when: always
reports:
junit: reports/pester-result.xml
paths:
- reports/
expire_in: 30 days
После запуска pipeline GitLab UI показывает сами тесты, время выполнения, красные/зелёные, историю. Команда сразу видит, какой тест начал фейлиться после какого коммита.
Моки и Arrange/Act/Assert
Для тестов скриптов и модулей (не инфраструктуры) Pester даёт мокинг:
Describe 'Get-DiskSpaceReport' {
BeforeAll { . "$PSScriptRoot\Get-DiskSpaceReport.ps1" }
It 'Filters drives with less than 10% free' {
Mock Get-CimInstance {
@(
[pscustomobject]@{ DeviceID='C:'; Size=100GB; FreeSpace=5GB },
[pscustomobject]@{ DeviceID='D:'; Size=100GB; FreeSpace=50GB }
)
}
$r = Get-DiskSpaceReport -ComputerName localhost -ThresholdPct 10
$r.Count | Should -Be 1
$r.DeviceID | Should -Be 'C:'
}
}
Best practices
- Каждый тест делает одну проверку. Не запихивайте 5 Should в один It.
- BeforeAll — собирает данные один раз, дальше все It работают с результатами. Экономит время.
- Теги (Tag) нужны для фильтрации: Simple, Comprehensive, Security, AfterDeploy.
- Параметризация через -ForEach для тестов на парк серверов.
- Держите тесты в репозитории рядом с кодом. Версионируйте, review-те, обсуждайте.
- Не тестируйте чужие модули — только свой код и своё состояние инфраструктуры.
- Не меняйте состояние системы в тестах. Тесты — read-only.
Внедрим Pester-тесты в вашу практику
Напишу набор инфраструктурных тестов под ваш парк: baseline Windows Server, AD-здоровье, SQL, Exchange, файловые серверы. Интеграция с GitLab CI или Jenkins. 15+ лет опыта с Windows-инфраструктурами от 30 до 800 рабочих мест. Тестовый стенд — Dell с Xeon Platinum 8280 и 40G Mellanox в дата-центре МТС, если нужна отдельная среда.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ — частые вопросы по Pester
- Чем Pester 5 отличается от 4?
- Pester 5 поддерживает Discovery и Run-фазы, новую конфигурацию через New-PesterConfiguration, BeforeDiscovery, Contexts с параметрами, параллельное выполнение. Синтаксис местами несовместим с 4.
- Нужен ли Pester, если у меня уже есть мониторинг?
- Мониторинг следит за текущим состоянием непрерывно. Pester-тесты проверяют соответствие эталону — удобно после установки, миграции, восстановления из бэкапа. Они дополняют мониторинг, а не заменяют.
- Как запускать Pester на удалённом сервере?
- Либо через Invoke-Command с передачей скрипта, либо разложить тесты локально и запускать по расписанию, складывая NUnitXml в сетевую шару. Второй вариант надёжнее — WinRM может быть отключён.
- Что такое Operational Validation Framework?
- OVF — подход от Microsoft: разделяем тесты на Simple (быстрые, для каждого деплоя) и Comprehensive (долгие, для аудита). Структура Diagnostics\Simple и Diagnostics\Comprehensive с модулем OperationValidation для запуска.
- Как интегрировать с GitLab CI или Azure DevOps?
- Запускаете pwsh -c 'Invoke-Pester -Configuration (New-PesterConfiguration) -OutputFile result.xml -OutputFormat NUnitXml', а CI-система парсит NUnit-отчёт и показывает в UI. GitLab понимает из коробки в artifacts.reports.junit.