nikkie-ftnextの日記

イベントレポートや読書メモを発信

PHPUnitのドキュメントを機にxUnit Test Patternsのサイトを確認し、Test Double・Stub・Mockを整理 〜広義のモックと狭義のモック〜

はじめに

「頑張れ!」ってきっと 愛してるって言葉♪ nikkieです

2/11のPHPカンファレンス2024登壇1準備より1ネタ。
PHPUnitのドキュメントのTest Doubleを読んだところ、モックについて気づきがありました。

目次

PHPUnitのドキュメント「6. Test Doubles」

PHPUnitは11がリリースされましたが、今回は10.5に当たっています

日本語でも読めるようです2(ただし2023年1月にアーカイブされており、差分もあると思います)

このドキュメントは『xUnit Test Patterns』(xUTP)をたびたび参照しています。

この機にxUTPのサイトの関連箇所も確認しました。

『xUnit Test Patterns』で整理された語彙が前提

一読して思い出したのは『テスト駆動開発』の付録C。
訳者のt-wadaさんによる解説です🦁

語彙の整理により、xUTP出版後は「広義のMock Object」と「狭義のMock Object」を分けて議論できるようになり、後続のモックライブラリの機能名も明確になりました。(Kindle版 p.493)

http://xunitpatterns.com/Types%20Of%20Test%20Doubles.gif の図を引用します

  • Test Doublesが、広義のMock Object
  • Test Doublesの中の1つ Mockは、狭義のMock Object

テスト駆動開発』にも同様の図があり、私はこれをふーんと眺めていたのですが、今回PHPUnitのドキュメントとxUTPのサイトを読んでいったことで理解が深まりました。

注:xUTPはテストを4つのフェーズで見る

以降でTest Double・Stub・Mockを表す図を引用しますが、図を見る際に念頭に置くのがよさそうなのが、xUTPはテストを4つのフェーズで見ていることです。

  • Setup
  • Exercise
  • Verify
  • Teardown

私は3A(Arrange-Act-Assert)に親しんでいる3のですが、どうやら以下の対応のようです。
ref: xUTPのサイトを検索して見つけたスライド
http://xunitpatterns.com/~gerard/AgileIndia2018-Test-Refactoring-Exercise-SlidesV3.pdf (slide=22)

  • Setup = Arrange
  • Exercise = Act
  • Verify = Assert
  • Teardown4

なおSUTはSystem Under Testの略で、テスト対象のことです。

Test Doubles(広義のモック)

doubleを辞書で引くと「代役、替え玉」という意味がありました(ウィズダム英和辞典)。
なるほど、テスト用の代役ということなんですね。

xUTPのサイトも確認すると

We replace a component on which the SUT depends with a "test-specific equivalent."

http://xunitpatterns.com/Test%20Double.gif の図を引用

この代役(double)として、stubやmockという具体があります(後述)。

PHPUnit(バージョン10)では、createStubcreateMockで、Test Doublesを用意できます。

Test Stubs

https://docs.phpunit.de/en/10.5/test-doubles.html#test-stubs

図がすごく分かりやすいと思っていて、
http://xunitpatterns.com/Test%20Stub.gif

  • SetupでStubを作成
  • ExerciseでSUTはStubの返り値を受け取る

We replace a real object with a test-specific object that feeds the desired indirect inputs into the system under test. (xUTPサイトより)

StubはSUTが間接的に入力する代役というわけですね。

ちょうぜつ本より

スタブというのは、相手が正しく動くかどうかに関係なく、「仮に期待どおりの答えを得たとしたら」と仮定するダミーオブジェクトです。非常に重い通信をともなうものの場合や、実際に動いてしまうとまずいものの場合によく利用されます。(Kindle版 p.225)

実際に動いてしまうとまずいものの代役であり、SUTにはStubの返り値が重要(SUTへの入力となるので)。
たしかにこういうテストコードを書いたことがありますね、あれはStubだったんだな〜

Mock Objects(狭義のモック)

https://docs.phpunit.de/en/10.5/test-doubles.html#mock-objects

こちらも図が(Stubと比較するとなお)分かりやすいと思いました。
http://xunitpatterns.com/Mock%20Object.gif

  • SetupでMockを作成
  • ExerciseでMockにはSUTの出力が渡る
    • Mockは設定された期待値を返す
  • VerifyでMockの呼び出され方を検証

Replace an object the system under test (SUT) depends on with a test-specific object that verifies it is being used correctly by the SUT. (xUTPサイトより)

MockはSUTが間接的に出力する代役、言い換えるとSUTが使うモノの代役ということですね。

ちょうぜつ本より

テストのアサーションとして意味があるのはモックの方です。使っているサブモジュールの事前条件を満たしている(ちゃんと仕様どおりに使っている)ことを示すことで、実物を動かさずに正しさを示したと言えるようになります。(Kindle版 p.225)

MockはPythonでテストを書く上で使い倒していますね(設計ツールとしてMockを使っています)。
代役にして、その使い方を検証していたんだな〜

感想:広義のモックと狭義のモック

Test DoublesにはStubやMock以外にもありますが、ここまでを見て広義と狭義という点もこれまでよりピンと来るようになりました。

PHPUnitはStubとMock(狭義のモック)を提供していて、これらはTest Doubles(広義のモック)としてドキュメントで解説されます。

別の例としてPythonのunittestを考えると、unittest.mockが提供するMockは、広義のモックではないかと思います(開発者メーリスなどを参照したわけではないので、実装者の意図とは異なるかも)。
unittest.mockにはMockしかありません(StubやSpyはありません)。
私はunittest.mock.MockをxUTPのStubやMockの用途で使ってきたので、Test Doublesとして使えるという点で広義のモックととらえています。

仮にPythonPHPUnitのようなxUTPに忠実なテストライブラリがあったとしたら(言い換えるとテストコードを初めて書く経験がPHPUnitだったら)、いくつかの概念を掴む上で近道となったかもと思いました。
unittest.mockのおかげで概念理解の近道ももちろんありました。トレードオフなのかなと思います

終わりに

PHPUnitのドキュメントを機にxUnit Test Patternsのサイトを参照し、Test Doublesの理解を深めました。

  • Test Doubles(代役。広義のモック)
    • Test Stubs(SUTに間接的に入力する代役)
    • Mock Objects(SUTが使うものの代役。狭義のモック)

『xUnit Test Patterns』は邦訳がなく、分厚さもあって、なかなか読む機会がない書籍でした。
今回PHPUnitのドキュメントに誘われてサイトを確認したところ、これまでにテストコードを書いた経験から思った以上に読め、理解も深まるという小さな成功を積めました✌️


  1. 大阪で僕と握手!
  2. こちらのアーカイブで知りました (この発表で扱っているのは、広義のモックだと思います)
  3. PyCon APAC 2023の発表スライドより
  4. ロバストPython』で知ったAnnihilateが対応するのかもしれません。https://ftnext.github.io/2023-slides/pyconapac/practice-test-code.html#/13/6