用 Pester 完善 PowerShell 测试 ── 让运维脚本不易损坏的实务方法
· 小村 豪 · PowerShell, Pester, Windows, 测试, 自动化, CI, 存量资产利用
1. 首先要弄清楚的事
PowerShell 脚本,往往一开始只是一些小型工作的自动化。
收集文件。 搜索日志。 生成 CSV。 搬移旧文件。 确认服务状态。
这些工作只要几十行,用眼睛看就能确认动作是否正确。但在实务中持续使用之后,就会出现这些变化。
- 增加要处理的文件夹
- 加入排除条件
- 修改 CSV 的列
- 删除前先归档
- 从任务计划程序或 CI 中执行
- 出错时要发出通知
到了这个阶段,「曾经在自己手边跑起来过」已经不够了。
PowerShell 的可怕之处,恰恰和它的便利性是一体两面。 如果只是读取数据,可以放心尝试,但删除、移动、覆盖、重启服务、变更权限这类操作,只要条件判断出一点差错,就会直接变成事故。
这时就该用上 PowerShell 的测试框架 Pester。本文不打算覆盖 Pester 的所有功能,而是整理如何为现有的 PowerShell 脚本,在实务中做「测试完善」,让它们变得不容易损坏。
PowerShell 的测试,不只是为了写出干净的代码。它是在修改之前减少不安、在修改之后能有依据地进行确认的工具。
顺带一提,本文出现的代码,已经作为一整套可用 Invoke-Pester 执行的示例(测试对象脚本、Pester 测试、CI 用执行脚本)发布在 GitHub 上。
pester-powershell-test-maintenance - komurasoft-blog-samples (GitHub)
2. Pester 要守护什么
引入 Pester,并不代表一切都会自动变得安全。首先该决定的是「用测试守护什么」。
在 PowerShell 的运维脚本中,优先确认这 4 项,通常最容易见效。
| 想守护的事 | 测试要看的内容 |
|---|---|
| 条件判断 | 以哪些文件、行、用户、服务为对象 |
| 输出格式 | 输出到 CSV 的列名、返回值的属性、条数 |
| 危险操作之前 | 删除、移动、停止的对象是否符合预期 |
| 外部依赖 | 文件系统、API、命令执行、日期时间、环境变量的处理 |
尤其最先该测试的,不是删除处理本身,而是挑选删除对象的处理。
举例来说,如果是删除旧日志的脚本,不要一开始就直接测试 Remove-Item,而是先测试「哪些日志会被选为对象」。
用这种拆分方式,会更容易测试。
收集对象的函数
↓
确认、记录对象的处理
↓
移动、删除、通知等变更处理
PowerShell 的测试完善,并不是要立刻对现有脚本做大改造。首先,把危险处理之前的判断部分拆成函数,用 Pester 确认它的返回值。
3. 先统一版本
本文以 Pester v5 为前提。
在比较旧的 Windows 环境中,即使原本就预装了 Pester,也可能是 v3 系列。不要直接沿用现有环境里安装的版本,先确认版本号。
Get-Module Pester -ListAvailable |
Sort-Object Version -Descending |
Select-Object Name, Version, Path
如果要重新安装,可以从 PowerShell Gallery 安装。
Install-Module -Name Pester -Scope CurrentUser -Force -SkipPublisherCheck
Import-Module Pester
Get-Module Pester
团队使用时,要确认开发机、构建服务器、任务执行环境中的 Pester 版本是否一致。
PowerShell 测试中常见的混乱,往往不是代码本身的问题,而是来自测试运行器版本的差异。
尤其是旧文章或内部笔记中,可能还残留着 Pester v4 及更早的写法。如果要重新完善,建议向 v5 的写法靠拢,后续会更容易维护。
4. 确定文件的存放位置
在 Pester 中,一般会把测试文件命名为 *.Tests.ps1。
最小的配置如下:
scripts/
Get-OldLogFile.ps1
Get-OldLogFile.Tests.ps1
规模稍大一点时,可以把 src 和 tests 分开。
src/
public/
Get-OldLogFile.ps1
Remove-OldLogFile.ps1
tests/
public/
Get-OldLogFile.Tests.ps1
Remove-OldLogFile.Tests.ps1
两种方式都可以,重要的是先定好规则。
- 每个函数对应一个测试文件
- 测试文件名以
.Tests.ps1结尾 - 统一测试对象的加载方式
- 不要把单元测试和集成测试混在一起
一开始,把测试对象的 .ps1 和测试文件 .Tests.ps1 放在同一目录下就足够了。
5. 跑通最小的测试
先准备一个简单的函数 Get-OldLogFile.ps1。
function Get-OldLogFile {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string] $Path,
[int] $Days = 30,
[string] $Filter = '*.log',
[datetime] $Now = (Get-Date)
)
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
throw "Folder not found: $Path"
}
$limit = $Now.AddDays(-1 * $Days)
Get-ChildItem -LiteralPath $Path -Filter $Filter -File |
Where-Object { $_.LastWriteTime -lt $limit } |
Sort-Object -Property LastWriteTime |
Select-Object FullName, Name, Length, LastWriteTime
}
这里为了方便测试,让 $Now 可以通过参数传入。
如果在函数内部每次都直接调用 Get-Date,结果就会随测试执行日而变化。只要把日期做成参数,就能把「以 2026 年 6 月 1 日为基准、超过 30 天的文件」这种条件固定下来测试。
接下来,把测试写在 Get-OldLogFile.Tests.ps1 中。
BeforeAll {
. $PSScriptRoot\Get-OldLogFile.ps1
}
Describe 'Get-OldLogFile' {
BeforeEach {
$script:Root = Join-Path $TestDrive 'logs'
New-Item -ItemType Directory -Path $script:Root -Force | Out-Null
$oldLog = Join-Path $script:Root 'old.log'
$newLog = Join-Path $script:Root 'new.log'
$oldTxt = Join-Path $script:Root 'old.txt'
Set-Content -LiteralPath $oldLog -Value 'old log' -Encoding UTF8
Set-Content -LiteralPath $newLog -Value 'new log' -Encoding UTF8
Set-Content -LiteralPath $oldTxt -Value 'old text' -Encoding UTF8
(Get-Item -LiteralPath $oldLog).LastWriteTime = [datetime]'2026-05-01T00:00:00'
(Get-Item -LiteralPath $newLog).LastWriteTime = [datetime]'2026-05-31T00:00:00'
(Get-Item -LiteralPath $oldTxt).LastWriteTime = [datetime]'2026-05-01T00:00:00'
}
It '只返回超过指定天数的 .log 文件' {
$result = Get-OldLogFile `
-Path $script:Root `
-Days 30 `
-Now ([datetime]'2026-06-01T00:00:00')
$result | Should -HaveCount 1
$result[0].Name | Should -Be 'old.log'
}
It '对不存在的文件夹会失败' {
{ Get-OldLogFile -Path (Join-Path $TestDrive 'missing') } |
Should -Throw
}
}
执行测试。
Invoke-Pester -Output Detailed .\Get-OldLogFile.Tests.ps1
这里用到的 $TestDrive,是 Pester 提供的测试用临时区域。不需要动用真正的 C:\Logs 或共享文件夹,就能使用仅在测试中创建的文件。对于包含文件操作的 PowerShell 脚本,先养成使用 $TestDrive 的习惯会更安全。
6. 把测试名称写成规格
Pester 的 It 里写的字符串,不只是单纯的说明,对日后阅读的人来说,其实是一份小小的规格书。
举例来说,这样的命名就有点弱。
It 'works' {
# ...
}
看不出来到底该正常运行的是什么。
在实务中,把条件和期望结果写进名称,会更容易阅读。
It '只返回超过指定天数的 .log 文件' {
# ...
}
It '刚好到期日的文件不视为对象' {
# ...
}
It '对不存在的文件夹会失败' {
# ...
}
好的测试名称,在失败时特别管用。CI 的日志一旦出现这样的信息,就能马上知道坏掉的是哪里。
[-] Get-OldLogFile.刚好到期日的文件不视为对象
测试名称,其实是写给未来的自己的备忘录。
7. 补一个边界条件
前面的 Get-OldLogFile,是用这个条件判断旧文件的。
$_.LastWriteTime -lt $limit
由于用的是 -lt,和到期日刚好相同时间的文件不会被视为对象。
这个判断看起来很小,但在实务中很重要。因为「超过 30 天」和「包含第 30 天」,结果的条数会不一样。
把边界条件加入测试。
It '刚好到期日的文件不视为对象' {
$border = Join-Path $script:Root 'border.log'
Set-Content -LiteralPath $border -Value 'border log' -Encoding UTF8
(Get-Item -LiteralPath $border).LastWriteTime = [datetime]'2026-05-02T00:00:00'
$result = Get-OldLogFile `
-Path $script:Root `
-Days 30 `
-Now ([datetime]'2026-06-01T00:00:00')
$result.Name | Should -Not -Contain 'border.log'
}
测试不是写得越多越好。不过,日期、数字、条数、权限、文件名模式这类带有边界的处理,测试的价值特别高。
8. 固定返回值的形状
PowerShell 脚本的返回值形状,有时会在不知不觉间发生变化。
一开始是直接返回 FileInfo。
中途加入了 Select-Object。
后来为了 CSV 又改了列名。
这类变更会影响后续处理,所以先测试返回值的属性,就能察觉到未预期的变化。
It '返回后续处理需要的属性' {
$result = Get-OldLogFile `
-Path $script:Root `
-Days 30 `
-Now ([datetime]'2026-06-01T00:00:00')
$propertyNames = $result[0].PSObject.Properties.Name
$propertyNames | Should -Contain 'FullName'
$propertyNames | Should -Contain 'Name'
$propertyNames | Should -Contain 'Length'
$propertyNames | Should -Contain 'LastWriteTime'
}
对于传给 CSV 输出或报表生成的函数,不仅值本身,列名也是规格的一部分。
要确认的不只是「跑起来了」,而是「以后续处理所期望的形状返回」。
9. 把删除处理和对象选定分开
接下来看删除处理。先看一个不太好的例子。
Get-ChildItem C:\Logs -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
Remove-Item -Force
写法简短又方便,但不容易测试。因为对象选定和删除串在同一条管道里,很难分清楚该确认哪一部分。
在实务中,会这样拆开:
function Remove-OldLogFile {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string] $Path,
[int] $Days = 30,
[datetime] $Now = (Get-Date)
)
$targets = Get-OldLogFile -Path $Path -Days $Days -Now $Now
foreach ($target in $targets) {
if ($PSCmdlet.ShouldProcess($target.FullName, 'Remove old log file')) {
Remove-Item -LiteralPath $target.FullName -Force
}
}
}
这里加上了 SupportsShouldProcess,让函数端可以接受 -WhatIf。
Remove-OldLogFile -Path C:\Logs -Days 30 -WhatIf
对于删除类的 PowerShell 函数,尽可能设计成能用 -WhatIf 先行演练,会更安全。
10. 用 Mock 替代危险操作
在 Pester 中,可以用 Mock 替代实际的命令执行。
测试删除处理时,不需要真的执行 Remove-Item。
该调用的场景有没有被调用。 不该调用的场景有没有被误调用。
只要看这两点就够了。
以下是 Remove-OldLogFile.Tests.ps1 的示例。
BeforeAll {
. $PSScriptRoot\Get-OldLogFile.ps1
. $PSScriptRoot\Remove-OldLogFile.ps1
}
Describe 'Remove-OldLogFile' {
It '对旧日志文件调用 Remove-Item' {
Mock Get-OldLogFile {
[pscustomobject]@{
FullName = 'C:\Logs\old.log'
Name = 'old.log'
Length = 10
LastWriteTime = [datetime]'2026-05-01'
}
}
Mock Remove-Item {}
Remove-OldLogFile `
-Path 'C:\Logs' `
-Days 30 `
-Now ([datetime]'2026-06-01')
Should -Invoke Remove-Item `
-Times 1 `
-Exactly `
-ParameterFilter { $LiteralPath -eq 'C:\Logs\old.log' }
}
It '使用 WhatIf 时不调用 Remove-Item' {
Mock Get-OldLogFile {
[pscustomobject]@{
FullName = 'C:\Logs\old.log'
Name = 'old.log'
Length = 10
LastWriteTime = [datetime]'2026-05-01'
}
}
Mock Remove-Item {}
Remove-OldLogFile `
-Path 'C:\Logs' `
-Days 30 `
-Now ([datetime]'2026-06-01') `
-WhatIf
Should -Invoke Remove-Item -Times 0
}
}
在这个测试中,Get-OldLogFile 和 Remove-Item 都被 Mock 掉了,所以就算真正的 C:\Logs\old.log 不存在也没关系。要确认的是 Remove-OldLogFile 的判断逻辑。
- 有对象时就调用
Remove-Item - 使用
-WhatIf时不调用Remove-Item - 调用时传入的是预期的路径
危险程度越高的处理,测试调用条件,会比测试真正执行本身更安全。
11. 不要过度使用 Mock
Mock 很方便,但用得太多会让测试失去价值。因为如果全部都用 Mock,就会离真实的 PowerShell 行为太远。
大致的参考标准如下。
| 处理 | 建议做法 |
|---|---|
| 日期 | 用参数固定 |
| 创建文件 | 使用 $TestDrive |
| 删除、移动 | 用 Mock 和 -WhatIf 确认 |
| 调用 Web API | 对 Invoke-RestMethod 等做 Mock |
| 发送邮件、通知 | 对发送用命令做 Mock |
| CSV 读写 | 在 $TestDrive 中创建小型的真实文件 |
如果连文件读写都全部 Mock 掉,可能会漏掉真正的字符编码、换行、列名之类的问题。
另一方面,删除、通知、外部 API、停止服务这类处理,最好真的不要去执行。
要把「使用真实对象的地方」和「Mock 的地方」区分开来。
12. 把现有脚本改成便于测试的样子
引入 Pester 之后,现有脚本的写法也会稍微发生变化。不过一开始不需要做大规模的设计变更,先做到这个程度就足够了。
修改前
$limit = (Get-Date).AddDays(-30)
Get-ChildItem C:\Logs -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt $limit } |
Remove-Item -Force
修改后
function Get-OldLogFile {
param(
[string] $Path,
[int] $Days = 30,
[datetime] $Now = (Get-Date)
)
$limit = $Now.AddDays(-1 * $Days)
Get-ChildItem -LiteralPath $Path -Filter *.log -File |
Where-Object { $_.LastWriteTime -lt $limit }
}
function Remove-OldLogFile {
[CmdletBinding(SupportsShouldProcess)]
param(
[string] $Path,
[int] $Days = 30,
[datetime] $Now = (Get-Date)
)
Get-OldLogFile -Path $Path -Days $Days -Now $Now |
ForEach-Object {
if ($PSCmdlet.ShouldProcess($_.FullName, 'Remove old log file')) {
Remove-Item -LiteralPath $_.FullName -Force
}
}
}
改动的地方并不大。
- 把日期改成参数
- 把对象选定变成函数
- 把删除处理拆成另一个函数
- 加上
SupportsShouldProcess
仅凭这些,就会变得容易测试很多。
在 PowerShell 的测试完善中,与其从设计理论谈起,把「日期」「路径」「外部命令」「变更操作」变成可以从外部替换,会更有效果。
13. 确定测试的分类
在 Pester 中,可以给 Describe、Context、It 打标签。
例如,把快速的单元测试和真正接触环境的集成测试分开。
Describe 'Get-OldLogFile' -Tag 'Unit' {
It '只返回超过指定天数的 .log 文件' {
# 使用 TestDrive 的快速测试
}
}
Describe 'Log maintenance smoke test' -Tag 'Smoke' {
It '可以读取真正的日志文件夹' {
Test-Path -LiteralPath 'C:\Logs' | Should -BeTrue
}
}
只执行单元测试。
Invoke-Pester -TagFilter Unit
排除较慢或依赖环境的测试。
Invoke-Pester -ExcludeTagFilter Slow, RequiresAdmin, Network
在实际工作中,如果每次都想跑完所有测试,有时会难以持续下去。
先把快速又没有副作用的测试设为标准,再用标签把依赖环境的测试区分出来,在需要的时机再执行。
14. 在 CI 上运行
Pester 就算只在本地运行也很有帮助,但如果要以团队方式管理脚本,能在 CI 上运行会更让人安心。
例如,可以准备一个类似 tools/Invoke-ProjectTests.ps1 的文件。
$ErrorActionPreference = 'Stop'
Import-Module Pester
$config = New-PesterConfiguration
$config.Run.Path = @(
Join-Path $PSScriptRoot '..\tests'
)
$config.Run.Exit = $true
$config.Output.Verbosity = 'Detailed'
$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'JUnitXml'
$config.TestResult.OutputPath = Join-Path $PSScriptRoot '..\test-results.xml'
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = @(
Join-Path $PSScriptRoot '..\src'
)
$config.CodeCoverage.OutputPath = Join-Path $PSScriptRoot '..\coverage.xml'
Invoke-Pester -Configuration $config
在 CI 端,执行这个脚本。
pwsh -NoProfile -File .\tools\Invoke-ProjectTests.ps1
重点是,不要把 CI 专属的配置写得太深入到测试文件里。
测试文件是用来写规格的地方。 CI 用的输出格式、覆盖率、退出代码等,整理到执行用脚本里会更清晰。
15. 把覆盖率当作地图,而不是目标
Pester 也能输出代码覆盖率。不过,最好不要一开始就过度追逐数字。因为覆盖率本身并不等同于测试的质量。
举例来说,只要调用一次删除对象的提取函数,那一行就算通过了。但如果边界条件或排除条件没有被确认过,并不会带来实务上的安心感。
覆盖率可以这样使用。
- 找出完全没有被执行过的函数
- 找出重要却没有测试的分支
- 按变更频率高的脚本排出优先级
- 在 CI 中留下测试执行的证据
重点不在于把数字拉高,而是要看「重要的判断有没有被测试到」。
16. 导入现有脚本的顺序
要为现有的 PowerShell 资产导入 Pester 时,不要一开始就把整体都纳入测试范围,这样会更容易推进。下面列出建议的顺序。
1. 挑选失败会造成麻烦的脚本
一开始,适合挑选这类脚本。
- 包含删除、移动、覆盖
- 每天或每月都会运行
- 写在操作手册上,但实际依赖特定人员
- 过去曾发生过条件判断失误
- 输出的 CSV 会被其他业务使用
从方便但坏掉会很麻烦的脚本开始。
2. 先把读取部分拆出来做成函数
一开始要测试的,不是变更处理,而是读取处理。
读取日志
筛选对象
统计条数
整理成 CSV 用的格式
这部分容易用 $TestDrive 测试,也不容易出事故。
3. 从外部传入日期和路径
如果日期或路径被写死,就很难测试。
# 想避免的写法
$root = 'C:\Logs'
$limit = (Get-Date).AddDays(-30)
便于测试的写法。
param(
[string] $Path,
[datetime] $Now = (Get-Date)
)
只要能从外部传入值,测试的稳定性就会大幅提升。
4. 把危险操作放到最后
删除、移动这类操作,统一放到最后处理。
创建对象
↓
把对象记录下来
↓
用 -WhatIf 先行确认
↓
真正执行
测试时也按相同的顺序确认。
17. 常见的卡壳情况
| 症状 | 原因 | 应对方法 |
|---|---|---|
| 本地能通过但 CI 失败 | 当前工作目录不同 | 以 $PSScriptRoot 为基准 |
| 结果每天都不一样 | 直接使用了 Get-Date |
准备类似 -Now 的参数 |
| 测试差点删掉真实文件 | 使用了真实文件夹 | 使用 $TestDrive 和 Mock |
| Mock 没有生效 | 模块边界或作用域不同 | 检查 -ModuleName 或加载方式 |
| 不知道该测到什么程度 | 规格没有拆分到函数里 | 拆成对象选定、整形、变更处理 |
| 测试很慢 | 接触了外部服务或网络 | 单元测试中对外部依赖做 Mock |
| 看测试名称看不出内容 | 名称写成 It 'works' 这种形式 |
把条件和期望结果写进名称 |
表面上看起来是 Pester 的问题,实际上很多时候是脚本结构本身造成的。
不容易测试的部分,通常在运维上也是容易出问题的部分。
18. 测试完善中要先定好的规则
如果要以团队方式管理 PowerShell 脚本,比起细节写法,更应该优先定好规则。
例如可以订立这类规则。
- 测试文件统一命名为
*.Tests.ps1 - 测试对象一律从
$PSScriptRoot加载 - 文件操作的测试使用
$TestDrive - 删除、移动、通知、API 调用原则上都做
Mock - 把日期做成参数,让它可以被固定
- 在
Describe或It上加Unit、Smoke、RequiresAdmin等标签 - CI 中以
Unit为标准执行 - 对变更类函数尽可能加上
SupportsShouldProcess - 把过去的问题留下来作为回归测试
规则太多反而守不住。一开始,光靠这 3 条就足够了。
使用 TestDrive
固定日期
危险操作做 Mock
只要守住这 3 条,PowerShell 的测试就会相当稳定。
19. 也要确定哪些不测试
在测试完善中,「测试什么」和「不测试什么」同样重要。
举例来说,下面这些内容不需要在单元测试中过度确认。
- Windows 本身的
Get-ChildItem是否正常运行 Remove-Item是否真的会删除文件- PowerShell 标准命令的内部规格
- 外部 API 是否一直有响应
- 网络共享是否一直可用
该测试的,是自己团队的判断逻辑。
- 用什么条件确定对象
- 传入哪个路径
- 输出哪些列
- 失败时该如何处理
- 危险操作能不能先行演练
把「信任标准命令的部分」和「守护自己逻辑的部分」区分开来。
20. 总结
PowerShell 是能快速自动化日常工作的便利工具。但在实务中长期使用的脚本,责任会一点点变重。一开始只是自己使用的单行命令,逐渐变成每天运行的运维处理,最终开始影响其他人的工作和业务数据。
用 Pester 做测试完善,就是配合这种变化来守护脚本的工作。
把要点列出来。
- 先测试对象选定
- 让日期和路径可以从外部传入
- 把文件操作封闭在
$TestDrive里 - 对删除、移动、通知、API 调用做
Mock - 让变更类函数可以用
-WhatIf先行演练 - 让测试名称可以当作规格来读
- 在 CI 中先跑快速又没有副作用的测试
PowerShell 的安全运维,不是要一次性引入庞大的机制。
拆成小函数。 写小测试。 在危险处理之前,确保能够先确认状态。
不断积累这些做法,PowerShell 就能从「方便但有点可怕的脚本」,逐渐变成「改动后也能确认的业务工具」。
参考链接
- 本文的完整示例代码(测试对象脚本、Pester 测试、CI 用执行脚本) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/pester-powershell-test-maintenance
- Pester Quick Start
- Pester Installation and Update
- Pester File placement and naming
- Pester TestDrive
- Pester Mocking
- Pester Tags
- Pester Configuration
- Pester Test Results
- Pester Code Coverage
- PowerShell Gallery: Pester
- PowerShell Documentation - Microsoft Learn
相关文章
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
PowerShell 脚本进阶 ── 安全地实现日志排查、归档与报表自动化
本文整理了用 PowerShell 脚本安全推进日志排查、CSV 报表、旧日志归档、留存记录,直至任务计划程序执行的实务步骤。
PowerShell 命令基础 ── 应先掌握的操作与安全使用方法
为了让 PowerShell 初学者在实际工作中不会迷失方向,本文整理了如何查找 Cmdlet、管道、文件操作、CSV 处理、执行策略,以及避免事故的确认步骤。
在 C#(CSharp)中执行 PowerShell 并以对象形式接收结果
本文从实务角度整理如何从 C# 启动 PowerShell,并以 PSObject 而非字符串来接收结果,涵盖 PowerShell SDK、AddCommand、AddParameter、BaseObject、Properties 直至错误处理。
PowerShell 实用命令合集 —— 积累日常工作中常用的小工具
本文整理 PowerShell 日常工作中常用的实用命令,说明 Measure-Object、Group-Object、Select-String、Compare-Object、Tee-Object、Start-Transcript 等命令的使用场景和时机。
业务应用的日期时间与时区 ── 从 DateTime 的陷阱到 UTC 存储原则、测试设计
服务器迁移后时间整整差了 9 个小时、只有海外分支机构的日期会变成前一天——本文从 DateTime 的 Kind 与隐式转换梳理日期时间事故的根本原因。内容涵盖与 DateTimeOffset 的取舍、UTC 存储与 ISO 8601 原则、TimeZoneInfo 与夏...
常见问题
汇总了咨询这一主题时常见的问题。
- 使用 Pester 时,最先该测试什么?
- 不是删除处理本身,而是从挑选删除对象的处理(对象选定)开始测试。运维脚本中,优先确认「条件判断」「输出格式」「危险操作之前」「外部依赖」这 4 项,通常最容易见效。不需要一开始就对现有脚本做大幅改造,只要把危险处理之前的判断部分拆成函数,先用 Pester 确认它的返回值即可。
- Pester 的 TestDrive 是什么?
- `$TestDrive` 是 Pester 提供的测试用临时区域。不需要动用真正的 `C:\Logs` 或共享文件夹,只用测试中创建的文件就能验证文件操作。在包含文件操作的 PowerShell 脚本测试中,先养成使用 `$TestDrive` 的习惯,就能避免误删真实文件这类事故。
- Pester 的 Mock 该用到什么程度?
- 删除、移动、调用 Web API、发送邮件、发送通知这类不该真正执行的处理,适合用 Mock 替代;日期则用参数固定;文件创建和 CSV 读写,比较适合在 `$TestDrive` 中创建小型的真实文件来处理。如果全部都用 Mock,会离真实的 PowerShell 行为太远,可能会漏掉字符编码、换行、列名之类的问题,因此重点在于把「使用真实对象的地方」和「Mock 的地方」区分开来。
- 为什么在本地能通过的 Pester 测试,在 CI 上却会失败?
- 常见原因是当前工作目录不同,把测试对象的加载基准改成 `$PSScriptRoot` 就能解决。如果结果每天都不一样,通常是因为直接使用了 `Get-Date`,可以用类似 `-Now` 的参数把日期固定下来。如果 Mock 不生效,要怀疑是模块边界或作用域不同,检查 `-ModuleName` 或加载方式。表面看起来是 Pester 的问题,实际上很多时候是脚本结构的问题。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。