用語集 · テストの基礎
テストダブル(Test Double)
英語: Test double
別名: test doubles, テストスタブ, test stub, モックオブジェクト
定義
ダミー、スタブ、フェイク、スパイ、モックなど、テスト内で本物の依存関係の代わりを務めるオブジェクトの総称で、テスト対象のコードを高速かつ予測可能に動かします。
テストダブルは、決済ゲートウェイ、データベース、時計、メールサービスなどの本物の依存関係を、テストが制御できるものに置き換えます。Gerard Meszaros によって広められたこの名前は、映画のスタントダブル(スタントマン)に由来します。テストダブルを使うとテストが高速かつ決定的になり、タイムアウトやカードの決済拒否のように、本物では再現しにくい状況をシミュレーションできます。
5 つの種類
| 種類 | 役割 | 例 |
|---|---|---|
| ダミー(Dummy) | 引数を埋めるためだけに渡され、使われることはない | メソッドが要求するが、このテストでは一切触れない null や空のロガー |
| スタブ(Stub) | 呼び出しに対してあらかじめ用意した答えを返す | rates.getRate("EUR") が常に 0.92 を返す |
| スパイ(Spy) | どのように呼び出されたかも記録するスタブ | send() が特定のアドレスで 1 回呼ばれたことを記録する |
| モック(Mock) | 期待値をあらかじめ設定しておき、テストがやり取りを検証する | gateway.charge(4999) がちょうど 1 回呼ばれたことを検証する |
| フェイク(Fake) | 動作するが簡略化された実装 | HashMap をバックエンドにしたインメモリのリポジトリ |
日常会話ではこれらすべてを「モック」と呼ぶことが多く、Mockito のようなライブラリはスタブ、スパイ、モックのいずれとしても振る舞えるオブジェクトを生成します。それでも区別を知っておくと役立ちます。特にスタブとモックの違いです。スタブはテスト対象のコードに入力を供給し(状態の検証)、モックはコードが依存先に送る出力を確認します(振る舞いの検証)。
例(JUnit 5 と Mockito)
@Test
void declinedCardDoesNotSendConfirmation() {
PaymentGateway gateway = mock(PaymentGateway.class);
EmailService email = mock(EmailService.class);
when(gateway.charge(4999)).thenReturn(PaymentResult.DECLINED); // stub
var checkout = new Checkout(gateway, email);
var result = checkout.placeOrder(4999);
assertEquals(OrderStatus.PAYMENT_FAILED, result.status());
verify(email, never()).sendConfirmation(any()); // mock-style verification
}
(org.mockito.Mockito、org.mockito.ArgumentMatchers、org.junit.jupiter.api.Assertions からの static import は省略しています。)
よくある間違い
- 何でもモックにする。 内部の呼び出しをすべて検証するテストはリファクタリングのたびに壊れ、本物の結合が壊れていても合格してしまうことがあります。モックはアーキテクチャの境界で使い、その内側では本物のオブジェクトやフェイクを優先しましょう。
- 現実から乖離したテストダブル。 本物のサービスでは数か月前に名前が変わったフィールドを返し続けるスタブ化された API は、誤った安心感を与えます。契約テストと少数の本物の結合テストが、テストダブルを誠実に保ちます。
- 自分が所有していない型をモックする。 サードパーティのクライアントは自前のインターフェースでラップし、そのインターフェースのほうをテストダブルにしましょう。