Использование Pester для тестирования при разработке PowerShell скриптов

от автора

Когда пришлось писать сложные, большие скрипты на PowerShell и с течением времени изменять их, мне хотелось найти средство, которое позволит упростить проверку работоспособности моих скриптов. Таким средством оказался Pester — фреймворк для модульного тестирования.

О том, что он может и об основах его использования я и расскажу.

Pester позволяет писать тесты к любым исполняемым в powershell командам или скриптам. В том числе к функциям, кмдлетам, модулям. Он позволяет группировать тесты, так что вы можете запустить все тесты сразу, или тесты только определенной функции или тесты всех функций определенного скрипта.

Pester может быть запущен в консоли powershell или интегрирован в среду разработки. Pester поможет вам, если вы слышали про разработку через тестирование и хотели попробовать ее для разработки ваших скриптов. И если у вас есть уже готовые скрипты, для которых вы хотите сделать тесты – Pester тоже вам поможет.

Как начать.Загрузка и интеграция Pester с PowerShell ISE

Pester представляет собой модуль для powershell, написанный Scott Muc и опубликованный на Github. Для того, чтобы пользоваться Pester надо просто скачать его и распаковать в папку одну из папок Modules на вашем компьютере.

Что за папка Modules?

Папок Modules несколько. К примеру, папка Modules по пути %UserProfile%\Documents\WindowsPowerShell\Modules позволяет хранить модули, которые необходимы только вашей учетной записи. Причем, как правило, этой папки не существует, пока вы самостоятельно ее не создадите. А в папке %windir%\system32\WindowsPowerShell\v1.0\Modules хранятся модули, доступные всем пользователям.

Актуальный для вашей системы список папок для хранения модулей powershell хранится в переменной окружения $env:PSModulePath. К примеру, список папок Modules с моего компьютера:

PS C:\> $env:PSModulePath -split ';' F:\Users\sgerasimov\Documents\WindowsPowerShell\Modules C:\Windows\system32\WindowsPowerShell\v1.0\Modules\  

Этот список может меняться при установке программного обеспечения, к примеру средства администрирования Lync Server при установке добавляют к списку путь к папке со своими модулями.

Воспользуйтесь папкой Modules в профиле текущего пользователя. Создайте ее с помощью проводника или с помощью powershell, как показано ниже:

cd $env:USERPROFILE\documents new-item -Name WindowsPowerShell -ItemType directory new-item -Path .\WindowsPowerShell -Name Modules -ItemType directory 

После этого разархивируйте архив в папку Pester в папке Modules.

Чтобы интегрировать Pester с PowerShell ISE создайте в папке %UserProfile%\Documents\WindowsPowerShell файл Microsoft.PowerShellISE_profile.ps1 со следующим содержанием:

try {     Import-Module Pester } catch {     Write-Warning "Импорт модуля Pester не удался" }  

Если файл уже есть, то просто добавьте указанный выше код в файл.

Теперь, каждый раз, когда вы будете запускать PowerShell ISE модуль Pester будет подгружаться автоматически и вам останется лишь пользоваться им.

Как писать тесты и исполнять? Общая схема

Тесты пишутся в отдельных файлах. По-умолчанию предлагается следующее решение:

На каждый скрипт создается файл с именем имяскрипта.Tests.ps1. Например, у вас есть скрипт CreateUser.ps1 или вы планируете написать скрипт с таким именем. Тогда тесты для этого скрипта и его функций вы помещаете в файл CreateUser.Tests.ps1

Когда вы напишите тесты и будете запускать их, Pester будет просматривать все файлы с «.Tests.» в имени в текущем и во вложенных каталогах и выполнять тесты из них. Это позволяет, например, хранить файлы с тестами во вложенной папке, а не в папке со скриптами.

Файл тестов представляет собой powershell скрипт с группами тестов. Можно задавать несколько уровней вложенности групп тестов пользуясь командами Describe и Context. Команда It описывает 1 тест.

Приведу совсем простой пример, который нам продемонстрирует как пользоваться Pester для написания и выполнения тестов. Для понимания схемы.

Пример

Допустим у вас есть скрипт, возвращающий после выполнения “Hello World!” и вам надо написать для него тест.

Файл скрипта HelloWorld.ps1 у вас уже есть:

return "Hello world!" 

Создайте файл с именем HelloWorld.Tests.ps1. В нем будет находиться тест для вашего скрипта, который будет проверять, что скрипт после запуска возвращает “Hello world!”:

Describe "Проверка скрипта HelloWorld" {        it "Скрипт возвращает строку Hello World!" {      $result = .\HelloWorld.ps1     $result | Should Be "Hello World!"     } } 

Блок Describe описывает в целом какой скрипт тестируется, а в блоке It содержится сам тест. Вначале строкой

$result = .\HelloWorld.ps1 

осуществляется выполнение скрипта и получение его результатов, а затем строкой

$result | Should Be "Hello World!" 

описывается, каким должен быть полученный результат. Для этого используется команда Should которая выполняет проверку соответствия полученного значения заданному условию. А условие задается оператором Be, который говорит, что условие — это равенство строке «Hello World!».

Если проверка, заданная командой Should завершается успешно, то тест пройден, в ином случае тест считается проваленным.

Скопируйте код, указанный выше в файл HelloWorld.Tests.ps1 и сохраните этот файл.
После этого, убедитесь, что текущая директория указывает на папку, в которой находятся файлы HelloWorld.ps1 и HelloWorld.Tests.ps1. У меня это “F:\Projects\iLearnPester\Examples>” и выполните команду Invoke-Pester для запуска тестов:

Тест прошел успешно. Об этом свидетельствует зеленый цвет строки с названием теста (соответствует фразе после блока It). Если тест завершается неудачей, то названием теста выводится красным, а ниже указывается, что пошло не так.

Ожидалась строка «Hello World!», но скрипт вернул строку «Hello all!». Кроме того, указывается файл тестов и строка, на которой в файле тестов находится проваленный тест.

Если вы хотите попробовать разработку через тестирование. Тогда вы вначале пишете тест, а уж затем скрипт/функцию к нему.

Команда Should и оператор, следующий за ней (например, Be) вместе создают Утверждение. В Pester есть следующие утверждения:

  • Should Be
  • Should BeExactly
  • Should Exist
  • Should Contain
  • Should ContainExactly
  • Should Match
  • Should MatchExactly
  • Should Throw
  • Should BeNullOrEmpty

Внутрь утверждения всегда можно вставить Not и сделать отрицание, например: Should Not Be, Should Not Exist.

Расскажу подробнее про утверждения

Should Be

Сравнивает один объект с другим и выдает исключение, если объекты не равны. Сравниваются строки без учета регистра, числа, массивы чисел и строк. Пользовательские объекты (pscustomobject) и ассоциативные массивы не сравниваются.

#строки  $a = "строка" $a | Should Be "строка"         		#пройдет успешно $a | Should Be "СТРОКА"         		#пройдет успешно $a | Should Be "Другая строка"		#пройдет неудачно $a | Should Not Be "Другая строка"	#пройдет успешно  #числа  $a = 10 $a | Should Be 10         	#пройдет успешно $a | Should Be 2          	#пройдет неудачно $a | Should Not 2         	#пройдет успешно  #массивы чисел $a = 1,2,3 $a | Should Be 1,2,3		#пройдет успешно $a | Should Be 1,2,3,4		#пройдет успешно $a | Should Be 4,5,6		#пройдет неудачно  #массивы строк $a = "qwer","asdf","zxcv" $a | Should Be "qwer","asdf","ZXCV"	#пройдет успешно  $a | Should Be "qwer","asdf","zxcv", "rrr" 	#пройдет успешно 

Should BeExtactly

То же, что и Should Be, только строки сравниваются с учетом регистра

$actual="Actual value" $actual | Should BeExactly "Actual value" # пройдет успешно $actual | Should BeExactly "actual value" # пройдет неудачно 

Should Exist

Проверяет, что объект существует и доступен одному из PS провайдеров. Самое типичное — проверить что файл существует. По сути выполняет кмдлет test-path для переданного значения.

$actual=(Dir . )[0].FullName Remove-Item $actual $actual | Should Exist # Пройдет неудачно  import-module ActiveDirectory $ADObjectFQDN = "AD:CN=Some User,OU=Users,DC=company,DC=com" $ADObjectFQDN |  Should Exist # Пройдет успешно если пользователь есть  $registryKey = "HKCU:\Software\Microsoft\Driver Signing" $registryKey | Should Exist # Пройдет успешно если ветка реестра есть. 

Учтите, что можно проверить лишь наличие ветки реестра таким образом, но не какого-то конкретного ключа, т.к. PS провайдер, работающий с реестром дает доступ к ключам как к свойствам ветвей реестра. Он не считает их объектами.

Should Contain

Проверяет, что файл содержит заданный текст. Поиск выполняется без учета регистра и может использовать регулярные выражения.

Set-Content -Path c:\temp\file.txt -Value 'Съешь еще этих мягких французских булок' 'c:\temp\file.txt' | Should Contain 'Съешь Еще' # Пройдет успешно 'c:\temp\file.txt' | Should Contain 'Съешь*булок' # Пройдет успешно 

Should ContainExactly

Проверяет, что файл содержит заданный текст. Поиск выполняется с учетом регистра и может использовать регулярные выражения.

Set-Content -Path c:\temp\file.txt -Value 'Съешь еще этих мягких французских булок' 'c:\temp\file.txt' | Should Contain 'Съешь Еще' # Пройдет неудачно 'c:\temp\file.txt' | Should Contain 'Съешь*булок' # Пройдет успешно 

Should Match

Сравнивает две строки с использованием регулярных выражений без учета регистра.

"Вася" | Should Match ".ася" #  Пройдет успешно "Вася" | Should Match ([regex]::Escape(".ася")) #  Пройдет неудачно  

Should MatchExactly

Сравнивает две строки с использованием регулярных выражений с учетом регистра.

"Вася" | Should Match "ВАСЯ" #  Пройдет неудачно "Вася" | Should Match ".ася" #  Пройдет успешно 

Should Throw

Считается истинным, если в тестируемом скрипт-блоке происходит исключение. Можно так же указать ожидаемый текст исключения.

На вход передается скрипт-блок. С функциями, к сожалению, не работает.

{ необъявленнаяфункция } | Should Throw # Пройдет успешно  { throw "Ошибка в функции проверки параметров" } | Should Throw "Ошибка в функции проверки параметров" # Пройдет успешно  { throw "Ошибка в функции проверки результатов" } | Should Throw "Ошибка в функции проверки параметров" # Пройдет неудачно  {throw "Ошибка в функции проверки результатов"} | Should Throw "результатов" # Пройдет успешно   { $foo = 1 } | Should Not Throw # Пройдет успешно 

Should BeNullOrEmpty

Проверяет, что переданное значение равно $null или пусто (для строки, массива и т.п.). Тут стоит напомнить, что $null это не 0.

$a = $null $b = 0 $c = [string]"" $d = @()  $a | Should BeNullOrEmpty  # Пройдет успешно $b | Should BeNullOrEmpty  # Пройдет неудачно $c | Should BeNullOrEmpty  # Пройдет успешно $d | Should BeNullOrEmpty  # Пройдет успешно  

Что он еще умеет?

Mock-функции.

В Pester есть mock-функции, которые позволяют перед вызовом теста переопределить какую-либо функцию или кмдлет.

Например, вы разрабатываете скрипт, который будет получать ip-адрес текущей машины и в зависимости от того к какой сети принадлежит этот адрес прописывать тот или иной dns-сервер в настройках адаптера. Но у вашей машины, на которой вы разрабатываете скрипт всего 1 ip адрес и менять его для тестов хлопотно. Тогда вы просто перед вызовом теста переопределите функцию, получающую ip-адрес так, чтобы она возвращала не текущий адрес, а нужный для проверки.

Вот эскиз нашего скрипта (назовем SmartChangeDNS.ps1).

$MoskowNetworkMask = "192.168.1.0/24" $RostovNetworkMask = "192.168.2.0/24"  $IPv4Addresses = GetIPv4Addresses foreach($Address in $IPv4Addresses) {     if(CheckSubnet -cidr $MoskowNetworkMask -ip $Address)     {         #устанавливаете dns 192.168.1.1     }      if(CheckSubnet -cidr $RostovNetworkMask -ip $Address)     {         #устанавливаете dns 192.168.2.1     } }  

Он знает 2 маски сети в Москве и Ростове. Получает с помощью функции GetIPv4Addresses все IPv4 адреса текущей машины и дальше в цикле foreach проверяет принадлежность какого-либо адреса подсети функцией CheckSubnet. Функции GetIPv4Addresses и CheckSubnet вы уже написали и проверили. Теперь, чтобы проверить функции в целом, нам надо написать тесты, в которых мы переопределим функцию GetIPv4Addresses так, чтобы она возвращала нужный адрес. Вот как это делается:

describe "SmartChangeDNS" {      it "если компьютер в сети 192.168.1.0/24" {         Mock GetIPv4Addresses {return "192.168.1.115"}         .\SmartChangeDNS.ps1          $DNSServerAddres = Get-DnsClientServerAddress -InterfaceAlias "Ethernet" -AddressFamily IPv4 | Select -ExpandProperty ServerAddresses          $DNSServerAddres | Should Be "192.168.1.1"     }      it "если компьютер в сети 192.168.2.0/24" {         Mock GetIPv4Addresses {return "192.168.2.20"}         .\SmartChangeDNS.ps1          $DNSServerAddres = Get-DnsClientServerAddress -InterfaceAlias "Ethernet" -AddressFamily IPv4 | Select -ExpandProperty ServerAddresses          $DNSServerAddres | Should Be "192.168.2.1"     }  }  

Теперь при исполнении скрипта дело дойдет до выполнения функции GetIPv4Addresses, будет исполнена не та ее версия, что указана в скрипте, а та, которую мы определили командой Mock.

Переопределение функций с помощью Mock позволяет абстрагироваться, когда нужно, от внешних систем, модулей или вызываемых функций.

TestDrive

Pester так же предоставляет временный PS-диск, который можно использовать для работы с файловой системой в рамках выполнения тестов. Такой диск существует в рамках одного блока Describe или Context.

Если диск создан в блоке Describe, то он и все файлы созданные на нем видны и доступны для модификации в блоках Context. Файлы, созданные в блоке Context с завершением этого блока удаляются и остаются лишь файлы, созданные в блоке Describe.

ссылка на оригинал статьи http://habrahabr.ru/post/264697/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *