Open3

mockについてちゃんと理解したい

Kumamoto-HamachiKumamoto-Hamachi

そもそも"モック"ってなんだっけ?

https://en.wikipedia.org/wiki/Mock_object

In object-oriented programming, mock objects are simulated objects that mimic the behaviour of real objects in controlled ways, most often as part of a software testing initiative. A programmer typically creates a mock object to test the behaviour of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behaviour of a human in vehicle impacts. The technique is also applicable in generic programming.

mockオブジェクトは実際のオブジェクトの振る舞いを管理された方式下で模倣するオブジェクトのことで、多くの場合ソフトウェアのテスト実行計画の一部として行われる。

  • the object supplies non-deterministic results (e.g. the current time or the current temperature);
  • it has states that are difficult to create or reproduce (e.g. a network error);
  • it is slow (e.g. a complete database, which would have to be initialized before the test);
  • it does not yet exist or may change behavior;
  • it would have to include information and methods exclusively for testing purposes (and not for its actual task).

上記のような特性をもつオブジェクトには代替としてmockを用いるのが便利とのこと。つまり、

(1)不確定な結果を返すもの(現在の時刻や気温など)
(2)生成・再生成が難しい状態を持つもの(ネットワークエラー等)
(3)緩慢な動きをするもの(テスト前に初期化しないといけないだろう完全な状態のDB)
(4)まだ存在していない、もしくは振る舞いが変化し得るもの
(5)テストのためだけの(実際のタスクには必要ない)情報やメソッドを含まないといけないだろうもの

例えば、"目覚まし時計プログラム"が"タイムサービスプログラム"から現在の時刻を取得して特定の時刻にベルを鳴らす仕組みになっていたとする。もしmockがなければテストをするために実際に目覚ましが鳴る時間まで待ってやる必要がある。"タイムサービス"のmockを用意してやれば実時間に関係なく時計のベルが鳴る時間を提供してやれるのでいつでも目覚ましのテストをすることが出来るようになる。

Mock objects have the same interface as the real objects they mimic, allowing a client object to remain unaware of whether it is using a real object or a mock object.

モックオブジェクトは本物のオブジェクトと同じインタフェース(受け口)を持つのでクライアント(使う側)としてはモックか本物かどちらのオブジェクトを使っているのか意識しないまま使うことが出来る。

Many available mock object frameworks allow the programmer to specify which, and in what order, methods will be invoked on a mock object and what parameters will be passed to them, as well as what values will be returned.

多くのモックオブジェクトのフレームワークではプログラマーに、「どのメソッドをどの順番で呼び出すか、どのパラメーターを渡すか、同様にどういった値を返り値にするか」を指定出来るようにしている。

Mocks, fakes and stubs

文献によって定義はマチマチだが、概ね「テスト環境で同じインタフェース提供をするための本物のオブジェクトを」というところは共通している。

モックの一番シンプルな形態としてはmethod stubのように決められたレスポンスをするだけのもの。対極(on the other side of spectrum)として複雑なものとなると、ロジックや例外などを完全に備えていたりします。

またシンプルなものも複雑なものもそれぞれのコールの実行コンテキストのアサーションを持っていたりする。(メソッドの呼び出し順番やデータの一貫性をアサーションする)

the two ends of complexity spectrum:複雑さの両端...?両極...?

Setting expectations

モックのメソッドを用意した時にシミュレート対象の本物のオブジェクトにないプロパティを用意してやると捗ることがある。

例えばisUserAllowed(task: Task):booleanというユーザー認証のメソッドを持つモックオブジェクトがある時、isAllowedという実際のオブジェクトにはない(開発者が設定可能な)プロパティを生やしてやることで、次の呼び出し時の振る舞いの"期待"を簡単に設定出来るようになる。

Similarly, mock-only settings could ensure that subsequent calls to the sub-system will cause it to throw an exception, hang without responding, or return null etc. Thus it is possible to develop and test client behaviors for realistic fault conditions in back-end sub-systems as well as for their expected responses. Without such a simple and flexible mock system, testing each of these situations may be too laborious for them to be given proper consideration.

モック限定の設定の中でその後のサブシステムの呼び出しでの例外発生、応答なしの中断、NULLを返したりすることを保証して障害状況の再現を行うなどが出来る。

Writing log strings

This is a missed opportunity. The mock method could add an entry to a public log string. The entry need be no more than "Person saved", or it may include some details from the person object instance, such as a name or ID. If the test code also checks the final contents of the log string after various series of operations involving the mock database then it is possible to verify that in each case exactly the expected number of database saves have been performed. This can find otherwise invisible performance-sapping bugs, for example, where a developer, nervous of losing data, has coded repeated calls to save() where just one would have sufficed.

何かしらのデータ保存の際にそのデータの構造の検証をするだけでなく文字列のログを残しておくほうが、保存回数の検証も出来て良いよ的な話...?

テスト駆動開発

TDDでやるときにもモックは役立つぜ。
関心の分離(separation of concerns)

reluctance:気が進まないこと
tenet:主義
subsequent:後続の
Conversely:逆に

モックでない本物のオブジェクトを用いたE2Eのテストはユニットテストというよりインテグレーションテストとして実行される必要がある的な話。

mockの限界

モックはコード実装とユニットテストを密結合にしてしまうことがある。
例えば、モックオブジェクトのメソッドが何回呼ばれるかいつ呼ばれるかをテストするコードを書いていたとする。この後にコードのリファクタを行うと全てのモックオブジェクトが以前の実装のコントラクトに従っていたとしてもテストが失敗することがある。

これは、ユニットテストは内部実装よりもメソッドの外部動作をテストすべきものであることを示している。モックオブジェクトをユニットテストの一部として使いすぎると、 リファクタリングによるシステムの進化にともない、 テスト自体のメンテナンス量が大幅に増えてしまう可能性があります。逆に、あるメソッドをモック化するだけなら、 実際のクラス全体を設定するよりもはるかに少ない構成で済み、 メンテナンスの必要性が低くなるかもしれません。

モック対象のオブジェクトの振る舞いを正確にモデル化しなければならないけど、対象オブジェクトが別開発者や別PJで作られたものならそれは結構難しいよね。さらに正確に再現出来ないなら結局テストで意図しない失敗も発生して辛いよねって話。