この記事は、連載「確かめない技術」の第 5 回です。うまくいかない症状と、その対処を一つずつ並べています。各回はファイルやスクリプトを一つ置けば完結します。その仕組みがなぜ要るのかは、置いたあとに解説を読めば分かります。連載の全体像と各回の一覧は序論にあります。

今回は、テストを間引くことの話です。間引くというのは、重いテストを普段の実行から外しておいて、その領域を触った日にだけ走らせることです。私はある日、「テストは通っています」という報告を受け取りました。そのとき走らなかったテストがどれだけあるかは、報告のどこにも書かれていませんでした。速くする工夫は死角を隠す —— 間引いたぶんは、報告からも消えます。

1 作目「読まない技術」の第 1 回で、テストの出力を、件数と失敗だけの数行の要約にしました。今回は、その要約に、走らせていないものを書き足します。

まず一つだけ、測ってみてください。

テスト全量は何秒かかりますか。

そして AI とのセッション中、その全量は何回走っているでしょうか。

私の環境は全量 277 秒・13,950 件です。

この中には、実画像を書き出す画像処理、閾値まで実リクエストを繰り返すレート制限、26 万行のデータを投入する精度検証——触っていない日に走らせる価値のない重いテストが大量に入っています。だから間引くこと自体は正しい判断で、AI に任せても「関連するテストだけ走らせますね」と AI は自分から間引き始めます。

問題は間引いた瞬間に生まれます。走らせなかったものの存在が、報告から消えるのです。AI の報告は「テストは通っています」。何が走っていないかは言いません。読む側は合格を「全部が通った」と読みます。速くする工夫はそのまま、見ていない範囲を隠す工夫になります。

理解したことは一文にできます。

速くする工夫は「何を見ていないか」を隠す。走らせない代わりに、走らせていないものを毎回目の前に置く。

CC BY 4.0

仕組み: 間引きと引き換えに、間引いた一覧を常時出す

二つ合わせて一つの仕組みです。まず重いテストに領域名を付け、その領域名が指定されたときだけ走るようにします。

# 重いテストの側(例: 決済フローの E2E)
setup do
  skip "runs only with E2E=payment" unless ENV["E2E"].to_s.split(",").include?("payment")
end

そして 1 作目 第 1 回のラッパーが、要約の最後に走らせていない領域の一覧を毎回印字します。テストコードから領域名を拾って、今回指定されたものと突き合わせるだけです。

# scripts/test.sh の要約の最後に足す(短くしたもの: 単一指定を想定。手元のコードは複数指定と領域の説明に対応)
echo "[E2E] areas run: ${E2E:-none}"
echo "[E2E] the following areas were not run. Re-run with E2E='<area>' only when you touched them:"
grep -rhoE 'E2E=[a-z_]+' test/ | sed 's/E2E=//' | sort -u | grep -vx "${E2E:-__none__}" | sed 's/^/  - /'

私の環境の出力はこうなります。

[E2E] areas run: payment
[E2E] the following areas were not run. Re-run with E2E='<area>' only when you touched them:
  - thumbnail generation (writes real images)
  - rate limiting (repeats real requests up to the threshold)
  - score posting → propagation to aggregates
  - batch idempotency (run twice, same values)
  …

私の環境では、この「実行していません」リストに 22 件が並びます。毎回です。うるさく感じるかもしれませんが、この 22 行がこの仕組みの本体です。間引いた事実は、間引いた本人(AI)の報告には現れません。だから、ツールの出力に必ず出す形にして、隠せなくします。1 作目 第 1 回で「件数の行だけは削らない」と書いたのと同じ思想を、大きくしたものです。

毎回 22 行は、トークンだけ見れば無駄です。それでも、指示ファイルに「E2E の走らせ忘れに注意」と書き足すのとは効き方が違います。ルールは、覚えた上で全部の判断に効かせ続けることを求めます——序論で見たとおり、だから足すほど薄まる。この一覧が求めるのは照合だけです。触った領域が載っていなければ、AI も人も読み飛ばして終わり。注意が要るのは、条件が立った行だけ。常駐させてよいのは、覚えなくていいものだけです。

効果は速度に出ます。普段の実行は重い領域を全部抜いて回り、決済を触った日だけ E2E=payment を付けて流す。全量 277 秒を毎回待つ生活には戻れません。

CC BY 4.0 はここまで

走らせなくなったもの

安心のための全量実行です。「何もしていないけど、全部流して合格を見ておくか」という儀式が消えました。安心の根拠は合格の総数から、「走らせていない 22 件のどれにも今日は触っていない」という一覧の読み合わせに変わりました。

そしてこの読み合わせは、リストが目の前に毎回出るので数秒で終わります。

CC BY 4.0

注意: 「触ったのに走らせ忘れる」は残る

このリストは走らせ忘れを見えるようにしますが、防ぎはしません。決済を触った日に AI が E2E=payment を付け忘れることはあり得ます。

私の環境では最後の網として、pre-commit hook に「直した領域の E2E を走らせたか」の照合を入れています。1 作目 第 4 回でやった「間違えると高くつく入口にガードレールを置く」の一例です。リストで見えるようにして、入口で拾う。二重に守れば、走らせ忘れが静かに通り抜けることはなくなります。

検証手順: 三点を一度ずつ

前提

  • 重いテストを 1 つ以上選んで領域名を付け、その領域名が指定されたときだけ走る形にしてあること
  • 1 作目 第 1 回のラッパーの要約の末尾に、走らせていない領域の一覧を出す処理を足し終えていること

所要時間: 10 分

手順

  1. あなたが、普段どおりに(領域名を指定せずに)テストを走らせます
  2. あなたが、領域名を指定して走らせます(E2E=<領域名> のように)

合格条件(すべて満たすこと)

  • 手順 1 で、要約の末尾の一覧に、その領域名が載っている
  • 手順 2 で、その重いテストが実際に走る(実行数が手順 1 より増えます)
  • 手順 2 で、末尾の一覧から、その領域名が消えている

合格しなかったとき

  • 一覧が空のまま変わらない —— 要約が、領域名をテストコードから拾えていません。一覧の中身は「指定した領域を引いた残り」なので、空の一覧は「全部走った」ではなく「拾えていない」の可能性のほうが高いと思ってください

後始末

  • 要りません。この検証は何も壊しません

確かめたのは「走らせていないものが見えること」だけです。見えることと、走らせ忘れないことは別です。触った領域を指定し忘れる事故は、この一覧では防げません(防ぐ側はコミットの入口に置きます)。


CC BY 4.0 はここまで

次回は最終回、「その場で決めない技術」です。