plugins/languages/powershell/skills/testing/SKILL.md
PowerShell testing with Pester 5.x (Describe/Context/It, BeforeAll/AfterAll/BeforeEach, Should -Be / -Throw / -Match / -BeOfType, Mock with parameter filter, InModuleScope, TestDrive/TestRegistry, tags & filters, code coverage, NUnit XML output) and static analysis with PSScriptAnalyzer (custom rules, Invoke-ScriptAnalyzer -EnableExit, Settings.psd1). Covers CI integration (-CI flag), test discovery layout, fixtures, and migration notes from Pester 4 to 5. Use proactively when the user asks "写 PowerShell 测试 / pester 用例 / mock cmdlet / 静态分析 / PSScriptAnalyzer". Also triggers on "Pester", "Should", "Mock", "InModuleScope", "TestDrive", "Invoke-ScriptAnalyzer", "PSSA".
npx skillsauth add lazygophers/ccplugin powershell-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| 工具 | 角色 | 安装 |
|------|------|------|
| Pester 5.x | BDD 测试框架 | Install-PSResource Pester |
| PSScriptAnalyzer | 静态分析 | Install-PSResource PSScriptAnalyzer |
Pester 5 是 2026 主流,与 Pester 4 不向后兼容(行为差异详见迁移指南)。
Tests/MyModule.Tests.ps1:
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.5.0' }
BeforeAll {
$modulePath = Join-Path $PSScriptRoot '..' 'MyModule.psd1'
Import-Module $modulePath -Force
}
AfterAll {
Remove-Module MyModule -ErrorAction SilentlyContinue
}
Describe 'Get-Foo' -Tag 'Unit' {
Context 'when input is valid' {
BeforeEach {
$script:fixture = @{ Name = 'alpha' }
}
It 'returns the expected object' {
$result = Get-Foo -Name $fixture.Name
$result | Should -Not -BeNullOrEmpty
$result.Name | Should -Be 'alpha'
}
It 'has correct type' {
Get-Foo -Name $fixture.Name | Should -BeOfType [pscustomobject]
}
}
Context 'when input is invalid' {
It 'throws ArgumentException' {
{ Get-Foo -Name '' } | Should -Throw -ExceptionType ([System.ArgumentException])
}
}
}
| 断言 | 含义 |
|------|------|
| Should -Be 5 | 严格相等 |
| Should -BeExactly 'Foo' | 区分大小写 |
| Should -Not -BeNullOrEmpty | 非空 |
| Should -BeOfType [int] | 类型 |
| Should -Match 'pattern' | 正则 |
| Should -Contain 'item' | 集合包含 |
| Should -HaveCount 3 | 集合长度 |
| Should -Throw | 抛异常 |
| Should -Throw -ExceptionType ([IOException]) | 抛指定类型 |
| Should -Invoke -CommandName X -Times 1 | mock 调用次数 |
| Should -InvokeVerifiable | 全部 verifiable mock 都被调 |
Describe 'Send-Notification' {
BeforeAll {
Mock -CommandName Invoke-RestMethod -ModuleName MyModule -MockWith {
return @{ ok = $true }
}
}
It 'calls REST endpoint once' {
Send-Notification -Message 'hi'
Should -Invoke Invoke-RestMethod -Times 1 -Exactly -ModuleName MyModule
}
It 'mocks with parameter filter' {
Mock Invoke-RestMethod -ParameterFilter { $Uri -like '*api/v1/*' } -MockWith {
return @{ filtered = $true }
}
# ...
}
}
关键点:
-ModuleName,否则 mock 不生效。-ParameterFilter 用脚本块匹配特定参数组合。-Verifiable 标记后用 Should -InvokeVerifiable 一次性验证。InModuleScope MyModule {
Describe 'Internal helpers' {
It 'normalizes path' {
Get-NormalizedPath '/tmp//foo/' | Should -Be '/tmp/foo'
}
}
}
It 'writes to disk' {
$path = Join-Path $TestDrive 'out.txt'
Set-Content -Path $path -Value 'hi'
Get-Content $path | Should -Be 'hi'
# 测试结束自动清理 TestDrive
}
TestRegistry 同理但作用 Windows 注册表。
Describe 'integration' -Tag 'Integration', 'Slow' { ... }
# 跑特定标签
Invoke-Pester -Tag Unit
Invoke-Pester -ExcludeTag Slow
$config = New-PesterConfiguration
$config.Run.Path = './Tests'
$config.Run.Exit = $true # 失败时进程非零退出
$config.Output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'NUnitXml'
$config.TestResult.OutputPath = 'testResults.xml'
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = './Public', './Private'
$config.CodeCoverage.OutputFormat = 'JaCoCo'
$config.CodeCoverage.OutputPath = 'coverage.xml'
Invoke-Pester -Configuration $config
CI 一行:
Invoke-Pester -CI # 等价启用 Exit / NUnitXml / 覆盖率
PSScriptAnalyzerSettings.psd1@{
Severity = @('Error', 'Warning')
IncludeRules = @('PS*')
ExcludeRules = @(
'PSUseShouldProcessForStateChangingFunctions' # 视项目放宽
)
Rules = @{
PSAvoidUsingCmdletAliases = @{ Whitelist = @('cd', 'ls') }
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NewLineAfter = $true
IgnoreOneLineBlock = $true
NoEmptyLineBefore = $false
}
PSUseConsistentIndentation = @{
Enable = $true
IndentationSize = 4
Kind = 'space'
}
}
}
Invoke-ScriptAnalyzer -Path . -Recurse `
-Settings ./PSScriptAnalyzerSettings.psd1 `
-Severity Warning `
-EnableExit # CI:发现告警即非零退出
| 规则 | 含义 | |------|------| | PSAvoidUsingWriteHost | 数据走 Write-Output | | PSUseShouldProcessForStateChangingFunctions | 修改状态需 -WhatIf 支持 | | PSUseApprovedVerbs | 函数动词在 Get-Verb 列表 | | PSAvoidUsingPositionalParameters | 显式参数名 | | PSAvoidUsingPlainTextForPassword | 用 SecureString | | PSUseDeclaredVarsMoreThanAssignments | 死代码 | | PSAvoidGlobalVars | 不污染 $global: |
MyModule/
├── Public/Get-Foo.ps1
├── Private/Get-Internal.ps1
├── Tests/
│ ├── MyModule.Tests.ps1
│ ├── Get-Foo.Tests.ps1
│ └── Helpers/
│ └── TestHelpers.psm1
└── PSScriptAnalyzerSettings.psd1
约定:每个 Public 函数对应一个 *.Tests.ps1,或汇总到 MyModule.Tests.ps1。
- name: Lint
shell: pwsh
run: |
Install-PSResource PSScriptAnalyzer -TrustRepository
Invoke-ScriptAnalyzer -Path . -Recurse -Settings ./PSScriptAnalyzerSettings.psd1 -EnableExit
- name: Test
shell: pwsh
run: |
Install-PSResource Pester -TrustRepository
Invoke-Pester -CI
- uses: actions/upload-artifact@v4
with: { name: test-results, path: testResults.xml }
Describe/Context/It 语法保留,但内部块(BeforeAll 等)作用域更严。It 内只跑断言;fixtures 全挪到 BeforeAll/BeforeEach。Describe/Context 范围。New-PesterConfiguration,不再传 hashtable 给 Invoke-Pester。BeforeAll/BeforeEach 而非 It 内做 setup-ModuleNameInModuleScope 测$TestDrivePSScriptAnalyzerSettings.psd1 入版本控Invoke-Pester -CI + Invoke-ScriptAnalyzer -EnableExittools
UI/UX 与布局设计——做界面布局/结构/导航/组件/交互的设计决策。触发:做UI/UX/布局/排版/导航/组件/交互/栅格/响应式/图表选型/字体配对。按媒介路由 HTML/Web、原生 App(iOS/Android/桌面)、CLI、TUI。需后端动态系统不适用;配色/主题/色板走姊妹 skill design-color。
tools
主题与配色设计——做颜色搭配/调色板/主题/品牌色阶/暗模式的设计决策。触发:选配色/调色/主题/色板/品牌色/暗模式/对比度/色盲/UI风格。按媒介路由 HTML/Web(CSS变量)、原生App(平台token)、CLI(ANSI)、TUI(真彩/256/16降级)。保证可访问性(对比度/色盲安全)。需后端动态系统不适用;UI/UX 布局/组件/交互走姊妹 skill design-uiux。
tools
跨任意组件(plugin/skill/agent/command)的验证驱动优化循环纪律 skill。当用户要优化某个已有组件却无明确方向、或要防止改了反而更差(自评乐观偏差 / 多维同改归因失效 / 为凑分加废话膨胀)、或要把一套通用「评分→单变量改→改后验证严格更好才留否则回滚→触顶停」的纪律套到任意组件上时使用。管优化过程本身的纪律(validation gate / ratchet / 独立验证 / 触顶停),不评单组件深度(交 skill-dev),不查插件接线(交 plugin-dev)。仅手动 /optimize-any 触发。
data-ai
两层规则记忆 (基于 .skein/spec)。planning 时 recall 召回相关规则、task finish 后 sediment 沉淀学习 + prune 自动精简过期/重复/断链规则。core 常驻硬规 + recall 按需召回, 经判定门自动写盘 (不逐次问用户)。产出 .skein/spec 下 core/recall 规则文件 + index。另支持空仓 bootstrap 播种规则基线、记忆大面积失效 (大重构/换栈) 时 reconstruct 可逆归档后按项目类型分型重建、maintain 手动体检 (超预算/stale/断链/重复/废弃, --apply 自动修复)、auto-fix (Stop hook 写 .pending-fix 标记 → main 派 skein-specer bg 跑 maintain --apply 全自动修, 断链只报告)。