mcpField
T
test-harness

test-harness

github.com/mathews-tomテスト・品質339102 回インストール1 日前に更新
リポジトリ

test-harness について

test-harnessは、Pythonソースコードから実行可能なpytestテストを作成するスキルです。テストの作成・保守を行う開発者向けに、通常・境界・エラー・非同期ケースと、プロジェクトの慣習に沿ったフィクスチャやモックを整理します。

主な機能

  1. 01関数と依存関係の分析
  2. 02通常・境界・エラーケースの網羅
  3. 03フィクスチャとモックの設計
  4. 04非同期テストのガイダンス
  5. 05簡易・標準・包括モード

活用シーン

  1. 01新しいモジュールのpytestテスト作成
  2. 02既存テストと重複せずカバレッジを拡張
  3. 03外部依存や非同期コードのテスト

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mathews-tom/armory --skill test-harness

インストール後、Claude Code で/test-harnessを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
cases.yaml
evals/fixtures/
sample_module.py
references/
async-testing.md
coverage-targets.md
fixture-design.md
mock-strategies.md
pytest-patterns.md

詳細

テストハーネス

ソースコードから包括的で実行可能な pytest ファイルを生成する、体系的なテストスイート生成機能です。関数シグネチャ、依存関係グラフ、複雑性の高い箇所を分析し、正常系、境界条件、エラー状態、非同期フローを網羅するテストを、適切なスコープのフィクスチャと目的を絞ったモックを用いて生成します。

参照ファイル

ファイル内容読み込むタイミング
references/pytest-patterns.mdフィクスチャのスコープ、parametrize、マーク、conftest の構成、組み込みフィクスチャ常に
references/mock-strategies.mdモックの判断ツリー、パッチの境界、アサーション、アンチパターン対象に外部依存関係がある場合
references/async-testing.mdpytest-asyncio のモード、イベントループのフィクスチャ、非同期モック対象に非同期コードが含まれる場合
references/fixture-design.mdファクトリーフィクスチャ、yield によるティアダウン、スコープの選択、構成テストに複雑なセットアップが必要な場合
references/coverage-targets.mdカバレッジのしきい値テーブル、ブランチと行の違い、pytest-cov の設定、除外パターンカバレッジ評価が求められる場合

前提条件

  • pytest >= 7.0
  • Python >= 3.10
  • pytest-asyncio — 非同期テストを生成する場合のみ必要です
  • pytest-mock — オプションです。unittest.mock の代替として mocker フィクスチャを提供します

ワークフロー

フェーズ 1:事前調査

テストを 1 つも書く前に、対象コードのモデルを構築します。

  1. 対象範囲を特定する — どの関数、クラス、モジュールをテストする必要がありますか?指定がない場合は、最近の変更を確認します:git diff --name-only HEAD~5
  2. 関数シグネチャを読む — パラメーター、型、戻り値の型、デフォルト値を確認します。各パラメーターがテストの観点になります。
  3. 依存関係をマッピングする — どの呼び出しが外部システム(DB、API、ファイルシステム、クロック)に接続していますか?これらがモックの候補です。
  4. 複雑性の高い箇所を検出する — 分岐数が多い関数、ネストが深い関数、戻り値の経路が複数ある関数には、より多くのテストケースが必要です。
  5. 既存のテストを確認する — テストがすでに存在する場合は、そのカバレッジを把握します。重複させず、拡張してください。
  6. プロジェクトの規約を読む — CLAUDE.md、conftest.py、pytest.ini/pyproject.toml を確認し、すでに使用されているフィクスチャ、マーカー、テスト構成のパターンを把握します。
フェーズ 2:テストケースの列挙

テスト対象の各関数について、次の 4 つのカテゴリに沿ってケースを列挙します。

カテゴリテストする内容例
正常系期待する入力で期待する出力が得られることadd(2, 3) が 5 を返す
境界条件有効な入力の範囲内にある境界値空文字列、ゼロ、最大の整数、単一要素
エラー無効な入力で適切な例外が発生することstr が期待される箇所に None、負のインデックス
状態状態遷移で正しい副作用が発生することオブジェクトが pending から active に遷移する

各ケースについて、次の項目を記録します。

  • 入力値(抽象的な値ではなく、具体的な値)
  • 期待する出力または例外
  • 必要なセットアップ(フィクスチャ)
  • 必要なモック(抑制する外部呼び出し)

テストロジックが同じで、入力値や出力値だけが異なるケースは、パラメーター化します。

フェーズ 3:フィクスチャの設計
  1. 共通のセットアップを特定する — 3 件以上のテストで同じオブジェクトが必要な場合は、フィクスチャに切り出します。

  2. スコープを選択する — セットアップの重複を避けられる最も狭いスコープを使用します。

    スコープ使用するケース例
    functionデフォルト。各テストで新しい状態を取得しますほとんどのユニットテスト
    classクラス内のテストで負荷の高いセットアップを共有しますテストクラスごとの DB 接続
    moduleファイル内のすべてのテストでセットアップを共有します読み込み済みの設定ファイル
    sessionテスト実行全体でセットアップを共有しますDocker コンテナの起動
  3. ティアダウンを設計する — クリーンアップが必要な場合は、yield フィクスチャを使用します。テスト後に副作用(一時ファイル、DB の行、モンキーパッチ)を決して残さないでください。

  4. conftest の候補を特定する — 複数のテストファイルで使われるフィクスチャは、conftest.py に配置します。1 つのファイルだけで使われるフィクスチャは、そのファイル内に配置します。

フェーズ 4:モック戦略
  1. モックする対象を決める — 外部依存関係のみをモックします。

    • ネットワーク呼び出し(API、データベース、メッセージキュー)
    • ファイルシステム操作(I/O ではなくロジックをテストする場合)
    • 時間に依存する動作(datetime.now、time.sleep)
    • ランダムまたは非決定的な動作
  2. モックしない対象を決める — 以下は決してモックしないでください。

    • テスト対象の関数
    • 対象から呼び出される純粋関数(対象を通してテストします)
    • データ構造と値オブジェクト
  3. モックのレベルを選択する — 定義箇所ではなく、テスト対象モジュールのインポート境界でパッチを適用します。@patch('requests.get') ではなく、@patch('mymodule.requests.get') を使います。

  4. モックのアサーションを追加する — 各モックで、期待する引数を使って期待する回数だけ呼び出されたことをアサートします。アサーションのないモックは、カバレッジの穴になります。

フェーズ 5:出力

以下の構成に従ってテストファイルを生成します。

  1. インポート(pytest、モック、対象モジュール)
  2. 定数とテストデータ
  3. フィクスチャ(スコープ順:session > module > class > function)
  4. 対象関数ごとにグループ化したテストクラスまたは関数
  5. 該当する場合はパラメーター化テスト

出力形式

# tests/test_{module}.py

import pytest
from unittest.mock import Mock, patch, MagicMock

from {module} import {target_function, TargetClass}


# ============================================================
# Fixtures
# ============================================================

@pytest.fixture
def valid_input():
    """Standard valid input for happy path tests."""
    return {concrete values}


@pytest.fixture
def mock_database():
    """Mock database connection."""
    with patch("{module}.db_connection") as mock_db:
        mock_db.query.return_value = [{expected data}]
        yield mock_db


# ============================================================
# {target_function} Tests
# ============================================================

class TestTargetFunction:
    """Tests for {target_function}."""

    def test_happy_path(self, valid_input):
        """Returns expected result for valid input."""
        result = target_function(valid_input)
        assert result == {expected}

    @pytest.mark.parametrize(
        "input_val, expected",
        [
            ({boundary_1}, {expected_1}),
            ({boundary_2}, {expected_2}),
            ({boundary_3}, {expected_3}),
        ],
        ids=["empty", "single", "maximum"],
    )
    def test_boundary_conditions(self, input_val, expected):
        """Handles boundary inputs correctly."""
        assert target_function(input_val) == expected

    def test_invalid_input_raises(self):
        """Raises TypeError for invalid input."""
        with pytest.raises(TypeError, match="expected str"):
            target_function(None)

    def test_external_call(self, mock_database):
        """Calls database with correct query."""
        target_function("lookup_key")
        mock_database.query.assert_called_once_with("SELECT * FROM t WHERE key = %s", ("lookup_key",))

スコープの設定

モードスコープ深さ使用するケース
quick単一関数ハッピーパス + 1 つのエラーケース迅速な反復、TDD のレッド・グリーンサイクル
standardファイルまたはクラスハッピーパス + 境界値 + エラー + モックほとんどのリクエストでのデフォルト
comprehensiveモジュールまたはパッケージすべてのカテゴリ + 非同期 + パラメーター化マトリックスリリース前、重要なパスのコード

キャリブレーションルール

  1. テストの分離は必須です。 すべてのテストは単独で、またどの順序で実行してもパスしなければなりません。 ほかのテストの副作用に依存するテストがあってはなりません。
  2. モックを適切に使います。 外部依存関係をモックし、内部ロジックはモックしないでください。過剰なモックは、コードが壊れていてもテストがパスする原因になります。モックが不十分だと、ネットワークがダウンしたときにテストが失敗します。
  3. 抽象的な値より具体的な値を使います。 テストデータにはプレースホルダーではなく、具体的な値を使ってください。"test_email" ではなく "[email protected]"、"some_number" ではなく 42 を使います。具体的な値なら、抽象的なプレースホルダーでは見逃される型の不一致を検出できます。
  4. テストごとにアサーションの焦点を1つにします。 テストでは1つの動作を検証します。異なる側面であっても、同じ動作を検証するアサーション(例:戻り値と副作用)なら複数あってもかまいませんが、無関係な動作を検証してはいけません。
  5. 重複させず、パラメーター化します。 入力値と出力値だけが異なるテストが2つある場合は、@pytest.mark.parametrize でまとめてください。読みやすいテスト名にするため、ids を使います。
  6. プロジェクトの慣例に合わせます。 プロジェクトで conftest.py のフィクスチャ、クラスベースのテスト、または特定のマーカーが使われている場合は、そのパターンに従ってください。競合するテストスタイルを導入しないでください。

エラー処理

問題解決策
対象関数に型ヒントがない使用パターン、デフォルト値、docstring から型を推測します。不確かな点はテストの docstring に記載します。
対象に深くネストされた依存関係があるテスト対象の関数に最も近い境界でモックします。推移的依存関係を個別にモックしないでください。
既存のテストインフラストラクチャがない(conftest、pytest 設定なし)テストファイルと同じ場所に最小限の conftest.py を生成します。追加したことを出力に記載します。
対象コードをテストできない(グローバル状態、隠れた依存関係)設計上の問題を出力で指摘します。テスト可能な範囲のテストを生成し、テストしやすくするリファクタリングを提案します。
非同期コードが検出されたが pytest-asyncio がインストールされていない依存関係の要件を記載します。@pytest.mark.asyncio を使った非同期テストのスタブを生成し、インストールするよう案内します。
対象モジュールをインポートできないインポートエラーを報告します。インポートできないコードのテストは生成しないでください。

テストを生成しない場合

以下の場合は、ユーザーの依頼を再検討してください。

  • コードが自動生成されたもの(protobuf、OpenAPI クライアント、ORM モデル)の場合 — 生成器またはスキーマをテストし、出力されたコードはテストしないでください
  • 依頼が UI/E2E テストの場合 — このスキルで生成するのは単体テストと統合テストのみです
  • テスト可能な明確な動作がない場合(純粋な設定、定数の定義)
  • サードパーティーライブラリのコードのテストを求められた場合 — ライブラリ自体ではなく、そのライブラリの使い方をテストしてください

よくある正当化

よくある正当化実態
「手動テストで十分です」手動テストは CI で実行されず、リグレッションを検出できず、コードベースの拡大にも対応できません
「このコードはテストするほど複雑ではありません」シンプルなコードもやがて複雑になります。テストは期待される動作を記録し、将来の変更によるリグレッションを検出します
「テストは後で追加します」テストは仕様です。テストがなければコードの動作は未定義で、結局後回しのままになります
「すべてをモックすればテストは速くなります」過剰にモックしたテストは、実際のシステムが失敗しても通過します。呼び出しチェーンの奥深くではなく、境界でモックしてください
「カバレッジ 100%ならコードは正しいです」カバレッジが測定するのは実行であり、正しさではありません。意味のあるアサーションなしにコードを実行するだけのテストには価値がありません
「正常系のテストだけで十分です」本番環境でのインシデントのほとんどは、エッジケースやエラーパスが原因です。正常系だけのテストは誤った安心感を与えます

危険信号

  • エッジケースやエラーパスがなく、正常系だけを対象とするテスト
  • 動作(「見つからない場合に 404 を返す」)ではなく、実装(「test_calls_function」)を説明するテスト名
  • 1 つのテストで 2 つを超えるモックを使用していること。テスト対象のユニットの結合度が高すぎることを示しています
  • 実行順序や共有された変更可能な状態に依存するテスト
  • 観測可能な動作ではなく、実装の詳細(モックの呼び出し回数)に対するアサーション
  • 「ユニットテストでカバーできる」という理由で結合テストを省略すること

検証

  • テストが Arrange-Act-Assert 構造に従い、各フェーズが明確に分かれている
  • テスト名が動作を説明している:test_<unit>_<scenario>_<expected_outcome>
  • エッジケースを網羅している:空の入力、境界値、エラーパス、null/None
  • カバレッジがしきい値を満たしている:全体で 80%、新規コードで 90%、重要なパスで 95%
  • すべてのテストが成功する:pytest / npm test が、出力を取得した状態で終了コード 0 を返す
  • 実行順序に依存するテストがなく、任意の順序で実行できる
  • モックは境界(外部 API、システムクロック、ユニットテストにおけるファイルシステム)でのみ使用している

コメント