用 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

規模稍大一點時,可以把 srctests 分開。

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-OldLogFileRemove-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 中,可以為 DescribeContextIt 加上標籤。

例如,把快速的單元測試和真正接觸環境的整合測試分開。

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 的參數
測試差點刪掉真實檔案 使用了真實資料夾 使用 $TestDriveMock
Mock 沒有生效 模組邊界或作用範圍不同 檢查 -ModuleName 或載入方式
不知道該測到什麼程度 規格沒有拆分到函式裡 拆成對象選定、整形、變更處理
測試很慢 碰到了外部服務或網路 單元測試中對外部依賴做 Mock
看測試名稱看不出內容 名稱寫成 It 'works' 這種形式 把條件和期望結果寫進名稱

表面上看起來是 Pester 的問題,實際上很多時候是腳本結構本身造成的。

不容易測試的部分,通常在維運上也是容易壞掉的部分。

18. 測試整備中要先定好的規則

若要以團隊方式管理 PowerShell 腳本,比起細部寫法,更該優先決定規則。

例如可以訂出這類規則。

  • 測試檔案統一命名為 *.Tests.ps1
  • 測試對象一律從 $PSScriptRoot 載入
  • 檔案操作的測試使用 $TestDrive
  • 刪除、移動、通知、API 呼叫原則上都做 Mock
  • 日期做成參數,讓它可以被固定
  • DescribeIt 加上 UnitSmokeRequiresAdmin 等標籤
  • 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 時,最先該測試什麼?
不是刪除處理本身,而是從挑選刪除對象的處理(對象選定)開始測試。維運腳本中,優先確認「條件判斷」「輸出格式」「危險操作之前」「外部依賴」這 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 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽