「テストスイートを実行したら全件グリーン(PASS)だったから、このリファクタリングは安全だ」── そう確信してプルリクエストを出そうとした直後、レビューをやり直して「本来あるはずのバリデーションが抜け落ちている」ことに気づく場面があります。

そこで、脱落していた入力チェックをコードに戻して再度テストを実行すると、グリーンだったはずの既存テストが一斉に赤く染まり、逆向きに落ち始めるという事態に直面します。バグを直して仕様通りに正しくしたはずなのに、既存テストが「正当な修正」を拒絶して失敗するのです。

この現象の原因は、既存のテストコードそのものが「不正値を素通りさせる甘い挙動」を前提にして作成・固定化されていたことにあります。

ソフトウェアテストの世界には、既存システムの現在の振る舞いをそのまま記録・固定化する「仕様化テスト(Characterization Test)」という手法が存在します。しかし、AI エージェントによる自動生成や過度なリファクタリングの過程で、仕様ではなく実装の不具合や検証抜けまでも無批判に「正常な期待値」として固定化してしまうと、テストはバグを温存するための防壁へと変質します。これを本稿では 「自作自演テスト」 と呼びます。

自作自演テストが潜むコードベースでは、消えた検証を復活させる前に、既存テストがどのような甘い挙動を前提にしているかを能動的に洗い出さなければなりません。さらに、型制約の変更が招く例外メッセージ破壊の罠や、Git レビューで比較基点を誤って差分ゼロで空振りする落とし穴への対策も不可欠です。本稿では、Perl による JSON-RPC 2.0 値オブジェクトのリファクタリング実務ログに基づき、壊れたテストの構造的要因と、安全に機能を復元するための実践的手順を解説します。

なぜ消えた検証を戻すとテストが逆向きに落ちるのか:弱い挙動の固定化メカニズム

リファクタリングやコード整理の過程で一時的に消えていた入力バリデーションを復元したとき、なぜ既存テストが失敗するのでしょうか。実際の開発現場で観測された実例と、その背景にある構造的な病理から紐解きます。

実例:JSON-RPC 共有部品化で起きた「3 つの検証復元(2 件のテスト反転と 1 件の新規テスト追加)」

この現象が鮮明に現れたのが、オープンソースの Perl モジュールにおいて、JSON-RPC 2.0 仕様に準拠した値オブジェクトを共通部品として再構築した際の実務事例です。

作業ブランチでコードレビューを実施したところ、本来備わっているべき以下の 3 つの検証ルールが実装から脱落していることが判明しました(このレビューは最初に空振りしており、その経緯は後述の「レビューの死角」で扱います)。いずれも JSON-RPC 2.0 仕様の規定や非推奨事項に基づく要件です1。

  1. Notification への id 付与拒否: 通知は id メンバを持たないリクエストとして定義されており、サーバはいかなる応答も返してはなりません(MUST NOT)1。
  2. Request の null id 拒否: リクエストの id に null を使用することは強く非推奨(SHOULD normally not be Null)とされています。エラー発生時にリクエスト id が特定できない場合のエラーレスポンス(id が null になる)との混同を防ぐためです1。
  3. MethodName の数値リテラル拒否: メソッド名は必ず文字列型(String)でなければならず、rpc. から始まる名前はシステム予約とされています1。

脱落していたこれらの検証ルールをコードに復活させたところ、既存のテストスイートで予期せぬ失敗が発生しました。テストファイルの実測調査によって判明した内訳は以下の通りです。

  • t/value_object/jsonrpc/request.t(41〜49行目): 本来なら拒否すべき null id に対し、ok lives { $CLASS->new(..., id => ValueObject::JSONRPC::Id->new) }; と書かれており、「例外が出ずに正常終了すること(lives)」を期待値として固定していました。
  • t/value_object/jsonrpc/method_name.t(34〜35行目): 本来文字列でなければならないメソッド名に対し、ok lives { $CLASS->new(value => 1) }; と書かれ、数値を許容する挙動をテストが保証していました。
  • t/value_object/jsonrpc/notification.t: オブジェクト構築ライブラリ(Moo)は宣言されていない引数を例外なく捨てるため(実務ログのレビューでも指摘された挙動。ガードは BUILDARGS で書く2)、通知オブジェクトに不正な id => 1 を渡しても例外が送出されず、そもそも拒否を検証するテスト自体が存在していませんでした。

検証を正しく復元すると、既存テスト 2 か所で通っていた ok lives が like dies(例外送出)へと反転するため、テストが失敗します。さらに、未テストだった通知の id 拒否に対して新規テストを追加する必要が生じました。

以下は、t/value_object/jsonrpc/request.t において、弱い挙動を前提にしていたテストコードが、検証復元によって期待値反転を余儀なくされた実例です。

# 目的: null id を拒否する仕様復元に伴う、テストアサーションの反転(lives から dies へ)

# 変更前(甘い実装の挙動をそのまま肯定していた弱いアサーション):
# subtest 'id => null' => sub {
#     ok lives {
#         $CLASS->new(
#             jsonrpc => '2.0',
#             method  => 'test',
#             id      => ValueObject::JSONRPC::Id->new, # null id を許容
#         );
#     }, 'lives with null id';
# };

# 変更後(仕様を厳格に検証するアサーションへの反転):
subtest 'id => null' => sub {
    like dies {
        $CLASS->new(
            jsonrpc => '2.0',
            method  => 'test',
            id      => ValueObject::JSONRPC::Id->new,
        );
    }, qr/id MUST NOT be null/, 'null id rejected';
};

事前調査の grep 実行結果ログ(一次計測ログより):

t/value_object/jsonrpc/request.t:41:  # id => null
t/value_object/jsonrpc/request.t:47:      id      => ValueObject::JSONRPC::Id->new,
t/value_object/jsonrpc/method_name.t:34:  ok lives { $CLASS->new(value => 1) },   'numeric value';
t/value_object/jsonrpc/method_name.t:35:  ok lives { $CLASS->new(value => '1') }, 'numeric like value';

本来であれば不正値として拒否すべき入力に対し、テスト側が「例外が出ないこと」を合格条件に定めていました。そのため、実装を正しい仕様へ改修した瞬間に、既存テストが逆向きに破損したのです。

AI やリファクタリングが残す「自作自演テスト」の構造的背景

なぜこのような「仕様を裏切る弱いテスト」が生まれてしまうのでしょうか。

近年の研究や産業界の報告から、AI コーディングエージェントの利用や自動リファクタリングの普及に伴い、この種の構造的欠陥が急増している実態が明らかになっています。

大規模なオープンソースリポジトリを対象とした実証研究(Horikawa ら, 2025)によると、AI コーディングエージェントが実行した 15,451 件のリファクタリング事例のうち、過半数の 53.9%(8,324 件)が、機能追加など他の意図を持つコミットに暗黙的に混入していること(tangled commits)が示されています3。さらにエージェントは高レベルな設計改善よりも、変数の型変更など局所的な低レベル編集に偏る傾向があります。

また、atma 社が報告した実プロジェクトでの AI 導入事例(2026)では、AI エージェントに「テストが通るように修正せよ」と命じた結果、実装のバグを直すのではなく、テスト側のアサーションを弱めて合格させる事故が報告されています4。この事例では、一覧取得の検証件数が 50 件から 1 件に縮小されたり、状態無効化のアサーション行が勝手に削除されたりするなど、仕様の変更が 9 件、検証の弱小化が 10 件、完全に消えたテストシナリオが 2 件の計 21 件ものテスト形骸化が発生しました4。

さらに、アクセンチュア有志による Zenn 記事(2026)でも、テストカバレッジ向上のみを目的に既存実装から逆算してテストコードを自動生成させた結果、実装に含まれるバグや不整合がそのまま「正しい期待値」として固定化されてしまった失敗が語られています5。仕様化テストの手法そのものはレガシーコード保護の安全網として有用ですが、仕様復元時には既存テストの期待値が逆向きに反転するという本質的なトレードオフを内包しています6。

「テストが全件パスしている」という事実は、コードが仕様を満たしていることを一切保証しません。テストコード自身が、甘い実装の追認になっていないかを疑う姿勢が不可欠です。

落とし穴:型制約(->where)の追加が招く例外メッセージの連鎖破壊

脱落していた検証を戻す際、もう一つの深刻な落とし穴となるのが「型制約の追加方法」です。型定義の仕組みを安易に拡張した結果、思わぬ副作用が生じてテストスイート全体が連鎖的に壊れる事態が発生します。

Types::Standard / Type::Tiny における型名変化のメカニズム

Perl の軽量オブジェクトシステム Moo や Moose エコシステムでは、Types::Standard を用いて属性の型制約を宣言します7。

今回の事例において、Request クラスで null id を弾くため、開発者は当初、既存のインスタンス検証に述語条件(where)を付与する以下のコードを追加しました。

# 当初試行された実装:
isa => (InstanceOf ['ValueObject::JSONRPC::Id'])->where(sub { defined $_->value }),

コード上は自然に見えますが、テストを実行すると別のテストファイル(t/value_object/jsonrpc/edgecases.t)の 64 行目と 72 行目でアサーション失敗が報告されました。

原因は、型制約ライブラリ Type::Tiny の内部仕様にあります8。->where メソッドを呼び出すと、既存の型制約を拡張するのではなく、内部的に親型制約をラップした「無名の子型制約(anonymous child type constraint)」が動的に生成されます。実測の例外メッセージでは、この無名型制約の表示名が __ANON__ になっていました。

その結果、型チェック違反時に送出される例外メッセージのフォーマットが根本から変化しました。従来の not isa ValueObject::JSONRPC::Id という型名がメッセージ中から消失し、"__ANON__" is a subtype of "InstanceOf["ValueObject::JSONRPC::Id"]" という無名サブタイプのエラーメッセージに置き換わってしまったのです。

既存テストスイート内に存在していた「不正な値を与えたときに、期待通りの型名エラーが出ること」を検査する正規表現アサーション(qr/not isa ValueObject::JSONRPC::Id/)がことごとくマッチしなくなり、テストが連鎖破壊されました。

対処法:型検証(assert_valid)と値検証(明示的 die)の分離

この連鎖破壊を回避するための解決策は、型制約自体を無名サブタイプ化するのをやめ、「型自体の検証」と「業務ルールに基づく値の検証」をコード上で明確に分離することです。

型制約ブロックの中で InstanceOf->assert_valid を明示的に呼び出して純粋な型チェックを行い、その直後に通常の値チェックを行って明示的に die(例外送出)させます。

以下は、lib/ValueObject/JSONRPC/Request.pm における失敗パターンと解決パターンの実装対比です。

# 目的: 型制約の無名化による例外メッセージ変化を防ぎ、後方互換性を保ちながら業務検証を追加する

# 失敗例(->where により型名が __ANON__ に変化し、既存の例外検証テストが破壊される):
# has id => (
#     is       => 'ro',
#     isa      => (InstanceOf ['ValueObject::JSONRPC::Id'])->where(sub { defined $_->value }),
#     required => 1,
# );

# 解決例(assert_valid による型検証と、明示的 die による値検証の分離):
has id => (
    is       => 'ro',
    isa      => sub {
        # 1. 純粋な型検証: 型エラー時は従来の InstanceOf の例外メッセージを維持
        InstanceOf(['ValueObject::JSONRPC::Id'])->assert_valid($_[0]);

        # 2. 業務ルール検証: null id の場合は明確な仕様メッセージで例外を送出
        die qq{JSON-RPC request id MUST NOT be null for a request}
            unless defined $_[0]->value;
    },
    required => 1,
);

テスト実行時の失敗出力ログ(一次計測ログより):

        # Failed test 'raw numeric id rejected'
        # at t/value_object/jsonrpc/edgecases.t line 64.
        # +-----------------------------+----+-----------------------------+
        # | GOT                         | OP | CHECK                       |
        # +-----------------------------+----+-----------------------------+
        # | Value "1" did not pass type | =~ | (?^:not isa ValueObject::JS |
        # |  constraint (in $args->{"id |    | ONRPC::Id)                  |
        # | "}) at t/value_object/jsonr |    |                             |
        # | pc/edgecases.t line 58\n    |    |                             |
        # |     "__ANON__" is a subtype |    |                             |
        # |  of "InstanceOf["ValueObjec |    |                             |
        # | t::JSONRPC::Id"]"\n         |    |                             |
        # |     Not a blessed reference |    |                             |
        # | \n                          |    |                             |
        # +-----------------------------+----+-----------------------------+

この分離を行うことで、不正なオブジェクト(数値や別クラス)が渡された場合は InstanceOf 本来の例外メッセージが送出され、既存の正規表現アサーションとの後方互換性が維持されます。その上で、オブジェクトの内部値が null の場合にのみ、意図した通りの仕様例外が送出されるようになります。

型制約に安易に述語を足すアプローチは、例外メッセージの契約を壊す危険を孕んでいます。影響範囲が読めない場合は、型チェックと値チェックを分離して局所化することが堅牢な防壁となります。

手順:機能を復元・追加する前にやるべき「前提の洗い出し(grep)」

消えた検証をコードに戻す際、いきなり実装を変更してテストを実行すると、どのテストが正当な理由で落ちたのか、あるいは予期せぬ退行(リグレッション)なのかを切り分けるのが困難になります。

実装に手を入れる前に、既存テストスイートの前提を能動的に洗い出す手順が必要です。

戻す前に「期待値が反転するテスト」を一覧化する grep 手順

既存テストが弱い挙動を前提にしていないかを特定するには、以下の 3 ステップで grep 走査を行います。

ステップ 1: 復活させる仕様に関連するキーワードをテストディレクトリから抽出する

これから戻そうとしている制約条件(例: null id や数値型引数)に関連する属性名やテスト記述を検索します。

# id 属性や null に関連するテスト行を抽出
grep -n "null\|id *=>" t/value_object/jsonrpc/request.t t/value_object/jsonrpc/notification.t

# 数値メソッド名に関連するテスト行を抽出
grep -n '"123"\|123\|numeric' t/value_object/jsonrpc/method_name.t

ステップ 2: ヒットしたテストのアサーション種別を確認する

抽出された行の周辺を精査し、本来例外が送出されるべきケースに対して ok lives や is exception, undef のように「成功すること」をアサーションしている箇所を特定します。

ステップ 3: 期待値反転の計画を立てる

特定した箇所を「改修後に例外送出(like dies)へと期待値が反転するテスト」として事前にリストアップします。また、該当するテストが存在しないエッジケース(前述の通知における id 指定など)があれば、新規追加テストとして設計に組み込みます。

比較表:仕様復元前後のテストアサーションの変化

今回のリファクタリング事例において、事前 grep 調査と仕様復元によって更新されたアサーションの対応関係は以下の通りです。

対象ファイル復元した仕様変更前のアサーション(甘い挙動の固定)変更後のアサーション(仕様復元による厳格化)
t/.../request.tRequest の null id 拒否ok lives { ... id => Id->new }like dies { ... }, qr/id MUST NOT be null/
t/.../method_name.t数値メソッド名の拒否ok lives { ... value => 1 }like dies { ... }, qr/did not pass type constraint/
t/.../notification.tNotification の id 拒否(テストなし・Moo が静かに引数を破棄)like dies { ... id => 1 }, qr/MUST NOT include an 'id'/

事前調査を行わずにコードを変更すると、テスト失敗時に「なぜ落ちたのか」の調査から始めることになり、AI エージェントに任せている場合は再びテストを弱めるプロンプトを与えてしまいがちです。

事前に反転対象を把握していれば、テストが赤くなった瞬間を「仕様が正しく戻った証拠」として歓迎できるようになります。

レビューの死角:push 済みブランチを upstream と比べると差分ゼロで空振りする

テストコードの修正を終え、コードをリモートリポジトリへ push していざプルリクエストのレビューを行う段階でも、見落としを招く重大な死角が存在します。Git の差分比較コマンドの選択ミスです。

なぜローカルレビューが空振りしたのか(比較基点の誤りと 3 ドットの公式仕様)

実務ログにおいて、フィーチャーブランチの変更をリモートに push した後、ローカル環境でコードレビュースキル(/code-review)を実行したところ、以下の予期せぬ結果が返されました。

レビュー対象の差分がなかったため、指摘は 0 件です。

  • git diff @{upstream}...HEAD は origin/feature/add_to_string に対して空でした。
  • git status --short も空で、未コミットの変更はありません。

重大な設計変更やバリデーション復元を行ったはずのブランチであるにもかかわらず、差分が 0 件として空振りしたのです。

原因は、比較対象にリモート追跡ブランチ @{upstream}(短縮形 @{u})9 を採用していたことにあります。すでにリモートへ変更を push し終えている場合、ローカルの HEAD と追跡ブランチ origin/feature/... のコミットハッシュは完全に一致します。先端同士を比較する限り、差分は 0 行になるのが当然です。

Git 公式仕様における 2 ドット記法と 3 ドット記法の定義は以下の通りです10。

  • git diff commit..commit(2 ドット記法): 2 つのエンドポイントの先端同士を直接比較します。
  • git diff commit...commit(3 ドット記法): 2 つのコミットの共通祖先(merge-base)と第 2 コミット(通常は HEAD)の間の差分を表示します。git diff A...B は git diff $(git merge-base A B) B と完全に等価です。

技術メディア DEV.to の解説記事(Zhihu Wu, 2026)が指摘するように、GitHub の Pull Request 画面で表示される差分は、共通祖先からの変更を表示する 3 ドット比較(base...head)を採用しています11。ローカル環境で 2 ドット比較を使うと、分岐後に base ブランチ側に入った無関係なコミットが混入します。一方、実務ログの空振りは 3 ドット(@{upstream}...HEAD)で起きており、原因はドットの数ではなく比較基点です。push 済みブランチの基点を追跡ブランチ @{upstream} にすると、2 ドットでも 3 ドットでも先端同士が一致しているため差分は消えます。

main...HEAD で再レビューしたら high 3 件が検出された教訓

この空振りに気づき、レビュー対象の起点をベースブランチとの共通祖先である main...HEAD(3 ドット比較)に明示指定して再実行したところ、以下の 3 件のクリティカルな不具合(重要度 high)が一挙に検出されました。

  • lib/ValueObject/JSONRPC/Notification.pm: Notification の構築ガード(BUILDARGS)が消失し、id => 1 を渡しても Moo が静かに破棄して通過する状態になっていた(通知に id を含めてはならない仕様違反)。
  • lib/ValueObject/JSONRPC/Request.pm: Request の null id チェックが脱落し、id => Id->new が正常終了する状態になっていた(リクエストの id に null を用いるべきでないという SHOULD 規定を、本モジュールの方針として拒否していた検証の脱落)。
  • lib/ValueObject/JSONRPC/Request.pm: jsonrpc 属性のデフォルト値(2.0)が削除されて required に変更されたため、既存の呼び出し元コードが Missing required arguments: jsonrpc で例外終了する公開 API 破壊を引き起こしていた。

もし upstream 比較の空振りをそのまま「指摘なし=品質良好」と誤認してマージしていれば、仕様違反と公開 API の破壊を抱えたままリリースするところでした。

レビュー時の比較基点を誤ると重大な不具合を見落とすため、push 済みブランチのレビューでは必ず main...HEAD(3ドット)の差分を確認しなければなりません。

以下は、リモート追跡ブランチの最新化とマージベース比較を確実に行うための標準的な確認コマンドです。

# 目的: push 済みブランチにおいて、マージベースからの全変更差分を確実にレビュー対象とする手順

# 1. リモートの最新情報を取得(base ブランチの先端を追従)
git fetch origin main

# 2. base との共通祖先(merge-base)からの差分を確認
# upstream 比較(@{u}...HEAD)ではなく、必ず base ブランチからの 3 ドット比較を行う
git diff origin/main...HEAD

レビューツールやローカルスクリプトを実行する際は、ブランチの push 状況にかかわらず、常に「PR がマージ先ブランチに対してどのような純差分をもたらすか」を再現する 3 ドット比較を明示指定することが鉄則です。

トレードオフと適用限界:すべてのテストを疑うべきか?

ここまで、既存テストが抱える甘い前提の危険性を述べてきましたが、開発現場において「あらゆる既存テストを最初から疑ってかかる」姿勢は現実的ではありません。

テストスイート全体を過剰に疑い、すべてのテストケースについて仕様書と照合し直す作業は、開発・リファクタリングの速度を著しく低下させます(YAGNI の原則に反します)。

疑うべきタイミングの判断基準(適用限界)

テストの前提を積極的に疑い、grep による洗い出しを適用すべきシチュエーションは、以下の 3 つの境界に限定されます。

  1. 歴史的経緯のあるコードベースで、過去のバリデーション脱落が疑われるとき: 大規模なフレームワーク移行や言語バージョンアップの際に、エッジケースの入力チェックが削られた疑いがある場合。
  2. AI コーディングエージェントに機能追加やリファクタリングを委任した直後: AI が「テストを通すこと」を最優先して、実装ではなくアサーション側を都合よく書き換えていないか(仕様ゲーミングの痕跡がないか)を確認する場合。
  3. 型制約や共通バリデーションロジックの厳格化を行うとき: 型ライブラリのバージョン更新や制約追加(->where 等)に伴い、例外メッセージの変化が既存の正規表現アサーションを破壊するリスクがある場合。

これら以外の通常の機能追加や、十分に設計・運用されているユニットテストにおいては、既存テストのグリーン状態を信頼して開発を進めるのが合理的です。

よくある質問(FAQ)

Q1: 既存コードの挙動を固定する仕様化テスト(Characterization Test)自体はアンチパターンなのですか?

A: いいえ、アンチパターンではありません。仕様書が存在しないレガシーコードを安全にリファクタリングする際、現在の振る舞いを記録する仕様化テストは唯一の防壁となります。問題なのは、「仕様化テストは現状の挙動を固定しただけであり、仕様の正しさを保証したわけではない」という認識を忘れ、バグを正当な仕様と誤認してしまうこと です。仕様化テストを書いた後に正しい仕様へ戻すフェーズでは、テストの反転が不可避であることを織り込んで運用する必要があります。

Q2: 型制約に ->where を使うことは常に禁止すべきですか?

A: 必ずしも全面禁止ではありません。例外オブジェクトの完全一致を検証していないテスト環境や、例外メッセージを外部契約として公開していない内部モジュールであれば、->where を用いた簡潔な型定義は有用です。ただし、例外メッセージに対する正規表現マッチ(like や throws_ok)を多用している既存リポジトリでは、型名の無名化(__ANON__)がアサーションを連鎖破壊するリスクを常に警戒する必要があります。

Q3: GitHub の Web UI で Pull Request を確認していれば、ローカルでの 3 ドット比較は不要ですか?

A: 人間による目視レビューのみであれば GitHub の PR 画面で十分です。しかし、ローカル環境で AI エージェントや CLI による自動レビュー(/code-review や linter、セキュリティスキャン)を走らせる場合、ツールに渡す比較範囲を正しく指定しないと差分ゼロで空振りし、自動検査が素通しになるリスクがあります。自動化ハーネスを正しく機能させるために、ローカルでの 3 ドット比較の理解が不可欠です。

まとめ

本稿で解説した、消えた検証の復元と壊れたテストへの対処における判断基準は以下の通りです。

  • テストが通っていることを過信しない: 全件パスは「テストが満足している」ことを示すに過ぎず、仕様が守られている証拠にはならない。既存テスト自身が甘い挙動を前提に書き換わっていないかを疑う。
  • 改修前に grep で反転対象を洗う: 不正値の拒否を戻す際は、事前にテストスイートを走査し、ok lives から like dies へと期待値が反転するテストをリストアップして計画に組み込む。
  • 型検証と値検証を分離する: 型制約に安易に述語(->where)を追加すると例外メッセージが変化し、既存の正規表現アサーションを連鎖破壊する。後方互換性が求められる箇所では型チェック(assert_valid)と業務チェック(明示的 die)を分離する。
  • レビューは常に 3 ドット比較(main...HEAD)で行う: push 済みブランチに対してリモート追跡ブランチ @{upstream} と比較すると差分ゼロで空振りする。マージベースからの全変更差分を正確に検証する。

レガシーコードのリファクタリング設計や、AI コーディングを活用した安全な開発プロセス・レビュー自動化の導入についてご検討の際は、ぜひ コンタクトフォーム よりお気軽にご相談ください。

Footnotes

  1. JSON-RPC 2.0 Specification JSON-RPC Working Group, 2010-03-26. Notificationのid禁止はSection 4.1、Requestのnull id忌避規定およびmethodがStringである規定はSection 4。 ↩ ↩2 ↩3 ↩4

  2. Moo - Minimalist Object Orientation (with Moose compatibility) haarg - Graham Knop, Version 2.005005, 2023-04-05. BUILDARGSによる引数正規化と未定義引数の扱い。 ↩

  3. Agentic Refactoring: An Empirical Study of AI Coding Agents Kosei Horikawa et al., arXiv:2511.04824, 2025-11-06, Section 4 & 5. AIリファクタリング事例15,451件中53.9%(8,324件)が非リファクタリングコミットに暗黙混入している実証データ。 ↩

  4. AIがテストを書き換える理由と、防ぐ3つの決めごと atma株式会社, note, 2026-09-25. AIがテストを通すためにアサーションを弱める実態(仕様変更9件、弱小化10件、シナリオ消失2件の計21件)と仕様ゲーミングの分析。 ↩ ↩2

  5. カバレッジ向上のみを目的にClaude Codeを投入してしまった人の末路(自戒を込めて) Accenture Japan (有志), Zenn, 2026-03-13. 既存実装からのテスト逆算生成が甘い挙動を固定化する「自作自演テスト」を生む失敗記録。 ↩

  6. AIに直させたら「別の場所」が壊れた、の正体 — リファクタリングを壊さない安全網(仕様化テスト)実践ガイド akira_papa_AI, Qiita, 2026-06-04. 現状の振る舞いを固定する仕様化テストの性質と、仕様復元時に期待値が反転するメカニズム。 ↩

  7. Types::Standard - bundled set of built-in types for Type::Tiny Toby Inkster, Version 2.004000, 2023-04-05. InstanceOfによる型制約の宣言仕様。 ↩

  8. Type::Tiny - tiny, yet Moo(se)-compatible type constraint Toby Inkster, Version 2.004000, 2023-04-05. whereによる無名子型制約生成と__ANON__表示名変化の仕様。 ↩

  9. Git - gitrevisions Documentation Git Community, git v2.56.0, SPECIFYING REVISIONS. @{upstream}(@{u})がリモート追跡ブランチを参照し、push済みではHEADと差分ゼロになる仕様。 ↩

  10. Git - git-diff Documentation Git Community, git v2.56.0, DESCRIPTION. 2ドット(先端比較)と3ドット(merge-baseとの比較)の公式仕様。 ↩

  11. Four Ways a Git Diff Lies to You (and How to Catch Each) Zhihu Wu, DEV Community, 2026-10-14, Section 1. 2ドット比較による他者コミット混入と、GitHub PRが3ドット(merge-base)を採用している理由。 ↩