Qiita記事ドラフト: 「なぜ僕のPesterテストは全滅したのか?- PowerShellモジュールテストでハマった3つの罠と完全な解決策」
【プロローグ】勝利を確信した、その矢先に…
こんにちは!以前、「PowerShellでMarkdownパーサー自作してPester地獄から生還した話」という記事を書きました。あの後、単体テストは全てパスし、私は勝利を確信していました。
しかし、それは新たなる地獄の始まりでした。
リファクタリングを進め、テストを拡充するたびに、コンソールは再び赤いエラーメッセージで埋め尽くされました。
Tests completed in xxxms
Tests Passed: 0, Failed: 15, Skipped: 0, Inconclusive: 0, NotRun: 0
CommandNotFoundException
, ParserError
, ParameterBindingValidationException
...。一度治したはずの病が次々と再発する。原因を一つ潰しても、また別のエラーが顔を出す。
この記事は、その「なぜか分からないがテストが全滅する」という泥沼から、どうやって根本原因を特定し、最終的にテストを安定させたかの全記録です。PowerShellでモジュールとPesterテストを扱うすべての人に、この経験が役立つことを願っています。
【第一の罠】モジュールはどこにいる? - Import-Module
の迷宮
テストが失敗する最も基本的な原因は、「テスト対象の関数が見つからない (CommandNotFoundException
)」ことでした。Import-Module
を実行しているにも関わらず、です。
失敗したアプローチ: $env:PSModulePath
の動的な変更
当初、私はテストスクリプト (MyCommonFunctions.Tests.ps1
) の BeforeAll
ブロックで、モジュールがあるディレクトリを $env:PSModulePath
に一時的に追加し、モジュール名 (-Name 'MyCommonFunctions'
) でインポートしようとしました。
# 失敗したアプローチ
BeforeAll {
$moduleDirectory = "..."
$env:PSModulePath = "$($moduleDirectory);$($env:PSModulePath)"
Import-Module -Name 'MyCommonFunctions' -Force
}
これは、ほとんどの環境で失敗しました。PowerShellのモジュール解決の仕組みは複雑で、$env:PSModulePath
を動的に操作する方法は、環境依存性が高く非常に不安定です。
解決策: 絶対的なパスで、一度だけインポートする
根本的な対策は、全ての曖昧さを排除することでした。
-
スクリプトの先頭で一度だけインポート:
BeforeAll
やBeforeEach
のようなブロックではなく、スクリプトの最上部で実行します。 -
フルパスを直接指定:
-Name
パラメータではなく、.psd1
マニフェストファイルへの解決済みのフルパスを直接渡します。 -
-Force
オプション: 実行ごとにモジュールが再ロードされることを保証します。 -
-ErrorAction Stop
とtry-catch
: インポート自体が失敗した場合、テストを続行せずに即座に停止させ、問題に気づけるようにします。
# MyCommonFunctions.Tests.ps1 (完成版の冒頭)
try {
# $PSScriptRoot (テストスクリプト自身の場所) を基準に相対パスを解決
$modulePath = Join-Path $PSScriptRoot "../../src/powershell/Modules/MyCommonFunctions/MyCommonFunctions.psd1"
Import-Module -Name $modulePath -Force -ErrorAction Stop
}
catch {
Write-Error "テストの前提条件であるモジュールのインポートに失敗しました。パス: '$modulePath', Error: $($_.Exception.Message)"
return # スクリプトを終了
}
# この後に Describe ブロックが続く...
教訓①: テストの依存関係は、曖昧な探索に頼らず、常に明示的かつ直接的に解決せよ。
【第二の罠】Pester構文の時限爆弾 - Ver5 との静かなる戦い
モジュールがロードされるようになっても、RuntimeException: Legacy Should syntax...
や ParameterBindingValidationException
が頻発しました。
原因と解決策
-
Pester 5.x 構文の徹底: 原因は、テストスクリプト内に古いPester 3.x/4.x の構文 (
Should -Be
) と、新しいPester 5.x の構文 (Should Be
) が混在していたことでした。中途半端な修正が、Pesterのパーサーを混乱させ、予期せぬParameterBindingValidationException
を誘発していました。
解決策: VS Codeの「ファイル内をすべて置換」機能を使い、Should -Be
をShould Be
に、Should -BeNullOrEmpty
をShould BeNullOrEmpty
に、機械的に一括変換しました。 これで構文エラーは完全に撲滅できました。 -
ファイルのエンコーディング: それでも残る不可解なエラーは、テストスクリプトファイルのエンコーディングが原因でした。ファイルを UTF-8 with BOM で保存し直すことで、Pester 5.x の動作が安定しました。特に日本語を含む環境では必須のようです。
-
VS CodeのPowerShellバージョン: 最終的に、VS Codeが使用するPowerShellを、システム標準の
Windows PowerShell (5.1)
から、別途インストールしたPowerShell 7.x
にsettings.json
で明示的に切り替えたことも、全体の安定に大きく寄与しました。
教訓②: テストフレームワークのバージョンアップは、破壊的変更を伴う。移行ガイドを熟読し、構文を完全に統一せよ。そして、自分のコードだけでなく、エディタや実行環境の設定も疑え。
【第三の罠】ロジックは正しい、でもテストは失敗する - 仕様書 vs テストケース
全ての実行時エラーが消え、ついにテストが中身の評価を始めると、今度は Expected ... But was ...
というアサーション失敗の嵐に見舞われました。
原因と解決策
これは、最も時間を費やしたデバッグでした。
-
デバッグ出力の徹底:
ConvertTo-AttributeObject
関数内にWrite-Verbose
を大量に仕込み、「何を入力し、どう解釈し、何を返したか」を徹底的に可視化しました。 -
真実の発見: ログを分析すると、関数のロジックは仕様通りに動作していました。 問題は、テストケース側で用意した入力文字列(カンマの数)や、期待値(
Should Be "..."
の中身)が、仕様とズレていたことでした。- 例えば、仕様では13個の属性フィールドを定義しているのに、テストケースの入力文字列には12個しかフィールドがなく、最後の値が「コメント」ではなく「最終更新日」として解釈されていた、などです。
-
解決策: テストケースの一つ一つを、仕様書と関数のデバッグ出力を元に、期待値を正確に修正していきました。
ConvertTo-AttributeObject
のロジック自体はほとんど変更する必要はありませんでした。
教訓③: テストが失敗した時、我々はつい「コードのバグ」を疑う。しかし、「テストの期待値の間違い」や「テストデータの仕様不整合」も同等に疑うべきである。
【結論】安定したテスト環境を手に入れて
この長く苦しい戦いの末、ついに15件のテストが全てパスする、安定したテスト環境を構築できました。
Tests completed in 419ms
Tests Passed: 15, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
この経験から得られた再発防止策は、今後のはい、承知いたしました。
Geminiが出力した「事実確認と再発防止策」の内容と、それ以前の経緯を踏まえ、私が最終的に提案するQiita記事のドラフトをリライトします。
リライトのポイント:
- 読者層の再設定: 前回のドラフトは少し開発者向けすぎたかもしれません。今回は、**「PowerShellを業務で使っていて、もう少し高度なことに挑戦したいが、テストでつまずいている人」**をメインターゲットに設定し、より共感しやすく、実践的な内容を目指します。
- ストーリー性の強化: 「Pester地獄」というキャッチーな言葉を開発における私の憲法となるでしょう。
- 仕様書を絶対的な正義とする。
- テストケースは仕様書から作る。
- ツールの入力には必ずバリデーションを入れる。
- 環境設定(PowerShellバージョン、エンコーディング)を最初に固める。
- 複雑な処理には、ためらわずに詳細なデバッグ出力を仕込む。
PowerShellとPesterは、正しく使えば非常に強力な開発・テストツールです。この記事が、過去の私のように「なぜか動かない」テストに悩む誰かの助けになることを願っています。
(付録として、最終版の MyCommonFunctions.Test.ps1
の主要部分や、settings.json
の設定例などを掲載)