はじめに
この記事では、バッチ処理のテストをどこに厚く書くべきかを整理します。
結論はシンプルです。
通しテストは最小限にして、純粋関数と結合テストに厚みを持たせる方が運用しやすいです。
特に次のような現場を想定しています。
- バッチを最後まで動かすテストが重い
- テストが落ちても原因の切り分けに時間がかかる
- 通しテストに確認項目を詰め込んでいて保守がつらい
- 計算ロジックや判定ロジックは単体で十分に検証できるはず
「通しテストは不要」という話ではありません。
通しテストの役割を絞り、純粋関数と結合テストに厚みを持たせる方が、全体として品質も保守性も上がりやすい、という話です。
通しテストを厚くすると何が起きるか
バッチ全体を通すテストは、一見すると安心感があります。
動いているところを目で見るような感覚があり、つい確認項目を増やしたくなります。
ただし、通しテストに詰め込みすぎると次のことが起きます。
実行時間が長くなる
通しテストはDBアクセス、ファイルI/O、外部API呼び出しなど、重い処理をすべて含みます。
確認項目が増えるほど、テストの実行時間が伸びます。
数十件のデータを用意して、バッチを最初から最後まで通す。
このテストが10本、20本と増えると、CI上でも手元でも実行するのが重くなります。
結果として、テストを頻繁に回さなくなります。
バッチの挙動変化に振り回される
通しテストは、バッチが通る経路全体の挙動に依存します。
処理の順序、取得するデータ、出力形式など、あらゆる変化が影響します。
例えば、通知文面のフォーマットを少し変えたとします。
それだけで通しテストが落ちます。
文面の変更が意図的なものであっても、通しテストは「想定外の変化」として拾います。
これが続くと、「テストが落ちた=バグ」ではなく「テストが落ちた=またフォーマット変更か」という感覚になります。
テストへの信頼が薄れていきます。
失敗しても原因を特定しにくい
通しテストは複数の処理をつないでいます。
失敗したとき、原因がどの層にあるかをすぐに判断できません。
- SQLの取得条件が変わったのか
- 集計ロジックが崩れたのか
- 保存先の構造が変わったのか
- 通知処理が変わったのか
毎回これを調べることになります。
通しテストが多いほど、障害調査の入口が増えます。
結合テストは必要だが、通しとは分けて考える
DB接続やファイルアクセスを含むテストを書くこと自体は問題ありません。
結合テストは、純粋関数では拾えない不具合を検出するために必要です。
次のような不具合は、結合テストで見るべきです。
-
WHERE条件が1つ抜けた -
LEFT JOINのつもりがINNER JOINになっていた - 論理削除データを誤って拾った
- ユニーク制約違反時の扱いが崩れた
- transactionのロールバックが正しく動いていない
ただし、これらはバッチ全体を最後まで通さなくても検証できます。
対象のRepositoryやQueryに絞った結合テストを書けば十分です。
通しテストでまとめて確認しようとすると、失敗したときに「どの層の問題か」が見えにくくなります。
結合テストは責務を絞って書く方が、失敗時の原因が明確になります。
純粋関数のテストを最も厚くする
最初に厚くすべきなのは、純粋関数のテストです。
純粋関数として切り出しやすいのは、例えば次のような処理です。
- 対象判定
- ステータス遷移判定
- 金額計算・丸め
- 集計処理
- CSV行の変換
- 通知文面の組み立て
この種の処理は、入力と出力の関係をそのまま検証できます。
純粋関数のテストには次の利点があります。
- 実行が速い
- 前提データが少なくて済む
- 失敗箇所がすぐ分かる
- 境界値や分岐を細かく潰しやすい
- リファクタリングしても壊れにくい
バッチの本質的な複雑さは、配線ではなく業務ルールにあることが多いです。
その業務ルールを純粋関数に寄せておけば、速く安全に厚くテストできます。
ただし、すべてのロジックを純粋関数に切り出せるわけではありません。
取得条件がSQLに強く埋め込まれる処理や、時刻・外部API・transaction境界に意味がある処理は、結合テスト側で見る方が自然です。
通しテストの役割を絞る
バッチ全体の通しテストに残すのは、次の確認だけで十分なことが多いです。
- エントリーポイントから最後まで落ちずに流れるか
- 主要な配線がつながっているか
- 代表的な1ケースで期待した結果になるか
逆に、通しテストで見すぎない方がよいものは次のとおりです。
- 分岐条件の全列挙
- 境界値の網羅
- 文面の細部
- 集計ロジックの全パターン
- 例外条件の細かな派生ケース
これらは、より小さいテストで確認した方が速くて正確です。
通しテストは「最終確認」として使うものです。
主戦場にすると、重くて壊れやすいテスト群になっていきます。
テストの配分イメージ
感覚としては、次のような配分が扱いやすいです。
- 純粋関数のテスト: 7割
- DBを含む結合テスト: 2割
- バッチ全体の通しテスト: 1割
厳密な比率ではありませんが、通しテストが最も多い構成は見直す価値があります。
通しテストが増えるほど、安心感より保守コストが先に来るようになります。
失敗時に原因を絞りやすくなる
この分け方の大きな利点は、失敗時の原因特定がしやすくなることです。
不具合が起きたとき、次のように切り分けられます。
- 純粋関数のテストが落ちた → 業務ルールの問題
- 結合テストが落ちた → SQLや永続化まわりの問題
- 通しテストだけ落ちた → 配線や起動経路の問題
通しテストだけを厚くしていると、毎回「どこが悪いのか」から調べることになります。
テスト層を分けることで、調査の入口が絞られます。
実装側で先にやること
通しテストを薄くしたいなら、実装側も整える必要があります。
特に有効なのは次の2点です。
- 判定や変換を純粋関数として切り出す
- バッチ本体を「取得・判定・保存・通知の組み合わせ」として分解する
バッチ本体が大きな手続きのままだと、ロジックを単独でテストできず、通しテストに頼らざるを得なくなります。
ロジックを分解しておけば、テストも自然に分散できます。
まとめ
バッチのテストで厚く書くべきなのは、最後まで通すテストではありません。
通しテストを厚くすると次のことが起きます。
- 実行時間が長くなり、テストを回さなくなる
- バッチの細部の変化に振り回されて、テストへの信頼が薄れる
- 失敗しても原因の特定に時間がかかる
結合テストは必要ですが、通しではなく責務を絞った形で書く方が原因を特定しやすいです。
厚くすべきなのは純粋関数のテストです。
業務ルールを純粋関数に寄せて、速く・壊れにくく・細かく検証できるテストを増やす。
通しテストはその上の最終確認として、役割を絞って置く。
この順序の方が、実務では長く回るテスト構成になりやすいです。