「非露出」保証のガードテストは、危険な値の発生源まで遡ってセンチネルを流す—手作り fake は意図しない値を構造上送れない
「あるフィールドはAPIレスポンスに絶対に含まれてはならない」という不変量を回帰テストで守るとき、ガードが本番コード(handler・DTO構築子)を実際に呼び出していても、その入力を作る層に手作り fake(usecaseモック等)が介在すると、ガードは実質的に空通しになることがある。
原因: 手作り fake はテスト作者が想定したフィールドだけを値付けして構築する。守りたい不変量の対象フィールドが、fake を作った時点ではまだDTOに存在しなかった(もしくは意図的に除外されていた)場合、fake はそのフィールドを永遠にゼロ値のまましか返さない。将来、実装側の構造体(usecaseのモデル型)に同名フィールドが追加され、DTOへ配線される退行が入っても、fake は自分の構築ロジックを自動で更新しないため、ガードは依然としてgreenのままになる。この失敗モードは、「本番データでは値が非ゼロだが、テストのfakeだけはゼロ値のまま」という最も危険な乖離を生む。
これは既知の「否定形ガードは本番コードを実呼びしなければ空通し」という教訓を一段進める。「本番コードを呼ぶこと」は必要条件であって十分条件ではない—その本番コードの入力を作るすぐ下の層が手作りの場合、その層はテスト作者が想定しなかった値を構造上送れない。
対策: 「絶対に漏れてはならない」種の不変量を守るガードは、値が本当に発生する場所(実 DB の行・外部 API の実レスポンス等)まで遡って配置する。具体的には、守りたいフィールドにセンチネル値(他と衝突しない、ひと目でそれと分かる文字列)を持つ行を実データストアへ投入し、実 repository → 実 usecase (もしくは同等の中間層)→ 実レスポンス構築を一本のパイプラインとして通し、最終出力にセンチネル値が現れないことを表明する。これはintegrationテスト相当のコストがかかるが、「絶対に漏らしてはいけない」種の不変量(PIIの非露出等)は、このコストを支払う価値がある。
併用: ユニットテスト層の許可リスト型ガード(レスポンスのキー集合完全一致など、別の失敗クラスを担当)は破棄せずに並行して残す。両者は直交する失敗クラス(契約外キーの出現 vs 特定フィールドの実データ経由の漏洩)を担う。
検証方法: 守りたいフィールドを実際に実経路(SELECT・中間モデル・シリアライズ層)へ配線する変異を作り、(a) センチネル境界テストが落ちることと、(b) 同じ変異でユニットテスト層の許可リストガードは通ってしまうこと、の両方を確認する。後者が実際に起きれば、ユニットテスト層だけでは不十分という識別反証になる。
適用範囲: データベース列だけでなく、外部APIの実レスポンスやファイルメタデータなど、「本番では値が乗るが、テストの手作り入力はutil的にゼロ値しか作らない」構造を持つすべてのレイヤー間境界に一般化できる。