| name | testing |
| description | 任意の技術スタックに適用可能なテスト戦略、パターン、ベストプラクティス。以下の場合に使用:
- ユニットテスト、統合テスト、E2E テストの作成
- テスト戦略の策定やカバレッジの改善
- テストのベストプラクティスの学習
- 失敗テストやフレーキーテストのデバッグ
- テストデータ、モック、フィクスチャのセットアップ
Trigger phrases: write tests, unit test, integration test, E2E test, test coverage, test strategy, mock, fixture, test data
|
| allowed-tools | Read, Glob, Grep, Bash, Write, Edit, WebSearch, WebFetch |
| model | sonnet |
| user-invocable | true |
テスト
スタック適応型のテストパターンとプラクティス。このスキルはテストの原則と方法論を定義するものであり、特定のフレームワークコマンドを定義するものではない。
設計原則
- 仮定せず発見する: プロジェクトが使用するテストツールを検出する
- プロジェクト優先: プロジェクトに設定されたテストコマンドを使用する
- ツールよりパターン: 任意のフレームワークに適用できるテストの概念を教える
テストピラミッド
/\
/E2E\ 少数: 重要なユーザージャーニー (5-10%)
/------\
/ 統合 \ 一部: サービス境界 (15-25%)
/----------\
/ ユニット \ 多数: ビジネスロジック (65-80%)
/--------------\
テスト構造
Arrange-Act-Assert (AAA)
全テストフレームワークに共通するユニバーサルパターン:
Arrange: テストデータと依存関係のセットアップ
Act: テスト対象コードの実行
Assert: 期待される結果の検証
命名規則
[テスト対象]_[シナリオ]_[期待結果]
例:
- createUser_withValidData_returnsUser
- calculateTotal_withEmptyCart_returnsZero
- login_withInvalidCredentials_throwsAuthError
フレームワーク検出
ステップ 1: テスト設定の発見
プロジェクト内のテスト設定ファイルを探す:
ls -la *test*.config.* *jest*.* *vitest*.* pytest.ini setup.cfg pyproject.toml 2>/dev/null
ls -d tests/ test/ __tests__/ spec/ *_test/ 2>/dev/null
grep -A 5 '"test"' package.json 2>/dev/null
grep -A 5 '\[tool.pytest' pyproject.toml 2>/dev/null
ステップ 2: プロジェクトのテストコマンドを使用
常にプロジェクト定義のスクリプトを優先する:
grep -E '"test":|"test:' package.json 2>/dev/null
grep -A 3 '\[scripts\]' pyproject.toml 2>/dev/null
cat Makefile 2>/dev/null | grep -E '^test:'
プロジェクトに設定されたコマンドで実行する:
npm test が設定されている場合 → npm test を使用
make test が存在する場合 → make test を使用
- それ以外の場合、検出されたフレームワークの標準コマンドを検索
ステップ 3: フレームワークコマンドのリサーチ
テストフレームワークに不慣れな場合、WebSearch を使用:
WebSearch: "[framework name] run tests command [year]"
WebFetch: [official docs] → "Extract test execution commands"
ユニットテストの原則
ユニットテストすべきもの
- ビジネスロジックとアルゴリズム
- エッジケースと境界条件
- エラー条件と例外処理
- 入力バリデーション
- 純粋関数(副作用なし)
ユニットテストすべきでないもの
- フレームワーク/ライブラリのコード
- 単純なゲッター/セッター
- 実装の詳細(構造ではなく動作をテスト)
- サードパーティ統合(統合テストを使用)
モックのガイドライン
モックすべき場合:
- 外部サービス(API、データベース)
- 時間依存の操作(日付、タイマー)
- 乱数生成
- ファイルシステム操作
- ネットワークリクエスト
モックすべきでない場合:
- テスト対象のコード自体
- 単純なユーティリティ関数
- 内部のドメインロジック
- 高速かつ決定的なもの
統合テスト
API テストパターン
1. テストデータベース/状態のセットアップ
2. エンドポイントへの HTTP リクエスト
3. レスポンスステータスコードのアサーション
4. レスポンスボディの構造と値のアサーション
5. 副作用(データベース状態変更)のアサーション
6. テストデータのクリーンアップ
データベーステスト戦略
| 戦略 | 説明 | 使用場面 |
|---|
| トランザクションロールバック | トランザクション内でテスト実行、終了後ロールバック | 高速な分離が必要 |
| テストデータベース | テスト専用の別データベース | フル統合テスト |
| シードとクリーン | フィクスチャをロード、終了後クリーン | リアルなデータシナリオ |
| コンテナ | テスト実行ごとの一時的な DB | CI/CD 環境 |
E2E テスト
ロケーターの優先順位
要素の検索に関するベストプラクティス(ほとんどの E2E フレームワークに適用):
- アクセシビリティロール - 最も堅牢、ユーザーのインタラクション方法と一致
- ラベル - フォーム入力用
- テキストコンテンツ - 表示コンテンツ用
- テスト ID - 最後の手段、テスト専用に明示的
ページオブジェクトモデル
ページの抽象化で E2E テストを整理:
pages/
login.page.[ext]
dashboard.page.[ext]
settings.page.[ext]
tests/
login.spec.[ext]
dashboard.spec.[ext]
E2E のベストプラクティス
- 全てではなく重要なユーザージャーニーをテスト
- リアルだが一貫したテストデータを使用
- フレーキーなセレクターを避ける(安定した識別子を優先)
- E2E テスト数を少なく保つ(ピラミッド原則)
- E2E テストは隔離された環境で実行
テストデータ管理
ファクトリー
動的テストデータにはファクトリーを使用:
- リアルなデータをプログラム的に生成
- 必要に応じて特定のフィールドをオーバーライド
- テスト内のハードコードされたマジック値を避ける
フィクスチャ
安定したシナリオにはフィクスチャを使用:
- ログインテスト用の事前定義ユーザーアカウント
- 変更されないリファレンスデータセット
- 異なるシナリオ用の設定プリセット
カバレッジガイドライン
目標閾値
| メトリクス | 推奨目標 |
|---|
| ステートメント | 80% |
| ブランチ | 80% |
| 関数 | 80% |
| 行 | 80% |
カバレッジコマンド
プロジェクト設定からカバレッジコマンドを発見する:
grep -E 'coverage|cov' package.json 2>/dev/null
grep -E 'coverage|cov' pyproject.toml 2>/dev/null
grep -E 'coverage|cov' Makefile 2>/dev/null
カバレッジスクリプトが存在しない場合、フレームワークのカバレッジオプションを検索:
WebSearch: "[test framework name] code coverage command"
テスト品質チェックリスト
失敗テストのデバッグ
プロセス
- 失敗メッセージを読む - 期待値と実際値を理解
- テストを単独で実行 - 他のテストへの依存がないか確認
- ロギングを追加 - 中間値を出力
- テストセットアップを確認 - 前提条件が満たされているか検証
- モックを確認 - モックが期待値を返しているか確認
- フレーキーさをチェック - 複数回実行
フレーキーテストの指標
- テストの合否が一貫しない
- テストがタイミングや実行順序に依存
- テストが実際の外部サービスを使用
- テストに非同期コードの競合状態がある
フレーキーテスト管理
フレーキーテストは CI の信頼性と開発者の信頼を損なう。検出、診断、修正のための体系的なアプローチを提供する。
フレーキーテストの検出
ローカルでの検出:
for i in {1..10}; do npm test -- --testNamePattern="suspect test" && echo "Pass $i" || echo "FAIL $i"; done
for i in {1..10}; do pytest path/to/test.py::test_name -x && echo "Pass $i" || echo "FAIL $i"; done
CI での検出パターン:
- 異なる PR で同じテストが断続的に失敗
- コード変更なしでリトライ時に通過
- 特定の CI ランナーや時間帯のみで失敗
フレーキーテストの一般的な原因
| 原因 | 症状 | 解決策 |
|---|
| タイミング依存 | 「タイムアウト」やタイミングの不整合で失敗 | sleep ではなく明示的な待機を使用 |
| 共有状態 | 他テストと同時実行で失敗、単独では通過 | テストデータの分離、フィクスチャを使用 |
| 順序依存 | テスト順序変更で失敗 | 各テストを独立させる |
| 外部サービス | ネットワークエラー、レート制限 | 外部依存をモック |
| 競合状態 | 非同期コードで断続的に発生 | 適切な async/await の処理 |
| リソース枯渇 | テストスイートの後半で失敗 | リソースのクリーンアップ、制限の引き上げ |
| 時間依存ロジック | 特定の時間(深夜、夏時間)で失敗 | 時間/日付関数をモック |
| ランダムデータ | ランダム入力で異なる結果 | 乱数ジェネレーターにシードを設定 |
フレーキーテストの修正
戦略 1: sleep を明示的な待機に置き換え
# 悪い例: 固定 sleep(短すぎるか無駄な場合がある)
sleep(2)
assert element.visible
# 良い例: タイムアウト付きの条件待機
wait_for(lambda: element.visible, timeout=5)
戦略 2: テストデータの分離
# 悪い例: テストがデータベース状態を共有
def test_create_user():
create_user("john") # 他のテストと競合する可能性
# 良い例: 各テストが分離されたデータを持つ
def test_create_user(unique_user_factory):
user = unique_user_factory() # テストごとに一意のユーザーを生成
戦略 3: 時間依存コードのモック
# 悪い例: 実時間を使用
def test_expiration():
token = create_token(expires_in=60)
time.sleep(61)
assert token.is_expired()
# 良い例: 時間をモック
def test_expiration(mock_time):
token = create_token(expires_in=60)
mock_time.advance(61)
assert token.is_expired()
戦略 4: 乱数ジェネレーターにシードを設定
# 悪い例: 非決定的
def test_random_selection():
result = pick_random_item(items) # 毎回異なる
# 良い例: 再現性のためにシードを設定
def test_random_selection():
random.seed(42) # または pytest-randomly の --randomly-seed を使用
result = pick_random_item(items)
隔離戦略
フレーキーテストをすぐに修正できない場合:
- フレーキーとしてマーク - フレームワーク固有の skip/xfail アノテーションを使用
- イシューを文書化 - 再現手順付きのチケットを作成
- ブロッキング CI から除外 - ノンブロッキングテストスイートに移動
- 修正期限を設定 - 隔離は一時的であるべき
# 例: Jest
test.skip('flaky: issue #123 - timing issue in CI', () => {...})
# 例: pytest
@pytest.mark.skip(reason="Flaky: issue #123 - race condition")
def test_flaky_feature(): ...
# 例: 別の CI ジョブで追跡
# .github/workflows/ci.yml
- name: Run stable tests (blocking)
run: npm test -- --testPathIgnorePatterns="flaky"
- name: Run flaky tests (non-blocking)
run: npm test -- --testPathPattern="flaky" || true
CI リトライ戦略
フレームワークオプション:
- Jest:
jest --runInBand(シーケンシャル)、jest-circus リトライ
- pytest:
pytest-rerunfailures プラグイン
- CI: 組み込みリトライ(GitHub Actions
retry、GitLab retry:)
リトライを使用するタイミング:
- 根本原因修正中の一時的な軽減策として
- 本当に一時的な問題(ネットワーク不具合)に対して
- 壊れたテストの恒久的な解決策としては使わない
フレーキーテストの予防
| プラクティス | メリット |
|---|
| テストをランダム順で実行 | 順序依存を早期に検出 |
| マージ前に CI で実行 | 環境差異を検出 |
| テストコンテナを使用 | 一貫したデータベース/サービス状態 |
| 外部サービスをモック | ネットワークのフレーキーさを排除 |
| グローバル状態を避ける | テスト間の干渉を防止 |
| CI タイムアウトを適切に設定 | 遅い/ハングしたテストを検出 |
Rules (L1 - Hard)
テストの信頼性と安全性に不可欠。
- NEVER: テスト間で可変状態を共有しない(フレーキーさの原因)
- NEVER: ハードコードされた遅延を使用しない(適切な非同期待機を使用)
- ALWAYS: バグ修正前にテストを書く(まず再現)
Defaults (L2 - Soft)
効果的なテストに重要。適切な理由がある場合はオーバーライド可。
- テスト実行前にプロジェクトのテストフレームワークを発見する
- プロジェクトに設定されたテストコマンドを使用する
- エッジケースとエラー条件をテストする
- 実装の詳細ではなく動作をテストする
- テストをスキップする際は理由を文書化する
Guidelines (L3)
包括的なテストカバレッジのための推奨事項。
- consider: 明確さのために AAA パターン(Arrange-Act-Assert)の使用を検討
- prefer: 動的テストデータにはファクトリーの使用を推奨
- consider: カバレッジ目標を約 80% に設定することを検討