定義
テストオラクル(test oracle) とは、ある入力に対する実行結果が正しいか誤っているかを判定する基準である。テストコードそのものではなく、テストコードが参照する正解の出どころを指す。
テストは二つの部分からなる。プログラムを特定の状態に導く入力生成と、その結果が正しいかを決める判定である。前者は自動化しやすいが後者は難しく、この困難をソフトウェア工学ではオラクル問題(oracle problem) と呼ぶ。
なぜ必要か
オラクルを明示しないと、非常によくある事故が起きる。先に実装を作り、その実装が実際に出した値をそのまま期待値に書き込んでしまうのだ。
// 実装を一度動かして出た値をそのまま貼り付けたテスト
expect(formatDiscount(1000, 0.1)).toBe("900원");
このテストは常に通り、そして何も検証していない。丸め規則が誤っていても通貨表記が誤っていても、実装がそう動くから期待値もそう書かれただけである。結果として、リグレッションは捕まえるが最初の誤りは決して捕まえられないテストになる。
コード生成ツールに任せる場面ではこの問題がさらに大きくなる。ツールは既存コード・既存テスト・現在の画面挙動をすべて読めるため、「今こう動くのだから、これが要件だろう」と推論しやすい。だからこそ、何が正解かを決める権限をどこに置くかを先に固定する必要がある。
仕組み
オラクルは正解の入手先によって分類できる。
| 種類 | 正解の出どころ | 限界 |
|---|---|---|
| 仕様オラクル | 承認された要件・受入基準の文書 | 文書の位置とバージョンを固定しないと基準が再び揺れる |
| 人間オラクル | 人が結果を見て判定する | 遅く再現しにくい。判定結果を文章で残さないと自動化に進めない |
| 派生オラクル | 旧バージョンの出力、参照実装、運用ログ | 旧バージョンのバグを正解に昇格させる危険 |
| 暗黙オラクル | クラッシュ、未処理例外、デッドロックなど誰が見ても誤りの事象 | ドメインの正確性は判定できない |
| メタモルフィックオラクル | 入力同士の関係から出力同士の関係を導く | 正解値そのものが計算できない領域でのみ必要 |
正解値を計算できない領域では、メタモルフィック関係が現実的な代替になる。
// 正しい並び順を知らなくても検証できる関係
const once = sortByScore(items);
const twice = sortByScore(shuffle(once));
expect(twice).toEqual(once); // 入力順序が結果を変えてはならない
実務での適用
要点は、判定基準をテストより先に文章で書き、各項目に出典を付けることである。
| # | 状況(Given) | 操作(When) | 結果(Then) | 禁止(Never) | 出典 |
| --- | ------------- | -------------- | ------------------ | -------------------- | ---------------- |
| 1 | 一覧が空 | ページ進入 | 空状態の案内を表示 | スピナーが回り続ける | 仕様書 3.2 節 |
| 2 | 要求が5秒超過 | 自動キャンセル | 再試行ボタン表示 | 無限待機 | 利用者の明示回答 |
出典を付けられない行は空欄のままにして確認を求める。慣例で埋めた瞬間、その行は検証ではなく推測になる。
作業が長期化する場合は、この表をファイルとして保存し内容ハッシュを記録しておく。途中で基準がずれれば、人より先にツールが気づく。
shasum -a 256 docs/acceptance/checkout.md
トレードオフ
基準を先に文書化する作業は序盤の速度を確実に落とす。要件がまだ探索段階なら、表を埋めようとすること自体が無駄になりうる。
得られるものは二つある。期待値が実装から独立するため、実装をまるごと書き直してもテストをそのまま使える。そして議論になったとき、「コードがこうだから」ではなく文書の一行を指して決着できる。
暗黙オラクル(クラッシュしない)はコストがほぼゼロだがドメイン誤りを全く捕まえられず、仕様オラクルはコストが高いが検出力が最も高い。リスクに応じて混ぜるのが現実的である。
使うべきでない場合
- プロトタイプの探索段階。 何を作るか自体が未定なら、基準の固定はむしろ妨げになる。
- 正解が本質的に複数ある領域。 レイアウトの微調整、文言、推薦順序などは単一の期待値に固定すると正常な変更のたびにテストが壊れる。不変条件(重なりがない、件数が保存される)や視覚回帰で扱う。
- 外部システムが正解を決める場合。 サードパーティの応答をそのまま期待値に固定すると、相手の変更でこちらのテストが壊れる。契約の形だけを検証する。
よくある間違い
- 実装を動かして期待値をコピーする。 最も多く、最も静かな失敗。判別法は単純で、「実装を消して書き直しても、この期待値をそのまま使えるか?」と問えばよい。
- テストが赤くなると反射的にテストを直す。 要件と実装がずれた signal かもしれない。まずどちらが誤りかを確かめる。
- 判定基準が人の頭の中にしかない。 その人が不在になるとチーム全体が正解を知らない状態になる。
- 未決の行を慣例で埋める。 「普通はこうするから」で埋めた行は、後で要件違反として戻ってくる。
- オラクルとハーネスを混同する。 locator の不備による失敗は判定基準の問題ではなく道具の問題である。原因を分類しないと見当違いの箇所を直すことになる。
関連概念
- mutation-testing — その判定が実際に機能する(誤った実装を捕まえる)かを逆から検証する手法
- playwright-flaky-vs-failed-triage — 判定結果そのものを信頼できるかという別軸の問題