👥

何でも"モック"と呼んでない?mock, stub, fake, dummy, spyの違い

2024/01/29に公開

テストコードを書くとき、本当のオブジェクトの代わりに使うものは何でも"モック"と呼んでいましたが、実はそうではないことを教わりました。

ユビキタス言語などもそうですが、メンバーとの認識のズレを発生させないためには正しい用語を使うことは重要です。という訳で Martin Fowler の Mocks Aren't Stubs の用語の違いの章を読んでみました。

結論

モックを「本物のオブジェクトの代わりに置き換わって使われるオブジェクトの総称」みたいな意味合いで使っている場合は、それは正しくは テストダブル と呼びます。
※ダブルを辞書で調べると「代役」という意味に当たる。「2」のイメージから連想できるね。

そしてテストダブルは以下の種類に分かれます。

Mocks:モック

モックは呼び出され方を事前定義することができるオブジェクトで、関数が呼び出される回数や呼び出されたときの引数の値といった、期待値・期待動作を定義し、それをテストできるテストダブル。
モックを使うことでテスト対象オブジェクトの「振る舞い(どのようにオブジェクト(テスト時点ではモック)を呼び出しているか)」をテストすることができる。

objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

Stubs:スタブ

スタブはテスト中の呼び出しに対して返す値が事前準備されているテストダブル。

Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.

Spies:スパイ

スパイはスタブの機能に加えて、どのように自身が呼び出されたかに基づいて何か追加情報(おそらく呼び出し回数や引数)を保持するテストダブル。例えば、本当はメールを送る処理だが、メールを送ったふりをしつつ何回メール送信が呼ばれたかの情報も保持し、それをテストするなど。

Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

Fake:フェイク

フェイクはテストのためのワークアラウンドの実装を持っているテストダブル。例えば、実際はDBとやり取りを行う処理だが代わりに内部にHashMapなどを持っていてインメモリDBのように振る舞うなど。

Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an in memory database is a good example).

Dummy:ダミー

ダミーは実引数として渡される何もしない。ただ引数を埋めるために使われるテストダブル。

Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists.

正直なところ、似ているものがあってややこしいという印象を持ちました。なので t_wada さんのツイートからも参照されている goyoki さんの記事を見てみました。

Mocks と Spies の違い

なおMock ObjectとTest Spyは両方とも間接出力を検証するためのTest Doubleです。ただ「Mock ObjectはMock Object内で間接出力結果を評価する」のに対し、「Test Spyは間接出力を保持するだけで、間接出力結果の評価は後からテストコード上で行う」という違いがあります。

この文章を踏まえた上で、さらに Mockito の @Spy の例を見ると完全に理解できました。

@Spy
List<String> spyList = new ArrayList<String>();

@Test
void givenUsingSpyAnnotation_whenSpyingOnList_thenCorrect() {
    spyList.add("one");
    spyList.add("two");

    verify(spyList).add("one");
    verify(spyList).add("two");

    assertThat(aSpyList).hasSize(2);
}

add()が呼ばれた時にその引数が"one"だったか」のテストは、呼び出しに関する情報をスパイの中で保持していないとできないテストです。スパイは情報を保持している、というのが非常によく伝わりました。

ただ、スパイは定義しなかった関数に関してはテスト対象オブジェクトの関数をそのまま呼び出す、などの機能もありましたので、上記の5つのテストダブルの違いを押さえつつも、利用するテストライブラリの動作を確認したほうが良さそうです。

Stabs と Fake の違い

テストの範囲内で本物と同じように動作するTest DoubleはFake Object。

スタブは返す値が事前に定義されていますが、フェイクの場合は簡素化されつつも本物と同じように動作をする、という違いがありそうです。

本物と同じように動作をする、というのは以下の記事を見ると分かりやすかったです。
https://www.baeldung.com/cs/faking-mocking-stubbing

フェイクはDBの部分が HashMap<String, User> に置き換わっていますが、呼び出し元のテスト対象クラスからするとDBのように見えていて、どのような User をフェイクに渡してもうまく動きます。

スタブは事前定義された値しか返さないので、スタブ化したオブジェクトがどんな処理を行うかには一切興味がなく、特定の値を返す動きだけをして欲しいというわけです。そのため、スタブではなくフェイクを使うということは、フェイクに値を返す以上の何らかを期待していることが多いと思います。

また、スタブはライブラリの機能を使うことができるので関数の置き換えコストが低いのに対し、フェイクは置き換えのためにインターフェースをすべて満たすように独自実装したうえでオブジェクトを生成しないといけないので、実装コストが大きくなります。

どれを使うべきなのか?

すみません見出しを作っていますけど書けないです。
将来書くぞという意味を込めて置いておきます。

終わりに

今日を境に語彙力が5倍になりました。(モック -> モック、スタブ、スパイ、フェイク、ダミー)

用語を正しく使うのは意思疎通をスムーズに行うために重要なので使い分けていこうと思いつつ、テストを書く際にどれを使えば良いのかについての知見を次はどんどん貯めていきたいと思います。

Discussion