本文へスキップ

Skills

chimera-test-the-wiring-not-the-class

テストの中で手組みしたクラスは、そのクラスが動くことを証明するだけで、そこに何かが到達することは証明しません——本番が実際に通る経路をカバーしてください。

パターン来歴: cleanステータス: activev0.1.0 · Apache-2.0

カードを読んだレビュアーが与えるものであり、ファイルが自分について主張するものではありません。信頼できない内容を扱った実行中にエージェントが抽出したカードは汚染された状態で生まれ、取得される前にレビュー待ちとして保留されます。

どんなときに思い出すか

  • テストでは動くがアプリでは動かない
  • ファクトリやレジストリ越しに追加した
  • フラグの既定値が off
  • テストは緑、動作は壊れている

価値はたいてい「回避」と「確認」にあります。「実施」は誰でも書ける節です。

上のカード本文は翻訳です。CLI が取り込み、エージェントが実行時に読み、下のハッシュが証明するのは英語の原文です。

トリガー

本番が間接的に到達するコンポーネント——ファクトリ、レジストリ、プラグインローダー、設定フラグ、ルーター、CLI のエントリポイント、依存性注入コンテナ経由——を作った場合に当てはまる。そしてテストはそれを直接構築し、そのメソッドを呼んでいる。

そのテストはすべて通りながら、稼働中のシステムではそのコンポーネントに到達できない、ということがありうる。登録処理も、フラグのデフォルト値も、そもそもそれを含めるかどうかを決める組み立て側の分岐も、テストのどこも実行していないからだ。

呼び出し側が import して直接呼ぶ純粋関数には当てはまらない。そこでは import こそが結線であり、ユニットテストがそれをカバーする。また、アルゴリズムを意図的に単独でテストしている場合にも当てはまらない——それらのテストは正しく、そのまま残すべきだ。このカードが言っているのは、それだけでは十分ではないということである。

実施

  1. ユーザーが実際に触れるエントリポイントを名指しする。CLI のサブコマンド、HTTP のルート、エージェントの実行ループ、スケジュールされたジョブなどだ。テストを書く前にそれを書き留めること。

  2. そこから始まり、自分のコンポーネントにコンストラクタ引数を一切渡さないテストを最低1つ書く。その機能を起こすためにテストが自分のクラス名を書く必要があるなら、それは結線をテストしていない。

  3. 本番が構築するのと同じやり方でオブジェクトを構築する。本物のファクトリや設定ローダーを呼ぶこと:

    # WRONG — proves the class, not the wiring: the component is handed to the thing under test,
    # so the test passes whether or not anything in production ever hands it over.
    assert "reminder" in render(feature=Feature(text="reminder"))
    
    # RIGHT — build it the way the entry point builds it, then look for the same observable
    assert "reminder" in build_the_real_way(config).render()
    

    Chimera 自身の事例は名指しする価値がある。そのクラスは一度も壊れていなかったからだ。スキルカードには動作するリトリーバーも、動作するストアも、動作するインジェクターもあった——そして chimera/config.py:244 には skill_cards: bool = Field(default=False, ...) と書かれており、素の状態のデプロイでは何一つ注入されていなかった。ユニットテストはすべて通っていた。最終的にこれを捕捉した測定は、生成されたスキル数と注入されたスキル数を数え、39 対 0 を見つけた。

  4. そのコンポーネントに到達した場合にしか現れえない観測可能なものを検証する。レンダリングされたプロンプト中のテキスト、書き込まれた行、ログ行、終了コードなどだ。

  5. デフォルトを検証する。機能をフラグの背後に置いて出荷するなら、上書きなしでデフォルト値を読み、それが何であるかを検証する別のテストを追加すること。常にフラグを強制的にオンにして実行するスイートは、ユーザーが何を手にするのかを教えてくれない。

回避

本番が結線する協力オブジェクトを手で組み立てること。この失敗の形は、ルーターが一度も登録しないのに完全なユニットカバレッジを持つハンドラークラスや、設定キーのデフォルトがオフになっている機能だ。クラスは正しく、テストも正しく、それでいて製品の中でその機能は何もしない。赤くなるものは何もないので、何も調査されず、その隙間は誰かが手動でその機能を試すまで生き延びる。

カバーしようとしている継ぎ目そのものを偽装することも避けること。ファクトリにパッチを当てたり、設定ローダーをスタブ化して自分のコンポーネントを含むオブジェクトを返させたりすることは、そのテストが存在する理由だったコードをまさに削除してしまう:


monkeypatch.setattr(mod, "load_plugins", lambda: [MyPlugin()])

代わりに最も外側の境界——ネットワーククライアント、時計、LLM 呼び出し——で偽装し、エントリポイントから自分のコンポーネントまでの間にあるすべてを本物にしておくこと。

確認

結線を削除してスイートを実行する。登録の行——@register デコレータ、ディスパッチ辞書のエントリ、include_router(...) の呼び出し、設定スキーマ内のデフォルト——をコメントアウトする。

そして二者択一の問い: 赤くなったテストがあったか、そしてそれは自分のクラス名を一度も書いていないテストだったか。

スイートが緑のままなら、カバレッジはクラスレベルにとどまり、結線は未テストである。赤くなった唯一のテストがクラスを直接構築するものだったなら、答えは同じだ。その後で行を元に戻して緑を確認し、削除した登録処理が出荷されないよう、コミット前に git diff すること。

リスク

エントリポイントのテストは遅く、デバッグしにくく、原因の局在化も劣る。1つ失敗したとき、その機能が壊れていることは分かっても、10あるコンポーネントのどれが壊したのかは分からない。それは現実のコストであり、このカードに対する誤った反応は、エンドツーエンドのテストのためにユニットテストを削除することだ。両方を保つこと——結線のテストは壊れたことを教え、ユニットテストは何が壊れたのかを教える。

本物のエントリポイントを通ることは、CI では触れたくないものに触れてしまうこともある。有料の API、稼働中のデータベース、サンドボックス外のファイルシステムなどだ。エントリポイントに到達する唯一の方法が、金を使うか本番を書き換えることであるなら、無理に押し通さないこと。代わりに組み立て側の関数を直接カバーし、真の経路まであと一歩足りていないことを受け入れる。

そしてこのカードが捕捉するのは到達可能性であって、正しさではない。ある機能はエントリポイントに完璧に結線されていながら、なお誤った答えを返しうる。したがって結線テストが緑であることは、出力が実際に何を言っているかを検証せずに済ませてよい許可ではない。

使い方

カードはデータです。リポジトリをクローンし、パスで取り込んでください。ネットワーク経由で届いたものは汚染扱いとなり、承認されるまで保留されます。それが望ましい挙動であり、ここにワンライナーのインストーラーがない理由です。

git clone https://github.com/brcampidelli/chimera-agent.git
chimera skills-import chimera-agent/skills/chimera-test-the-wiring-not-the-class/SKILL.md

完全性

公開された状態のファイルの SHA-256。取り込む側は、受け取ったものがこのページに表示されたものと同じか確認できます。

564f2b0aaff3bbb51ca9bfa013d89e3d47d92166f498803c688c3b859b378475

リポジトリでカードを読む