0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PowerShellでMarkdownパーサー自作してPester地獄から生還した話 ~VS Codeの設定も見直そう~

Posted at

はじめに: MarkdownでWBSを管理したい

こんにちは!普段PowerShellで業務効率化スクリプトを書いているのですが、もっと複雑なWBS(Work Breakdown Structure)をMarkdownで手軽に管理し、ゆくゆくはExcelとも連携できるツールを自作しようと思い立ちました。名付けて「MD-WBS-Tools」プロジェクトです。

Markdownのシンプルさとテキストベースの管理のしやすさは魅力ですが、これをPowerShellでパースし、さらにPesterで単体テストを書こうとしたところ、予想外の「地獄」が待っていました…。

この記事では、その過程で遭遇した数々のエラーと、その解決に至るまでの試行錯誤、そして意外なところに潜んでいた根本原因について共有したいと思います。PowerShellで自作ツール開発やPesterテストに挑戦している方の、何かしらの参考になれば幸いです。

やりたかったこと: simple-md-wbs 記法とツール概要

私が考えた simple-md-wbs は、Markdownの見出しとリストを使い、属性情報をコメントや専用行に記述するシンプルなものです。

# プロジェクトX

## D1_大分類A

%% ID01,2025-01-01,2025-01-10,5,先行,PREV01,2025-01-02,2025-01-09,50%,担当A,組織X,2025-01-08,コメント

- タスクA <!-- T01,2025-01-01,,,,,,,,,,コメント -->

これをPowerShellでパースし、CSVに出力したり、将来的にはCSVからこの形式に戻したりするツールを作るのが目標です。そして、そのコアとなるパース処理の関数をPesterでテストしようとしました。

Pester地獄の始まり: エラーまたエラー

意気揚々とテストを書き始めたものの、実行するたびに赤いエラーメッセージがコンソールを埋め尽くしました。

  1. CommandNotFoundException の嵐:

    • モジュールに関数を定義し、テストスクリプトで Import-Module しているはずなのに、The term '関数名' is not recognized... と怒られる日々。
    • 原因と対策:
      • モジュールマニフェスト (.psd1) の FunctionsToExport や、モジュールスクリプト (.psm1) の Export-ModuleMember で、テストしたい関数が正しくエクスポートされていませんでした。灯台下暗し!
      • 関数名をリファクタリングした際に、エクスポート設定を更新し忘れるという初歩的なミスも…。
  2. Pester構文の罠: -Be vs Be

    • RuntimeException: Legacy Should syntax (without dashes) is not supported in Pester 5.
    • ParameterBindingValidationException: コマンドレットの動的パラメーターを取得できません。
    • 原因と対策:
      • 以前Pester 3.x系を使っていた名残で、アサーションを Should -Be のようにハイフン付きで書いていました。Pester 5.xではハイフンなしの Should Be が正しい構文です。
      • 最初は一部だけ直して動いたように見えたテストも、他の箇所に古い構文が残っていると、Pesterがテスト構造を正しく解釈できず、ParameterBindingValidationException のような間接的なエラーを引き起こすことが判明。ファイル全体を検索して徹底的に置換することで解決しました。
  3. 状態を持つ関数のテストと BeforeEach の挙動:

    • WBSのIDを階層的に自動生成する関数 (Get-DecodedAndMappedAttribute) は、内部でカウンターを保持しています。テストごとにカウンターをリセットするため BeforeEach { Reset-WbsCounters } を使いました。
    • しかし、これだけでは不十分です。各テストケース(It ブロック)が独立して正しい結果を出す必要があります。そのテストが必要とする前提条件(上位階層のIDがすでに生成されているなど)を、各テストケース内で再現する必要がありました。
    • 教訓: ステートフルな関数のテストは、状態管理とテストの独立性をどう担保するかが鍵。BeforeEach での状態リセットと、テストケース内での前提条件セットアップの組み合わせが重要。
  4. 謎の ParameterBindingValidationException とエンコーディング:

    • Pester構文を修正しても、Get-DecodedAndMappedAttribute TestsBeforeEachReset-WbsCounters を呼ぶ箇所で ParameterBindingValidationException: 引数が null であるため、パラメーター 'Name' にバインドできません。 が出続けました。
    • 原因の切り分け: Reset-WbsCounters 関数自体に -Name パラメータはなく、呼び出し方も間違っていない。Pesterがテストブロックを解釈する内部処理で問題が起きている可能性を疑いました。
    • 対策 (効果あり): テストスクリプトファイル (MyCommonFunctions.Test.ps1) のエンコーディングを UTF-8 with BOM で保存し直したところ、このエラーが解消! 日本語環境におけるPester 5.xとBOMなしUTF-8の相性問題だった可能性があります。

灯台下暗し: VS CodeのPowerShell実行バージョン

上記のエラーと格闘する中で、もう1つ大きな見落としがありました。
それは、VS CodeがターミナルやPowerShell拡張機能でどのバージョンのPowerShellを使用しているかという設定です。

私の環境では、PowerShell 7.xを別途インストールしていましたが、VS CodeのデフォルトはWindows PowerShell(5.1) のままでした。
Pester 5.xはPowerShell Core(6以降)での利用が推奨されており、PowerShell 5.1との組み合わせでは不安定な挙動を示すことがあります。

settings.json の修正:
VS Codeの settings.json に以下を追記(または修正)し、デフォルトで使用するPowerShellのバージョンを明示的にPowerShell 7.xにしました。

{
    // ... 他の設定 ...
    "powershell.powerShellAdditionalExePaths": {
      "PowerShell 7.5.1": "C:\\Program Files\\PowerShell\\7\\pwsh.exe" //  ご自身のインストールパスに合わせて
    },
    "powershell.powerShellDefaultVersion": "PowerShell 7.5.1", //  追加または修正
    // ... 他の設定 ...
}

この設定変更により、Pesterの動作が格段に安定し、原因不明のエラーが減少したように感じます。これまでの多くの問題の根底に、この実行バージョンの不一致があった可能性も否定できません。

属性パース関数のデバッグと期待値の調整

ConvertTo-AttributeObject 関数のテストでは、多くの属性が省略されたり、コメントにカンマや特殊文字が含まれたりするケースで、期待値と実際の出力がなかなか一致しませんでした。

これは、関数のパースロジック(とくに省略されたフィールドの扱いや、コメントの範囲特定)と、テストケースで記述する期待値の間の微妙なズレが原因でした。
関数のデバッグ出力(Write-Host でパース途中の情報を表示)とテストの実行結果を何度も見比べ、関数の実際の挙動に合わせてテストケースの期待値を正確に記述し直すことで、最終的にすべてのテストをパスさせることができました。

教訓: テストは、書くだけでなく、対象関数のロジックを深く理解し、期待値を正確に設定することが重要。デバッグ出力は友達。

現在の成果と今後の展望

苦労の末、コアとなるモジュール関数 (Get-DecodedAndMappedAttribute, ConvertTo-AttributeObject, Reset-WbsCounters) の単体テストはすべてパスするようになりました!

# MyCommonFunctions.Test.ps1 の最終的な実行結果(イメージ)
Tests completed in xxxms
Tests Passed: 13, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0

現在は、このモジュールを利用するメインスクリプト (Convert-SimpleMdWbsToCsv.ps1) のリファクタリングと、依存関係解決機能の実装を進めているところです。
将来的には、CSVから simple-md-wbs への逆変換や、Excel VBAマクロとの連携による表示最適化も視野に入れています。

まとめ: PowerShellとPesterと格闘して得た教訓

  • Pesterのバージョンと構文は正確に合わせよう。(とくにV3/V4からV5への移行時)
  • モジュールの関数は正しくエクスポートしよう。(マニフェストファイル大事)
  • 状態を持つ関数のテストは、状態管理と独立性を意識しよう。BeforeEach とリセット関数)
  • ファイルのエンコーディングは「UTF-8 with BOM」が無難(とくに日本語環境)。
  • VS CodeのPowerShell実行バージョン設定を見直そう!(これが一番の盲点だったかも)
  • テストの期待値は、関数の実際の挙動と寸分違わず合わせよう。(デバッグ出力は神)

PowerShellでのツール開発は非常に強力ですが、テスト環境を整え、細部まで注意を払うことの重要性を改めて痛感しました。
この記事が、同じようにPowerShellやPesterで奮闘している誰かの一助となれば幸いです。


(付録)主要なスクリプトや設定ファイルのスニペット
(ここには、記事本文で触れた MyCommonFunctions.psm1 の関数定義の一部、MyCommonFunctions.Test.ps1 の代表的なテストケース、settings.json の設定例などを掲載すると良いでしょう)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?