Skip to main content

Measuring coverage

Running the full test suite now will show twenty-four tests passing. That tells you the things you thought to test work. It says nothing about the code you forgot.

Code coverage answers a narrower question than people usually assume: which lines of my code ran while the tests ran? Not whether they were tested well — just whether they executed at all. Lines that never execute are by definition untested, and that is worth knowing.

Turning it on

Code Coverage is enabled through the configuration object. Update test.ps1 and try it out:

test.ps1
$config = New-PesterConfiguration
$config.Run.Path = './Planetarium'
$config.Output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputPath = './testResults.xml'
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = './Planetarium'

Invoke-Pester -Configuration $config
./test.ps1
Tests completed in 870ms
Tests Passed: 24, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Processing code coverage result.
Covered 100% / 75%. 36 analyzed Commands in 7 Files.

Read that last line carefully — it trips people up. 100% is what you achieved; 75% is the target you are being measured against. The target is CodeCoverage.CoveragePercentTarget, which defaults to 75.

Make sure CodeCoverage.Path points to your code

Run.Path says which tests to run; CodeCoverage.Path says which code to measure. It will default to Run.Path which works in this tutorial, but might return 0% coverage if you used a dedicated tests folder. Set both options explicitly to avoid surprises.

Pester v6 uses a profiler-based tracer by default, which is fast enough to leave on routinely.

100% is not the finish line

The module is at 100%, and it would be a mistake to read that as "fully tested".

Coverage measures execution, not assertion. This test would give ConvertTo-AstronomicalUnit full coverage while checking nothing at all:

It 'Runs' {
InModuleScope Planetarium { ConvertTo-AstronomicalUnit -Kilometre 149597870.7 }
}

The line ran, so it is covered. Nothing was asserted, so it is untested. Coverage cannot tell those apart — which is why chasing a perfect coverage as a goal can lead to poor tests.

What coverage is genuinely good at is the opposite direction: it finds code you forgot entirely. A 100% score is weak evidence of quality; an uncovered line is strong evidence of a gap.

Adding a feature

Time to create a real gap the way it actually happens — by adding a feature.

Export-PlanetReport currently overwrites without asking, which is unfriendly for something that writes files. Add a guard:

Planetarium/Public/Export-PlanetReport.ps1
function Export-PlanetReport {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string] $Path,

[string] $Name = '*'
[string] $Name = '*',

[switch] $Force
)

$planets = @(Get-Planet -Name $Name)

if ($planets.Count -eq 0) {
throw "No planets matched '$Name'."
}

if ((Test-Path -Path $Path) -and -not $Force) {
throw "'$Path' already exists. Use -Force to overwrite it."
}

$report = foreach ($planet in $planets) {
'{0,-8} {1} AU' -f $planet.Name, (ConvertTo-AstronomicalUnit -Kilometre $planet.DistanceFromSunKm)
}

Set-Content -Path $Path -Value $report
}

Run the suite:

./test.ps1
Tests Passed: 24, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0

Still all green. Every existing test writes to a fresh path in TestDrive, so none of them hits the new branch — and none of them fails. Nothing in that output hints that you just shipped an untested code path. Code Coverage does:

Covered 95% / 75%. 40 analyzed Commands in 7 Files.
Missed commands:

File Class Function Line Command
---- ----- -------- ---- -------
Planetarium/Public/Export-PlanetReport.ps1 Export-PlanetReport 19 throw "'$Path' already exists. Use -Force to …
Planetarium/Public/Export-PlanetReport.ps1 Export-PlanetReport 19 throw "'$Path' already exists. Use -Force to …

Before you move on

0/5

Next: testing the new feature to close the gap.