diff --git a/Examples/Planets/Get-Planet.Tests.ps1 b/Examples/Planets/Get-Planet.Tests.ps1 new file mode 100644 index 000000000..fdd87fc51 --- /dev/null +++ b/Examples/Planets/Get-Planet.Tests.ps1 @@ -0,0 +1,151 @@ +# In practice tests and your code are placed in two +# separate files. Tests belong in .Tests.ps1 file and code belongs +# in .ps1 file. Open the Get-Planet.ps1 file as well, please. + + +# You can run this test file by pressing F5, if your editor +# suports running powershell. + +# You should see this output: +# Describing Get-Planet +# [+] Given no parameters, it lists all 8 planets 55ms +# +# Context Filtering by Name +# [+] Given valid -Name 'Earth', it returns 'Earth' 61ms +# [+] Given valid -Name 'ne*', it returns 'Neptune' 11ms +# [+] Given valid -Name 'ur*', it returns 'Uranus' 19ms +# [+] Given valid -Name 'm*', it returns 'Mercury Mars' 9ms +# [+] Given invalid parameter -Name 'Alpha Centauri', it returns $null 22ms + + +# First we need to import the Get-Planet.ps1 file to make the function +# Get-Planet available to our test. Notice the . at the start +# of the line. +$here = (Split-Path -Parent $MyInvocation.MyCommand.Path) +. $here\Get-Planet.ps1 + +# Normally we would use this PowerShell 3 and newer compatible +# version of the same code, but we need to keep our examples +# compatible with PowerShell v2. +# . $PSScriptRoot\Get-Planet.ps1 + + +# Describe groups tests for easy navigation and overview. +# Usually we use the name of the function we are testing as description +# for our test group. +Describe 'Get-Planet' { + + # 'It' performs a single test. We write informative description + # to tell others what is the result we expect. In this case + # we expect that calling Get-Planet without any parameters will + # return 8 items, because that is how many planets there are in our + # solar system. + It 'Given no parameters, it lists all 8 planets' { + # In the body of the test we repeat what our description says, + # but this time in code. + + # We call our Get-Planet function without any parameters + # and store the result for later examination. + $allPlanets = Get-Planet + + # We count how many planets we got. And validate it by using + # the Should -Be assertion. + $allPlanets.Count | Should -Be 8 + + # The assertion will do nothing if the count is 8, + # and throw an exception if the count is something else. + # Yes, it is this simple: if ($count -ne 8) { throw "Count is wrong"} + } + + # Context is the same as Describe, it groups our tests. Here we use + # it to group tests for filtering planets by name. + Context "Filtering by Name" { + + # We want our function to filter planets by name when -Name parameter is + # provided, and we want it to support wildcards in the name, because that + # is what most other functions do, and people expect this to be possible. + + + # We could write many individual tests to test this functionality, + # but most of them would be the same except for the data. So a better + # option is to use TestCases to provide multiple sets of data for our test + # but keep the body of the test the same. Pester then generates one test + # for each test case, and injects our values in parameters. + # This allows us to easily add more test cased as bugs start popping up, without + # duplicating code. + + + #There are three steps to make this work: description, paramaters, and testcases. + + # We put names of our parameters in the description and sorround them by <>. + # Pester will expand test values into desciption, for example: + # Given valid -Name 'ne*', it returns 'Neptune' + It "Given valid -Name '', it returns ''" -TestCases @( + + # We define an array of hashtables. Each hashtable will be used + # for one test. + # @{ Filter = 'ne*' ; Expected = 'Neptune' } + # Every hashtable has keys named as our parameters, that is Filter and Expected. + # And values that will be injected in our test, in this case 'ne*' and 'Neptune'. + @{ Filter = 'Earth'; Expected = 'Earth' } + @{ Filter = 'ne*' ; Expected = 'Neptune' } + @{ Filter = 'ur*' ; Expected = 'Uranus' } + @{ Filter = 'm*' ; Expected = 'Mercury', 'Mars' } + ) { + + # We define parameters in param (), to pass our test data into the test body. + # Paremeter names must align with key names in the hashtables. + param ($Filter, $Expected) + + # We pass $Filter to -Name, for example 'ne*' in our second test. + $planets = Get-Planet -Name $Filter + # We validate that the returned name is equal to $Expected. + # That is Neptune, in our second test. + $planets | Select -ExpandProperty Name | Should -Be $Expected + + # again we are jumping thru hoops to keep PowerShell v2 compatibility + # in PowerShell v3 you would just do this, as seen in readme: + # $planets.Name | Should -Be $Expected + } + + # Testing just the positive cases is usually not enough. Our tests + # should also check that providing filter that matches no item returns + # $null. We could merge this with the previous test but it is better to + # split positive and negative cases, even if that means duplicated code. + # Normally we would use TestCases here as well, but let's keep it simple + # and show that Should -Be is pretty versatile in what it can assert. + It "Given invalid parameter -Name 'Alpha Centauri', it returns `$null" { + $planets = Get-Planet -Name 'Alpha Centauri' + $planets | Should -Be $null + } + } +} + +# Want to try it out yourself? + +## Excercise 1: +# Add filter Population that returns planets with population larger +# or equal to the given number (in billions). +# Use 7.5 as the population of Earth. Use 0 for all other planets. + +# Make sure to cover these test cases: +# - Population 7.5 returns Earth +# - Population 0 returns all planets +# - Population -1 returns no planets + + + +# Excercise 2: Test that planets are returned in the correct order, +# from the one closest to the Sun. +# Make sure to cover these test cases: +# - Order of planets is correct when no filters are used. +# - Order of planets is correct when -Name filter is used. + + + +# Excercise 3 (advanced): Add function that will list moons orbiting a given planet. +# - Make sure you can list all moons. +# - Make sure you can filter moons for given planet. +# - Make sure you Get-Planet and Get-Moon functions work together. +# $moons = Get-Planet Earth | Get-Moon +# $moons.Name | Should -Be Moon diff --git a/Examples/Planets/Get-Planet.ps1 b/Examples/Planets/Get-Planet.ps1 new file mode 100644 index 000000000..ef83bc9e9 --- /dev/null +++ b/Examples/Planets/Get-Planet.ps1 @@ -0,0 +1,23 @@ +# This is not the best file to start from, +# open Get-Planet.Tests.ps1 as well :) + + +function Get-Planet ([string]$Name = '*') +{ + $planets = @( + @{ Name = 'Mercury' } + @{ Name = 'Venus' } + @{ Name = 'Earth' } + @{ Name = 'Mars' } + @{ Name = 'Jupiter' } + @{ Name = 'Saturn' } + @{ Name = 'Uranus' } + @{ Name = 'Neptune' } + ) | foreach { New-Object -TypeName PSObject -Property $_ } + + $planets | where { $_.Name -like $Name } +} + +# The code above uses New-Object instead of the [PSCustomObject] +# you saw in the readme file. This is only to keep the example +# compatible with PowerShell version 2. diff --git a/README.md b/README.md index a09d10c91..8efa72f48 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,154 @@ -__Build Status:__ [![Build status](https://build.powershell.org/guestAuth/app/rest/builds/buildType:(id:Pester_TestPester)/statusIcon)](https://build.powershell.org/project.html?projectId=Pester&tab=projectOverview&guest=1) +# Pester -Pester 3.0 has been released! To see a list of changes in this version, refer to the [What's New in Pester 3.0?](https://github.com/pester/Pester/wiki/What's-New-in-Pester-3.0) Wiki page. +Pester is the ubiquitous test and mock framework for PowerShell. ---- - -[![Join the chat at https://gitter.im/pester/Pester](https://badges.gitter.im/pester/Pester.svg)](https://gitter.im/pester/Pester?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +```powershell +# your function +function Get-Planet ([string]$Name='*') +{ + $planets = @( + @{ Name = 'Mercury' } + @{ Name = 'Venus' } + @{ Name = 'Earth' } + @{ Name = 'Mars' } + @{ Name = 'Jupiter' } + @{ Name = 'Saturn' } + @{ Name = 'Uranus' } + @{ Name = 'Neptune' } + ) | foreach { [PSCustomObject]$_ } + + $planets | where { $_.Name -like $Name } +} -Pester -======= -Pester provides a framework for **running unit tests to execute and validate PowerShell commands from within PowerShell**. Pester consists of a simple set of functions that expose a testing domain-specific language (DSL) for isolating, running, evaluating and reporting the results of PowerShell commands. +# Pester tests +Describe 'Get-Planet' { + It "Given no parameters, it lists all 8 planets" { + $allPlanets = Get-Planet + $allPlanets.Count | Should -Be 8 + } + + Context "Filtering by Name" { + It "Given valid -Name '', it returns ''" -TestCases @( + @{ Filter = 'Earth'; Expected = 'Earth' } + @{ Filter = 'ne*' ; Expected = 'Neptune' } + @{ Filter = 'ur*' ; Expected = 'Uranus' } + @{ Filter = 'm*' ; Expected = 'Mercury', 'Mars' } + ) { + param ($Filter, $Expected) + + $planets = Get-Planet -Name $Filter + $planets.Name | Should -Be $Expected + } -Pester tests can execute any command or script that is accessible to a Pester test file. This can include functions, cmdlets, modules and scripts. Pester can be run in *ad-hoc* style in a console or **it can be integrated into the build scripts of a continuous integration (CI) system**. + It "Given invalid parameter -Name 'Alpha Centauri', it returns `$null" { + $planets = Get-Planet -Name 'Alpha Centauri' + $planets | Should -Be $null + } + } +} +``` -**Pester also contains a powerful set of mocking functions** in which tests mimic any command functionality within the tested PowerShell code. +This code example lies a tiny bit, [find it annotated and production ready here](Examples/Planets). -Updating Pester on Windows 10 and Windows Server 2016 ------------ -There's a bit of a confusion with the version of Pester that ships in Windows 10 and Windows Server 2016. These operating systems by default have installed Pester version 3.4.0. Microsoft signed the Pester files (which they were required to do), but then PowerShellGet blows up when you try to update the module. Here's the command you need to run in order to get the latest version of Pester the first time on a Windows 10 system: +Learn more about the [usage and syntax](https://github.com/Pester/Pester/wiki) on our wiki. -`Install-Module Pester -Force -SkipPublisherCheck` +## Installation -Once that's done, you should be able to use a simple `Update-Module Pester` command in the future. +Pester is compatible with Windows 10, 8, 7, Vista and even 2003. We are also working hard on making it run on Linux and MacOS. -A Pester Test -------------- -BuildChanges.ps1 +Pester comes pre-installed with Windows 10, but we recommend updating, by running this PowerShell command _as administrator_: ```powershell +Install-Module -Name Pester -Force -SkipPublisherCheck +``` -function Build ($version) { - write-host "A build was run for version: $version" -} +Not running Windows 10 or facing problems? See the [full installation and update guide](https://github.com/pester/Pester/wiki/Installation-and-Update). -function BuildIfChanged { - $thisVersion=Get-Version - $nextVersion=Get-NextVersion - if($thisVersion -ne $nextVersion) {Build $nextVersion} - return $nextVersion -} +## Features -# Imagine that the following functions have heavy side-effect -function Get-Version { - throw New-Object NotImplementedException -} +### Test runner + +Pester runs your tests and prints a nicely formatted output to the screen. + +![test run output](doc/readme/output.PNG) + +Command line output is not the only output option, Pester also integrates with Visual Studio Code, Visual Studio, and any tool that can consume nUnit XML output. + +### Assertions -function Get-NextVersion { - throw New-Object NotImplementedException +Pester comes with a suite of assertions that cover a lot of common use cases. Pester assertions range from very versatile, like `Should -Be`, to specialized like `Should -Exists`. Here is how you ensure that a file exists: + +```powershell +Describe 'Notepad' { + It 'Exists in Windows folder' { + 'C:\Windows\notepad.exe' | Should -Exist + } } ``` -BuildChanges.Tests.ps1 +Learn more about assertion on [our wiki](https://github.com/pester/Pester/wiki/Should). + +### Mocking + +Pester has mocking built-in. Using mocks you can easily replace functions with empty implementation to avoid changing the real environment. ```powershell -$here = Split-Path -Parent $MyInvocation.MyCommand.Path -$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' -. "$here\$sut" - -Describe "BuildIfChanged" { - Context "When there are changes" { - Mock Get-Version {return 1.1} - Mock Get-NextVersion {return 1.2} - Mock Build {} -Verifiable -ParameterFilter {$version -eq 1.2} - - $result = BuildIfChanged - - It "Builds the next version" { - Assert-VerifiableMocks - } - It "Returns the next version number" { - $result | Should Be 1.2 - } - } - Context "When there are no changes" { - Mock Get-Version -MockWith {return 1.1} - Mock Get-NextVersion -MockWith {return 1.1} - Mock Build {} +function Remove-Cache { + Remove-Item "$env:TEMP\cache.txt" +} + +Describe 'Remove-Cache' { + It 'Removes cached results from temp\cache.text' { + Mock -CommandName Remove-Item -MockWith {} - $result = BuildIfChanged + Remove-Cache - It "Should not build the next version" { - Assert-MockCalled Build -Times 0 -ParameterFilter {$version -eq 1.1} - } + Assert-MockCalled -CommandName Remove-Item -Times 1 -Exactly } } ``` -Running Tests -------------- - C:\PS> Invoke-Pester +Learn more [about Mocking here](https://github.com/pester/Pester/wiki/Mock). -This will run all tests inside of files named `*.Tests.ps1` recursively from the current directory and print a report of all failing and passing test results to the console. +### Code coverage - C:\PS> Invoke-Pester -TestName BuildIfChanged +Pester can measure how much of your code is covered by tests and export it to JaCoCo format that is easily understood by build servers. + +![JaCoCo code coverage report](doc/readme/jacoco.PNG) + +Learn more about [code coverage here](https://github.com/pester/Pester/wiki/Code-Coverage). + +### Build server integration + +Pester integrates nicely with TFS, AppVeyor, TeamCity, Jenkins and other CI servers. + +Testing your scripts, and all pull requests on AppVeyor is extremely simple. Just commit this `appveyor.yml` file to your repository, and select your repository on the AppVeyor website: + +```yml +version: 1.0.{build} +image: WMF 5 +install: +- ps: choco install pester +build: off +test_script: +- ps: Invoke-Pester -EnableExit +``` -You can also run specific tests by using the `-TestName` parameter of the `Invoke-Pester` command. The above example runs all tests with a `Describe` block named `BuildIfChanged`. If you want to run multiple tests, you can pass a string array into the `-TestName` parameter, similar to the following example: +See it [in action here!](https://ci.appveyor.com/project/nohwnd/planets) - C:\PS> Invoke-Pester -TestName BuildIfChanged, BaconShouldBeCrispy +Pester itself is build on the [community build server](build.powershell.org). -Continuous Integration with Pester ------------------------------------ +[![Build status](https://build.powershell.org/guestAuth/app/rest/builds/buildType:(id:Pester_TestPester)/statusIcon)](https://build.powershell.org/project.html?projectId=Pester&tab=projectOverview&guest=1) -Pester integrates well with almost any build automation solution. There are several options for this integration: +## Further reading -- The `-OutputFile` parameter allows you to export data about the test execution. Currently, this parameter allows you to produce NUnit-style XML output, which any modern CI solution should be able to read. -- The `-PassThru` parameter can be used if your CI solution supports running PowerShell code directly. After Pester finishes running, check the FailedCount property on the object to determine whether any tests failed, and take action from there. -- The `-EnableExit` switch causes Pester to exit the current PowerShell session with an error code. This error code will be the number of failed tests; 0 indicates success. +Do you like what you see? Learn how to use Pester with our [Getting started guide](https://github.com/pester/Pester/wiki/Getting-started-with-Pester), and continue with some of the other [resources](https://github.com/pester/Pester/wiki/Articles-and-other-resources). -As an example, there is also a file named `Pester.bat` in the `bin` folder which shows how you might integrate with a CI solution that does not support running PowerShell directly. By wrapping a call to `Invoke-Pester` in a batch file, and making sure that batch file returns a non-zero exit code if any tests fail, you can still use Pester even when limited to cmd.exe commands in your CI jobs. +## Got questions? -Whenever possible, it's better to run Invoke-Pester directly (either in an interactive PowerShell session, or using CI software that supports running PowerShell steps in jobs). This is the method that we test and support in our releases. +Got questions or you just want to get in touch? Use our issues page or one of these channels: -For Further Learning: ------------------------------------ -* [Getting started with Pester](http://www.powershellmagazine.com/2014/03/12/get-started-with-pester-powershell-unit-testing-framework/) -* [Testing your scripts with Pester, Assertions and more](http://www.powershellmagazine.com/2014/03/27/testing-your-powershell-scripts-with-pester-assertions-and-more/) -* [Writing Pester Tests](https://github.com/PowerShell/PowerShell/blob/master/docs/testing-guidelines/WritingPesterTests.md) -* [Pester Wiki](https://github.com/pester/Pester/wiki) -* [Google Discussion Group](https://groups.google.com/forum/?fromgroups#!forum/pester) -* `C:\PS> Import-Module ./pester.psm1; Get-Help about_pester` -* Microsoft's PowerShell test suite itself is being converted into Pester tests. [See the PowerShell-Tests repository.](https://github.com/PowerShell/PowerShell-Tests) -* Note: The following two links were for Pester v1.0. The syntax shown, particularly for performing assertions with Should, is no longer applicable to later versions of Pester. - * [powershell-bdd-testing-pester-screencast](http://scottmuc.com/blog/development/powershell-bdd-testing-pester-screencast/) - * [pester-bdd-for-the-system-administrator](http://scottmuc.com/blog/development/pester-bdd-for-the-system-administrator/) +[![Pester Twitter](doc/readme/twitter-64.PNG)](https://twitter.com/PSPester) +[![Pester on StackOverflow](doc/readme/stack-overflow-64.PNG)](https://stackoverflow.com/questions/tagged/pester) +[![Testing channel on Powershell Slack](doc/readme/slack-64.PNG)](https://powershell.slack.com/messages/C03QKTUCS) +[![Pester Gitter](doc/readme/gitter-64.PNG)](https://gitter.im/pester/Pester?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/doc/readme/gitter-64.PNG b/doc/readme/gitter-64.PNG new file mode 100644 index 000000000..3ec4d112e Binary files /dev/null and b/doc/readme/gitter-64.PNG differ diff --git a/doc/readme/jacoco.PNG b/doc/readme/jacoco.PNG new file mode 100644 index 000000000..071654205 Binary files /dev/null and b/doc/readme/jacoco.PNG differ diff --git a/doc/readme/output.PNG b/doc/readme/output.PNG new file mode 100644 index 000000000..91bf28427 Binary files /dev/null and b/doc/readme/output.PNG differ diff --git a/doc/readme/slack-64.PNG b/doc/readme/slack-64.PNG new file mode 100644 index 000000000..21ad5f8e4 Binary files /dev/null and b/doc/readme/slack-64.PNG differ diff --git a/doc/readme/stack-overflow-64.PNG b/doc/readme/stack-overflow-64.PNG new file mode 100644 index 000000000..9fc936843 Binary files /dev/null and b/doc/readme/stack-overflow-64.PNG differ diff --git a/doc/readme/twitter-64.PNG b/doc/readme/twitter-64.PNG new file mode 100644 index 000000000..09944fc4d Binary files /dev/null and b/doc/readme/twitter-64.PNG differ