implement-unit-testing-script について
生成可能なビルドを一時フォルダーにステージングし、依存関係を分離した環境に導入してユニットテストを実行するスクリプトを作成するスキルです。Bash と PowerShell の共通手順を定めています。
主な機能
- 01ビルドを一時作業フォルダーにステージング
- 02分離環境への依存関係の導入
- 03言語ごとのユニットテストコマンドを実行
- 04ログと終了コードの規約
- 05Bash と PowerShell に対応
活用シーン
- 01プロジェクトビルド向けのユニットテストスクリプト生成
- 02ソースビルドフォルダーを変更せずにテスト実行
- 03テストや依存関係導入の失敗を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ plainlang/ plain-forge --skill implement-unit-testing-scriptインストール後、Claude Code で/implement-unit-testing-scriptを入力して読み込まれたか確認します。
詳細
ユニットテストスクリプトの実装
このスキルでは、一貫した言語に依存しないパターンに従って、生成されたビルドフォルダーのユニットテストを実行する単一の実行可能スクリプトを作成します。
リファレンス実装は assets/run_unittests_java.sh です。まずこれを読んでください。作成するスクリプトはすべて、このパターンを対象言語のツールとユーザーのシェル環境に忠実に置き換えたものである必要があります。これらのスクリプトには、Windows PowerShell 版も assets/run_unittests_*.ps1 にあります。
最初にシェルを選ぶ
何かを書く前に、スクリプトの対象となるシェルの種類を決めてください。対象は言語ではなく、ユーザーの環境によって異なります。
- Bash(
.sh) — macOS、Linux、WSL、Linux 上の CI ランナー。ユーザーがネイティブ Windows を使用している場合を除き、これをデフォルトとします。 - PowerShell(
.ps1) — ネイティブ Windows/PowerShell のみの環境。
プロジェクトから判断できない場合(OS を示す明確な手がかりや既存のスクリプトがない場合)は、ユーザーに確認してください。
同じ 7 つの手順を、どちらのシェルでも適用します。変わるのは構文だけです。
パターン
すべてのテストスクリプトでは、次の手順をこの順番で実行する必要があります。
- ツールチェーンの確認。 必要な言語ランタイム/ビルドツール(および必要なバージョンがあればそのバージョン)がインストールされていることを確認します。インストールされていない場合はエラーを表示し、コード
69で終了します。 - 引数の検証。 位置引数を 1 つだけ要求します。引数はソースのビルドフォルダーです。指定されていない場合は使用方法を表示し、コード
1で終了します。レンダラーはこの引数を絶対パスとして渡します(以前のバージョンのレンダラーでは相対パスが渡されていました)。そのため、スクリプトはどちらでも動作する必要があります。これにより、手順 3 の作業フォルダー名には、引数そのものではなく、引数のベース名を使用します。 - 作業ディレクトリの設定。 システムの一時ディレクトリに作業フォルダーを定義します。Bash では
/tmp/<lang>_$(basename "$1")、PowerShell ではJoin-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)"とします(以下では<temp>/<lang>_<basename>と表記します)。フォルダーが存在する場合は内容を消去し、存在しない場合は作成します。スクリプトの終了時にこのフォルダーが削除されるよう、クリーンアップ処理を登録します。Bash ではtrap 'rm -rf "$WORKING_FOLDER"' EXIT、PowerShell ではfinallyブロック内でRemove-Item -Recurse -Forceを使用します。このフォルダーだけを、その後の書き込み先とします。 - ビルドのコピー。 ソースフォルダーの内容をすべて、再帰的に作業フォルダーへコピーします。この手順の後は、スクリプトの残りの処理において、ソースフォルダー(
$1)を読み取り専用として扱います。 - 作業ディレクトリへの移動。
<temp>/<lang>_<basename>にcd/Set-Locationで移動します。移動に失敗した場合は、コード2で終了します。以降のすべての手順は作業フォルダー内で実行し、ソースのビルドフォルダーには決して書き戻さないでください。 - 依存関係を
<temp>/<lang>_<basename>内の隔離環境にインストール。 作業フォルダーごとに依存関係の保存先を設定し(Python の仮想環境./.venv、ローカルの./node_modules、プロジェクト専用の Maven リポジトリ./.m2など)、すべての依存関係をそこへインストール/解決します。ソースのビルドフォルダー、ユーザーのグローバルキャッシュ(~/.m2、システム全体のpip、~/.cargo、~/.npmなど)、または<temp>/<lang>_<basename>の外部には、絶対にインストールしないでください。インストールコマンドが失敗した場合は、その終了コードをただちに返し、手順 7 には進みません。言語ごとの詳細は 依存関係の隔離 を参照してください。 - テストの実行。 言語標準のテストコマンド(例:
mvn test、pytest、npm test、go test ./...、cargo test)を、手順 6 と同じ隔離環境を指定して実行します。スクリプトの最終終了コードは、テストコマンドが返す値とします。
ビルドフォルダーは読み取り専用 — 厳守ルール
$1 として渡されるソースのビルドフォルダーは入力専用です。スクリプトでは、以下の操作を決して行ってはいけません。
- 依存関係をこのフォルダーにインストールする(
$1内でpip installを実行しない、$1内でnpm installを実行しない、$1に書き込むmvn installを実行しない、Cargo のビルド成果物を$1以下に作成しない)。 - 仮想環境、
node_modules、.m2、.gocache、.cargoディレクトリをこのフォルダー内に作成する。 - このフォルダー内からテストコマンドを実行する(手順 5 の
cdの後、すべてのテストコマンドは<temp>/<lang>_<basename>内から実行します)。 - ログ、キャッシュ、ビルド出力、一時ファイルをこのフォルダー内に作成する。
ビルドフォルダーはレンダラー(デフォルトでは plain_modules/<module>/code)や後続のツールと共有されています。このフォルダーに書き込むと、「何が生成されたか」というレンダラーの認識が損なわれ、後続のレンダリングに問題が生じます。すべての書き込み先は <temp>/<lang>_<basename> にしてください。システムの一時ディレクトリにステージングするのは、ソースのビルドフォルダーをクリーンで再現可能なレンダリング成果物として維持し、ユーザーのプロジェクト内にビルド時の不要なファイルを残さないためです。
cwd がソースフォルダーになるコマンド、またはターゲットパスが $1/ で始まるコマンドを実行しようとしている場合は、そこで止めてください。操作を <temp>/<lang>_<basename> 内に移すか、スクリプトで禁止されている操作を行おうとしています。
規約
両方のシェルフレーバーで共通です。
- 終了コード:
1— 使用方法が誤っている(引数が不足している)。2— ファイルシステムの問題(作業フォルダーに移動できなかった)。69— 必須のツールチェーン/ランタイムがインストールされていない。- その他のゼロ以外のコード — 基盤となるテストコマンドから引き継がれます。
- 作業フォルダーの命名規則:
<temp>/<lang>_<basename>— システムの一時ディレクトリ(Bash では/tmp、PowerShell では[System.IO.Path]::GetTempPath())に、言語の短い識別子(java、python、node、go、rust、...)と、ビルドフォルダー引数の ベース名($(basename "$1")/Split-Path $BuildFolder -Leaf)を組み合わせます。生の引数をパスに埋め込まないでください。引数が絶対パスの場合があり、そのまま連結するとパスが二重になり、壊れてしまいます。すべての依存関係のインストール、ビルド出力、キャッシュ、テスト実行はこのフォルダー内で行います。手順 4 の後は、スクリプトがソースのビルドフォルダーに触れてはなりません。 - 終了時のクリーンアップ: 作業フォルダーは一時的なものです。成功時も失敗時も、スクリプトの終了時に削除してください(Bash では
trap 'rm -rf "$WORKING_FOLDER"' EXIT、PowerShell ではPop-Locationの後、finallyブロック内でRemove-Item -Recurse -Forceを実行します)。一時ディレクトリにもユーザーのプロジェクトにも、ビルドフォルダーが残って蓄積してはなりません。 - ログ記録 — できる限り詳しく記録します。 レンダリングからテスト結果の成功/失敗が表示されるまで、オペレーターが目にするのはこのスクリプトだけです。失敗した場合、調査に使える唯一の証拠は stdout/stderr です。静かな補助ツールではなく、本番環境のランブックとしてスクリプトを扱ってください。具体的には、次のとおりです。
- 各手順を実行前に告知し、関係する各変数の正確な値も表示します。 解決済みのソースフォルダー、作業フォルダー、言語、検出したツールチェーンのバージョン、分離ルート、依存関係マニフェストのパス、これから実行するテストコマンドを表示してください。「依存関係をインストール中」だけでは役に立ちません。「
./requirements.txtから、/usr/local/bin/python3にある Python 3.11.6 を使って仮想環境./.venvに依存関係をインストール中」のように表示すれば、問題の切り分けにすぐ役立ちます。 - 重要なコマンドはすべて、実行前に表示します。 Bash ではスクリプト本体に
set -xを使うか、各呼び出しの直前にecho "+ <cmd>"を実行します。PowerShell では$VerbosePreference = 'Continue'を設定し、Write-Verbose/Write-Hostを使って、引数を含む各コマンドラインを表示します。 - 明確なセクション見出しを表示します。 7 つの手順それぞれに、ログを目で追ってすぐに目的の箇所を見つけられるよう、
===== [3/7] Working directory setup =====のような見出しを付けます。 - 相対名だけでなく、解決済みの絶対パスを表示します。 別のマシンでログを読むオペレーターにも、実際の配置場所が分かるようにしてください。
- 手順 1 でツールチェーン自身の
--versionの出力と、解決されたパスを表示します。 成功時でも必ず表示してください。テストがローカルでは通るのに CI で失敗するとき、これが最も役立つ調査データです。 - インストールコマンドの出力は、そのまま表示します。
/dev/nullにパイプしたり、作業フォルダー内のログファイルにリダイレクトしたりしないでください。オペレーターには、依存関係リゾルバーの各行をスクリプト自身の出力ストリームで確認できる必要があります。 - 失敗時は、終了前にこれから実行する予定だった内容を表示します。 正確なコマンド、作業ディレクトリ、関連する環境変数(
PYTHONPATH、NODE_PATH、GOMODCACHE、CARGO_HOME、MAVEN_OPTS、PATH)を表示してください。 - テストコマンド、終了コード、作業フォルダーを示す最終サマリー行を表示します。 必要な場合に、オペレーターが手動で何を再実行すればよいかを正確に把握できるようにしてください。 ここでは冗長さはノイズではありません。自身の出力で処理内容を記録する詳しいスクリプトがあれば、問題の切り分けにかかる時間は 30 分ではなく 30 秒になります。詳しさを犠牲にして簡潔にしないでください。
- 各手順を実行前に告知し、関係する各変数の正確な値も表示します。 解決済みのソースフォルダー、作業フォルダー、言語、検出したツールチェーンのバージョン、分離ルート、依存関係マニフェストのパス、これから実行するテストコマンドを表示してください。「依存関係をインストール中」だけでは役に立ちません。「
依存関係の分離
依存関係の環境は必ず $WORKING_FOLDER 内に配置し、テスト実行によってユーザーのグローバルキャッシュが汚染されたり、逆に汚染したりしないようにしてください。言語に最も適した分離方法を選んでください。
| 言語 | 分離方法 | インストールコマンド($WORKING_FOLDER 内で実行) | テストコマンド |
|---|---|---|---|
| Python | ./.venv に venv を作成 | python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt(または pyproject.toml / uv sync / poetry install) | ./.venv/bin/pytest(または ./.venv/bin/python -m pytest) |
| Node.js | ローカルの ./node_modules(デフォルト) | npm ci(推奨)または npm install | npm test |
| Java | プロジェクト専用の Maven リポジトリを ./.m2 に配置 | mvn -Dmaven.repo.local=./.m2 dependency:resolve(事前ウォームアップは任意) | mvn -Dmaven.repo.local=./.m2 test |
| Go | モジュールキャッシュを ./.gocache に配置 | GOMODCACHE="$PWD/.gocache" go mod download(事前ウォームアップは任意) | GOMODCACHE="$PWD/.gocache" go test ./... |
| Rust | Cargo ホームを ./.cargo に配置 | CARGO_HOME="$PWD/.cargo" cargo fetch(事前ウォームアップは任意) | CARGO_HOME="$PWD/.cargo" cargo test |
注:
- インストールコマンドとテストコマンド内のすべてのパスは、
<temp>/<lang>_<basename>を基準とします。 そのため、手順 5 でスクリプトが作業フォルダーにcdします。以降、./.venv、./node_modules、./.m2などはすべて<temp>/<lang>_<basename>内を参照し、ソースのビルドフォルダーを参照することはありません。 - インストールコマンドとテストコマンドの両方で、必ず分離用のフラグまたは環境変数を指定してください。 依存関係の配置先を両者で一致させる必要があります。一致しない場合、テストコマンドがグローバルキャッシュ または(さらに悪いことに)ソースのビルドフォルダーに暗黙的にフォールバックする可能性があります。
- 仮想環境が必須なのは、文字どおり「仮想環境内に」配置する必要がある Python のみです。 ほかの言語では、同じ分離を実現する言語固有の方法を使います。
- Java/Go/Rust の事前ウォームアップは任意です。 これらのテストコマンドは必要に応じて依存関係を取得します。ウォームアップを別の手順にすることで、失敗の原因を診断しやすくなり、「インストール失敗」と「テスト失敗」を明確に区別できます。
- Bash では
source .venv/bin/activateを使って仮想環境を有効化しないでください。代わりに./.venv/bin/<tool>を直接呼び出します。こちらのほうが移植性に優れ、サブシェル関連の予期しない動作を避けられます。PowerShell でも同様に& .\.venv\Scripts\<tool>.exeを使います。 - インストールの終了コードは直ちに伝播させてください。 Bash では
<install cmd> || exit $?を使います。PowerShell では$LASTEXITCODEを確認し、0 以外の場合はexit $LASTEXITCODEを実行します。
Bash 固有の要件
- シバン:
#!/bin/bash。 - ファイル名:
assets/に配置するrun_unittests_<lang>.sh。 - 引数:
$1。 - 実行可能にする:
chmod +x assets/run_unittests_<lang>.sh。
PowerShell 固有の要件
- シバンは不要です。 代わりに、先頭に
param([Parameter(Mandatory=$true)][string]$Subfolder)ブロックを置きます。 - ファイル名:
assets/に配置するrun_unittests_<lang>.ps1。 - 終了コード:
exit 69などを使います(PowerShell でも Bash と同様に機能します)。 - ツールチェーンの確認:
Get-Command <tool> -ErrorAction SilentlyContinueを優先して使い、特定のバージョンが必要な場合はツールの--version出力を解析します。 - ファイルシステム:
Test-Path、Remove-Item -Recurse -Force、New-Item -ItemType Directory、Copy-Item -Recurse、Set-Locationを使います。スペースを含むパスに対応できるよう、パスを引用符で囲みます。 chmodの手順は不要です。 実行ポリシーによってスクリプトの実行が阻止される可能性がある場合は、ユーザーにSet-ExecutionPolicy -Scope CurrentUser RemoteSignedを案内します。スクリプトにこのコマンドを組み込まないでください。
ワークフロー
- 対象の言語、シェルの種類(Bash または PowerShell)、依存関係マニフェスト(
pom.xml、requirements.txt/pyproject.toml、package.json、go.mod、Cargo.tomlなど)を確認します。不明な点があれば質問してください。 - 正確な構成を再確認するため、assets/run_unittests_java.sh を読みます。
- 上記の7つのステップをそれぞれ、対象の言語およびシェルに対応するコマンドに置き換えます。ツールチェーンの確認、依存関係のインストール、テストの実行は言語固有の部分です。それ以外は、Bash と PowerShell の構文間で機械的に置き換えます。
- 依存関係の分離の表から依存関係の分離方法を選び、ステップ6とステップ7の両方で一貫して使用します。
- 新しいスクリプトを
assets/run_unittests_<lang>.shまたはassets/run_unittests_<lang>.ps1に保存します。Bash の場合はchmod +xを実行します。 - 新しいスクリプトを参照するよう
config.yamlを更新します。 新しく作成したスクリプトへのパスを指定して、unit-tests-script:キーを追加または更新します(例:unit-tests-script: test_scripts/run_unittests_<lang>.sh)。これは必須です。レンダラーが開発ワークフロー中にユニットテストスクリプトを実行するために、この参照が必要です。 - スクリプトをリンクされたリソースとして、ベースの
.plainファイルにリンクします。 スクリプトをディスク上に作成したら、add-resourceスキルを使い、プロジェクトのベースとなる.plainモジュールの実装要件セクションからスクリプトを参照する必要があります。リンクした後、仕様を読み返し、リンク先のパスが今作成したスクリプトを指していることを確認します。
アンチパターン
- (重大な誤り)ソースのビルドフォルダーにインストール、ビルド、またはその他の書き込みを行わないでください。
$1として渡されるビルドフォルダーは読み取り専用の入力です。インストール先、キャッシュ、ビルド成果物、ログ、一時ファイルはすべて<temp>/<lang>_<basename>に配置してください。これには、ソースフォルダーをcwdまたはターゲットにしてpip install、npm install、mvn install、cargo buildを実行しないこと、venv /node_modules/.m2/.gocache/.cargoディレクトリをソースフォルダー内に作成させないこと、テストコマンドをソースフォルダー内から実行しないことも含まれます。ビルドをシステムの一時ディレクトリにステージングするのは、ソースフォルダーをレンダリングのクリーンで再現可能な成果物として保つためです。書き込みを行うとレンダラーの認識が損なわれ、後続のレンダリングにも問題が生じます。 - (重大な誤り)ビルドフォルダーの引数をそのまま連結してパスを作成しないでください。 現在のレンダラーでは、
$1は絶対パスとして渡されます。WORKING_FOLDER=.tmp/$1、WORKING_FOLDER=<lang>_$1、"$current_dir/$1"のようなパターンでは、パスが気付かないうちに二重になります(/project//abs/path/...)。典型的な症状は、ImportError: Start directory is not importable、またはプロジェクトルートが二重に含まれるパスを示す「folder does not exist」です。必ず$(basename "$1")/Split-Path -Leafを使用してください。 - 「全員インストール済み」の場合でも、ツールチェーンの確認を省略しないでください。呼び出し元のシステムは、ランタイムがないことを検出するために終了コード
69を使用します。 - ソースフォルダーをそのまま再利用しないでください。まず必ず
<temp>/<lang>_<basename>にコピーしてください。レンダラーはこの分離に依存しています。 - 作業フォルダーを残さないでください。最初の書き込みより前にクリーンアップ(
trap … EXIT/finally)を登録し、早期に失敗した場合もフォルダーが削除されるようにしてください。 - 終了コードの仕様を変更しないでください。システムの他の部分は
1、2、69を特定の処理に使用します。これらのコードは Bash と PowerShell の両方のバリアントで同一でなければなりません。 - 異なるシェルを混在させたスクリプト(例:PowerShell を検出する
.sh、またはその逆)を書かないでください。シェルごとにスクリプトを1つ用意し、適切な拡張子を付けてください。 - ユーザーのグローバル環境(
~/.m2、システム全体のpip、~/.cargoなど)に依存関係をインストールしないでください。並行実行や他のプロジェクトに影響しないよう、必ず$WORKING_FOLDER内に隔離してください。 - インストール手順が成功したことを確認せずに、テストコマンドを実行しないでください。インストールに失敗した後で「テスト」を実行すると、テスト失敗に見える誤解を招くエラーが発生します。
config.yamlの更新を忘れないでください。 ユニットテストスクリプトを作成したら、必ずconfig.yamlのunit-tests-script:キーを追加または更新し、新しいスクリプトを参照するようにしてください。この設定がないと、レンダラーはユニットテストスクリプトの場所を特定できません。- ベース
.plainファイルで、スクリプトをリソースとしてリンクすることを忘れないでください。config.yamlからのみ参照されているスクリプトは、仕様契約から見えません。レンダラーはunit-tests-script:をビルドシステムへのポインターとして扱い、モデルが読むテスト要件の契約の一部とは見なしません。add-resourceスキルを使って、ユニットテストのテスト要件を持つ.plainモジュールからスクリプトへの Markdown リンクを追加してください(ワークフローのステップ7を参照)。リンクされたリソースを省略すると、モデルは実際のランナーを一度も確認しないまま、ランナーが満たす必要のあるテスト関連コードを作成・レビューすることになります。 - 簡潔すぎるスクリプトを書かないでください。 手順の出力がないこと、インストール出力を
>/dev/nullにリダイレクトすること、--versionの出力がないこと、失敗を示すメッセージがないことは、スクリプトをテスト対象のコードよりデバッグしにくくします。規約の ログ — できる限り詳細に記録する というルールに厳密に従ってください。すべての手順で開始を知らせ、すべてのコマンドを表示し、失敗時には実行しようとしていた内容と実行場所を表示してください。