Pester: тестирование инфраструктуры с PowerShell

Зачем тестировать инфраструктуру кодом

Инфраструктурные тесты — автоматизированная проверка того, что серверы, сервисы и конфигурации соответствуют ожидаемому состоянию. Вместо ручного чек-листа («проверить DNS», «проверить сертификаты», «проверить бэкапы») вы запускаете скрипт, который за минуту проверяет сотни параметров.

Pester — фреймворк тестирования для PowerShell, встроенный в Windows 10/11 и Windows Server 2016+. Синтаксис Pester интуитивен:

Describe "DNS Server" {
    It "Should resolve company.ru" {
        (Resolve-DnsName company.ru -ErrorAction Stop).IPAddress |
            Should -Contain "1.2.3.4"
    }
}

Типичные сценарии применения: проверка после деплоя (все ли сервисы запустились), регулярный аудит (не изменились ли критичные настройки), валидация перед обновлением (готова ли инфраструктура к апгрейду).

Установка и базовый синтаксис Pester 5

Windows поставляется с Pester 3.x, но рекомендуется использовать версию 5:

# Установка Pester 5
Install-Module -Name Pester -Force -SkipPublisherCheck
Import-Module Pester
Get-Module Pester | Select-Object Version

Структура теста Pester:

# tests/Server.Tests.ps1
Describe "Web Server Configuration" {

    Context "IIS Service" {
        It "W3SVC service should be running" {
            (Get-Service W3SVC).Status | Should -Be "Running"
        }

        It "Should listen on port 443" {
            $listener = Get-NetTCPConnection -LocalPort 443 -State Listen -ErrorAction SilentlyContinue
            $listener | Should -Not -BeNullOrEmpty
        }
    }

    Context "SSL Certificate" {
        It "Certificate should not expire within 30 days" {
            $cert = Get-ChildItem Cert:\LocalMachine\My |
                Where-Object { $_.Subject -match "company.ru" }
            ($cert.NotAfter - (Get-Date)).Days | Should -BeGreaterThan 30
        }
    }
}

Запуск тестов:

# Запуск конкретного файла
Invoke-Pester ./tests/Server.Tests.ps1 -Output Detailed

# Запуск всех тестов в директории
Invoke-Pester ./tests/ -Output Detailed

Тестирование Active Directory

Комплексный набор тестов для проверки здоровья AD:

Describe "Active Directory Health" {

    Context "Domain Controllers" {
        $DCs = Get-ADDomainController -Filter *

        foreach ($dc in $DCs) {
            It "$($dc.Name) should be reachable" {
                Test-Connection $dc.HostName -Count 1 -Quiet |
                    Should -BeTrue
            }

            It "$($dc.Name) NTDS service should be running" {
                $svc = Get-Service -ComputerName $dc.HostName -Name NTDS
                $svc.Status | Should -Be "Running"
            }

            It "$($dc.Name) SYSVOL should be shared" {
                Test-Path "\\$($dc.HostName)\SYSVOL" |
                    Should -BeTrue
            }
        }
    }

    Context "Replication" {
        It "No replication failures should exist" {
            $failures = Get-ADReplicationFailure -Target (Get-ADDomain).DNSRoot
            $failures | Should -BeNullOrEmpty
        }

        It "Replication should complete within 15 minutes" {
            $replStatus = Get-ADReplicationPartnerMetadata -Target * -Partition *
            $stale = $replStatus | Where-Object {
                $_.LastReplicationSuccess -lt (Get-Date).AddMinutes(-15)
            }
            $stale | Should -BeNullOrEmpty
        }
    }

    Context "DNS" {
        It "_ldap._tcp SRV record should exist" {
            $srv = Resolve-DnsName "_ldap._tcp.company.local" -Type SRV -ErrorAction Stop
            $srv | Should -Not -BeNullOrEmpty
        }
    }
}

Тестирование сетевой инфраструктуры

Проверка сетевых компонентов: DNS, маршруты, порты, VPN:

Describe "Network Infrastructure" {

    Context "DNS Resolution" {
        $dnsTests = @(
            @{ Name = "company.ru"; Expected = "1.2.3.4" },
            @{ Name = "mail.company.ru"; Expected = "1.2.3.5" },
            @{ Name = "vpn.company.ru"; Expected = "1.2.3.6" }
        )

        foreach ($test in $dnsTests) {
            It "$($test.Name) should resolve to $($test.Expected)" {
                $result = Resolve-DnsName $test.Name -Type A -ErrorAction Stop
                $result.IPAddress | Should -Contain $test.Expected
            }
        }
    }

    Context "Critical Ports" {
        $portTests = @(
            @{ Host = "db-srv-01"; Port = 5432; Service = "PostgreSQL" },
            @{ Host = "web-srv-01"; Port = 443; Service = "HTTPS" },
            @{ Host = "mail-srv-01"; Port = 25; Service = "SMTP" }
        )

        foreach ($test in $portTests) {
            It "$($test.Service) on $($test.Host):$($test.Port) should be accessible" {
                (Test-NetConnection $test.Host -Port $test.Port).TcpTestSucceeded |
                    Should -BeTrue
            }
        }
    }

    Context "Default Gateway" {
        It "Default gateway should respond" {
            $gw = (Get-NetRoute -DestinationPrefix "0.0.0.0/0").NextHop
            Test-Connection $gw -Count 1 -Quiet | Should -BeTrue
        }
    }
}

Тестирование серверов и сервисов

Набор тестов для проверки состояния серверов:

Describe "Server Health" -Tag "daily" {

    Context "Disk Space" {
        $volumes = Get-Volume | Where-Object { $_.DriveType -eq "Fixed" -and $_.DriveLetter }

        foreach ($vol in $volumes) {
            It "Drive $($vol.DriveLetter): should have >10% free space" {
                $freePercent = ($vol.SizeRemaining / $vol.Size) * 100
                $freePercent | Should -BeGreaterThan 10
            }
        }
    }

    Context "Windows Services" {
        $criticalServices = @("W3SVC", "MSSQLSERVER", "Spooler", "WinRM")

        foreach ($svc in $criticalServices) {
            It "Service $svc should be running" {
                $service = Get-Service -Name $svc -ErrorAction SilentlyContinue
                if ($service) {
                    $service.Status | Should -Be "Running"
                } else {
                    Set-ItResult -Skipped -Because "Service $svc not installed"
                }
            }
        }
    }

    Context "Windows Updates" {
        It "No critical updates should be pending for >30 days" {
            $session = New-Object -ComObject Microsoft.Update.Session
            $searcher = $session.CreateUpdateSearcher()
            $pending = $searcher.Search("IsInstalled=0 AND IsHidden=0").Updates
            $critical = $pending | Where-Object { $_.MsrcSeverity -eq "Critical" }
            $critical.Count | Should -BeLessThan 5
        }
    }
}

Удалённое тестирование серверов

Для проверки нескольких серверов используйте PowerShell Remoting:

$servers = @("srv-01", "srv-02", "srv-03")

Describe "Remote Server Checks" {
    foreach ($server in $servers) {
        Context "Server: $server" {
            $session = New-PSSession -ComputerName $server

            It "Uptime should be less than 90 days" {
                $uptime = Invoke-Command -Session $session -ScriptBlock {
                    (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
                }
                $uptime.Days | Should -BeLessThan 90
            }

            It "CPU usage should be below 90%" {
                $cpu = Invoke-Command -Session $session -ScriptBlock {
                    (Get-CimInstance Win32_Processor).LoadPercentage
                }
                $cpu | Should -BeLessThan 90
            }

            Remove-PSSession $session
        }
    }
}

Генерация отчётов и интеграция с CI/CD

Pester 5 поддерживает вывод в NUnit/JUnit XML для интеграции с CI-системами:

# Конфигурация запуска с отчётом
$config = New-PesterConfiguration
$config.Run.Path = "./tests"
$config.Output.Verbosity = "Detailed"
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = "NUnitXml"
$config.TestResult.OutputPath = "./test-results.xml"

Invoke-Pester -Configuration $config

Для интеграции с GitLab CI:

# .gitlab-ci.yml
infrastructure-tests:
  stage: test
  tags: [windows]
  script:
    - Import-Module Pester
    - $config = New-PesterConfiguration
    - $config.Run.Path = "./tests"
    - $config.TestResult.Enabled = $true
    - $config.TestResult.OutputFormat = "JUnitXml"
    - $config.TestResult.OutputPath = "test-results.xml"
    - Invoke-Pester -Configuration $config
  artifacts:
    reports:
      junit: test-results.xml

HTML-отчёт через ReportUnit

Для красивого HTML-отчёта используйте модуль ExtentReport или утилиту ReportUnit:

# Установка
Install-Module -Name PSWriteHTML -Force

# Генерация HTML из результатов Pester
$results = Invoke-Pester ./tests -PassThru
$results.Tests | ForEach-Object {
    [PSCustomObject]@{
        Name = $_.Name
        Result = $_.Result
        Duration = $_.Duration.TotalSeconds
        Error = $_.ErrorRecord.Exception.Message
    }
} | Out-HtmlView -Title "Infrastructure Test Report"

Автоматический запуск тестов по расписанию

Настройте регулярный запуск через Task Scheduler для ежедневного аудита:

# Создаём задачу через PowerShell
$action = New-ScheduledTaskAction -Execute "pwsh.exe" `
    -Argument '-File "C:\Scripts\Run-InfraTests.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At "07:00"
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest

Register-ScheduledTask -TaskName "Daily Infrastructure Tests" `
    -Action $action -Trigger $trigger `
    -Settings $settings -Principal $principal

Скрипт Run-InfraTests.ps1 запускает тесты и отправляет результаты:

# C:\Scripts\Run-InfraTests.ps1
Import-Module Pester

$config = New-PesterConfiguration
$config.Run.Path = "C:\Scripts\tests"
$config.Output.Verbosity = "None"
$results = Invoke-Pester -Configuration $config -PassThru

$failed = $results.FailedCount
$passed = $results.PassedCount
$total = $results.TotalCount

if ($failed -gt 0) {
    $body = "FAILED: $failed / $total tests.`n`n"
    $body += ($results.Tests | Where-Object Result -eq 'Failed' |
        ForEach-Object { "FAIL: $($_.Name) - $($_.ErrorRecord)" }) -join "`n"

    Send-MailMessage -To "admin@company.ru" -From "monitoring@company.ru" `
        -Subject "Infrastructure Tests: $failed failures" `
        -Body $body -SmtpServer "mail.company.ru"
}

Часто задаваемые вопросы

Нет. Pester 5 работает на PowerShell 7 (кроссплатформенный) — Linux и macOS поддерживаются. Однако тесты, использующие Windows-специфичные командлеты (Get-Service, Get-ADUser), работают только на Windows.

Используйте SSH-ремотинг PowerShell 7: $session = New-PSSession -HostName linux-srv -UserName admin -SSHTransport. Внутри сессии выполняйте команды Linux и проверяйте результаты через Pester-ассерции.

Pester 5 — полная переработка: новый discovery-pipeline (тесты сначала обнаруживаются, затем выполняются), конфигурация через New-PesterConfiguration, удалены устаревшие командлеты (Assert-MockCalled → Should -Invoke). Код тестов обратно совместим на 90%, но мок-функции требуют адаптации.

Разделите тесты по категориям: AD.Tests.ps1, Network.Tests.ps1, Backup.Tests.ps1. Используйте теги (-Tag "daily", -Tag "weekly") для запуска подмножеств. Параметры серверов храните в отдельном файле данных (PSD1 или JSON), а не в тестах.

Нужна помощь с настройкой?

Специалисты АйТи Фреш помогут с внедрением и настройкой — 15+ лет опыта, обслуживание от 15 000 ₽/мес

📞 Связаться с нами
#pester powershell#тестирование инфраструктуры#pester тесты#infrastructure as code тестирование#powershell тесты#pester настройка#аудит серверов powershell#operational validation
Комментарии 0

Оставить комментарий

загрузка...