第053話 「測定器が壊れている」せいで、直っていたはずの合格が12件も眠っていた話
いつ困る?
テストが失敗するのを見て「まあ既知の問題だから」とスルーする運用、ありますよね。「直るまで失敗扱いにしておく」というマーキングで先に進むのは、現場ではよくある合理化です。
でもこの合理化、「失敗扱いにする条件の作り方」を間違えると、直ったテストまで黙って眠り続けるという落とし穴を抱えています。壊れた計器で測り続けているので、「なおった」という出来事が永遠に観測されないのです。
何が起こったか
あるプロジェクトの構造的な負債を棚卸ししたとき、3件の課題が残っていました。コードの層が逆に依存している問題、テストの「既知の失敗」指定が肥大化している問題、そして大量のログやダンプがリポジトリに溜まっている問題です。
片付けていくうちに、2件目の実態が分かってきました。「テスト名に失敗パターンの一部の文字が含まれていたら、自動で『既知の失敗』扱いにする」という仕組みが入っていたのですが、部分一致が広すぎて、37個のテストに25パターンが適用され、うち12個は全く無関係なテストが黙って止められていたのです。
さらに恐ろしいのは「直ったこと」さえ分からない構造だったこと。「既知の失敗」指定は「成功したら失敗」と報告しない設定で動いていたので、12個のうちどんなにテストを直しても、合格は報われず、眠り続けました。
きれいにするつもりでモデルの置き場所を引っ越したときも、引っ越し先を前提にしたテストが97個一気に失敗しました。狙いを指す「住所」が変わったのに、テストの側が旧住所を指し続けていたのです。147箇所を新住所へ書き換えて、合格数が改修前と一字一句同じになることを確認してやっと保存できました。
もう一つ、頭を悩ませたのが「手元では緑なのに、CI だけ同じファイルが文法検査で赤い」現象。ファイルは一文字も変えていません。真因は、検査ツールが「これは自作コードか外部ライブラリか」を環境の状態から推測していたこと。手元には開発用にインストール済みで自作扱い、CI には入っていないので外部扱い。推測の根拠が環境ごとに違うので、同じ判定が環境でひっくり返っていたのです。最後は「これは自作だ」と明示的に宣言して、両環境を一致させました。
何が変わったか
- 一致条件を「部分一致」から「完全一致」へ変え、さらに「成功したら失敗として報告する」設定を有効化。本当にまだ壊れている25個だけを実測で確定してから残りを解放したので、CI を赤くせずに12個を回収できました
- モデル層を独立した場所へ抽出し、上位層が「モデルを使うためだけに」下の画面層へ依存していた構造を解消。層間の依存は28本から4本へ、循環も6組から5組へ減りました
- 生成物の溜め込みを追跡対象から外し、管理ファイル数を1,626から1,549へ削減
- 最終的にCI全ジョブが成功。テスト全体の合格数は改修前と全数値一致で、直したはずの6個の「復活合格」も計測に顔を出しました
教訓は2つ。「なおったことが分からない仕組みは、直す意味を半分消す」——既知の失敗を横流しする運用には、復活を検出する緊張が必要です。そして「ツールが推測している箇所は、環境で答えが変わる」——同じ判定が手元とCIで食い違うとき、コードではなく推測の根拠を疑う。
<details><summary>▶ 技術的にどうなってるか</summary>
- 対象: NexusCore(Flask/Python・テスト5,271件・CI 4ジョブ)
- 層逆流の解消:
src/nexuscore/models/を新設しDBモデルをwebappから抽出(62ファイル)。AST依存スキャンでapi→webapp28→4エッジ・integration→webapp6→0・循環6→5組(integration↔webapp解消) - xfail誤爆の実態:
tests/conftest.pyの自動適用がpattern in test_name(部分文字列マッチ)+strict=False固定で、25パターン→37テスト適用→12件誤爆 - 案C採用: 完全一致化+テスト単位のstrict指定+
xfail_strict=true。NEXUSCORE_DISABLE_AUTO_XFAIL=1でフルスイート実行(25 failed, 5272 passed)し「真に失敗する25件」を実測確定してから解放。フレークのみ明示的にstrict=False - 検証: pytest改修前と全数値一致(
5271 passed)・mypy 274ファイル Success・CI実測カバレッジ86.43%(出典run ID付きでREADME更新) - lint環境差:
pyproject.tomlにisort first-party指定が無くruffが解決可否から推定していた →known-first-party = ["nexuscore"]明示で両環境一致(src設定だけでは不十分・2回仮説否定の実測あり) - 遺物: logs/ 75ファイル+構造ダンプ43,766行を
git rm --cachedで追跡解除(1,626→1,549) - 反証条件も記録済み: 依存グラフで
api→webappが20超へ戻ったら新規コードでシム経由参照が復活していると疑う
</details>