はじめに
この記事では、手動での動作確認が済んでいるコードに、なぜさらに自動テストを追加するのかを整理します。
「バッチを流して確認した、動いている、だからテストは後でいい」という判断は自然です。
テストの価値は理解していても、動作確認済みのコードにわざわざ時間をかける理由が見えにくいのは当然だと思います。
この記事は、自動テストの一般的な価値を説くものではありません。
手動確認と自動テストが何を保証するのかの違いを整理し、後回しにするコストが実際にどこで発生するかを扱います。
手動確認が保証するのは「その瞬間」だけ
バッチを流して正常終了を確認した、ログを見て期待通りの動きをしていた。
この確認は正しいです。ただし、保証されるのはそのときの実行に限ります。
次の変更を加えたとき、その確認はリセットされます。
- 処理の順序を変えた
- 共通関数をリファクタリングした
- 依存している設定値を変えた
- 別の機能追加で共有ロジックに手を入れた
変更後に「前と同じ動作をしているか」を確認しようとすると、バッチを再度流す必要があります。
そのたびにデータの準備、実行、ログの目視確認というコストが発生します。
例として、こういうことが起きます。
バッチ処理で「未処理レコードだけを対象にする」という除外条件があったとします。
最初の確認では正しく動いていました。
しかし数週間後、共通のクエリ関数を別の機能向けに修正したとき、この除外条件が意図せず外れました。
バッチは正常終了しますが、対象外のレコードまで処理しています。
自動テストがなければこの変化に気づく仕組みがなく、本番で初めて発覚します。
自動テストはこの再確認コストをゼロに近づけます。
一度書けば、変更のたびに同じ検証を何度でも繰り返せます。
テストは動作確認のコードとして残り、チームの共有知になる
手動確認はその場で消えます。
「自分がどう確認したか」「どのケースを見たか」は他の人には伝わりません。
テストコードはリポジトリに残り、誰でも同じ手順で同じ確認を実行できます。
「このケースを必ず確認する」という暗黙知が、チームの共有知になります。
担当者が変わっても、時間が経っても、同じ観点で動作を検証し続けられます。
さらに、期待値が明確なテストは仕様の記録としても機能します。
「このインプットに対してこの結果を期待する」という形で書かれたテストは、コメントや設計書より正確です。
コメントや設計書は古くなりますが、テストは実際のコードと一緒に動き続けるため、乖離が生じにくいです。
書きにくければ設計の問題が見える
後からテストを追加しようとすると、書きにくいと感じることがあります。
この書きにくさは、テストの問題ではなく設計のシグナルです。
書きにくくなる主な原因は次のとおりです。
- 依存が多い関数になっている(DBアクセス・外部APIが直接呼ばれている)
- 複数の責務が1つの関数に混在している
- 検証したいロジックが副作用の中に埋まっている
「テストを書こうとしたら書けなかった」は、その関数が複数のことをしすぎているという発見です。
このタイミングで責務を分離すると、テストが書けるようになると同時に、コードの構造も改善されます。
どんなテストから書き始めるか
後からテストを追加するとき、すべてに網羅的に書こうとすると続きません。
優先順位を決めて始める方が現実的です。
次のような観点で優先度をつけると進めやすくなります。
- バグが出た箇所に回帰テストを書く。再発防止として最も費用対効果が高いです
- 手動確認のコストが重い処理から書く。バッチや複雑な条件分岐は再現に手間がかかるため、自動化の効果が大きいです
- 壊れやすい分岐から書く。除外条件や境界値のチェックは変更で崩れやすいため、優先して押さえます
全部書こうとせず、「ここが壊れたら困る」という箇所から始めるのが続けるコツです。
「今動いている」は「これからも壊れない」ではない
手動で動作確認したコードが壊れるのは、そのコードを直接触ったときだけではありません。
依存しているコードが変わったとき、設定が変わったとき、実行環境が変わったとき、気づかないうちに壊れます。
自動テストがあれば、壊れた瞬間に検知できます。
壊れたまま本番に届く前に止めることができます。
動いているコードにテストを足す理由は、今を守るためではなく、これからの変更を安全にするためです。