テストダブルについて

ikasam_a
2011-12-24

はじめに

ikasam_a です。クリスマスイブいかがお過ごしでしょうか。私は寿司食べたりしてました。

16日のエントリでテストダブル、特にフェイクやスタブについて触れましたが、今日はそもそもテストダブルって何ぞや、という話をします。またまた Perl の話はあまり出てこない予定です。

テストダブルとは

http://xunitpatterns.com/Test Double.html:title=xUnit Test Patterns] で登場した言葉で、システムのある部分をテストするために特定箇所を置き換えることがあるが、その置き換え手段の総称として「テストダブル(代役)」という名前を当てたということです。

置き換え手段としては、以下の5つが挙げられています。

  • ダミー
  • スタブ
  • スパイ
  • モック
  • フェイク
ダミー

メソッド呼び出しの数合わせに使うことを目的とした、ダミーオブジェクトのことです。

呼び出し先の要件さえ満たせば大体何でも良いので、Test::MockObject で作ったオブジェクトや、場合によっては勝手に bless したオブジェクトでもいいですね。

スタブ

スタブは、テストにおけるメソッド呼び出しに対して決まった結果を返すように作られたものです。

ゼロから指定したメソッドのみ結果を返すように作る場合と、既存のオブジェクトやクラスに対してスタブ定義したメソッドのみ動作を変更するよう作る場合があります。前者の例は Test::MockObject、後者は Test::Mock::Guard や Class::Monadic などが該当します。

スパイ

スパイは、スタブに呼び出し情報の記録能力を加えた感じです。メソッド呼び出し結果をログしつつ、スタブした固定値を返すといった感じです。

モック

モックは、Expectation(期待動作)を事前定義するオブジェクトです。この動作定義では、返す値以外に、呼び出しの引数や呼び出し回数なども定義できる場合が多いです。

期待されない呼び出しが行なわれた場合は、例外を投げるよう振る舞うことが期待されます。また、実行後に Verification(動作検証)が可能で、期待された呼び出しが行われたかを確認できます。

フェイク

フェイクは、ある特定のモジュール、コンポーネント、サブシステムなどの代わりに動作するものです。依存する外部サービスの代わりとして、テストに必要なAPIのみを実装して利用したりします。

実例としては、SQLite や HSQLDB のインメモリデータベースが Martin Fowler の bliki でも紹介されています。Perl モジュールでは、Test::mysqld や Test::Memcached、Test::TCP などがフェイクを体現したモジュールです。

モックとスタブ

よくモックとスタブの違いがわからないという話を聞きます。両者は事前定義した値を返すという振る舞いから、実装上の共通点や操作方法も似ていることが多いため、違いがわかりにくくなる場合があるのだと思います。

テストダブルでの定義では、両者は明らかに異なります。

スタブは黙って値を返しさえすればよく、一方モックの場合は、値を返すだけではなく期待通りに動作しない場合は例外を発生させるようになっています。この違いは、どの部分に着目してテストをしているかの違いを表しています。

スタブの場合は、それを利用する部分がテスト対象になります。AモジュールがBモジュールを利用する場合で、Aをテストしたい場合に依存するBにスタブを適用するというケースです。注目しない部分をテスト用に書き換えてしまう動作になるので、値を返しさえすれば良いという割り切りが可能です。

一方、モックの場合は、適用する部分そのものがテスト対象です。AモジュールがBモジュールを操作するような場合で、正しくBモジュールを呼び出しているかを検証するために、Bモジュールにモックを適用した上でテストすることになります。

ですので、今テストしたい部分がどこかを的確に認識できれば、モックとスタブのどちらを使うのがよい状況なのかを判断できるようになります。

スタブとフェイク

スタブとフェイクは、最終的に実現したいことが同じになるため、どちらを使うべきか判断に迷う場面があります。例えば以前のエントリにも出てきた HTTP のテストをする場合が良い例です。

この場合、テスト対象は HTTP クライアントで、外部コンポーネントとして何らかの API サーバなどが登場します。通信部分をスタブとすると、実際には通信は行われず、事前定義したレスポンスを返すようになります。一方フェイクで HTTP サーバを用意した場合は、通信を行った上でフェイク上で定義されるレスポンスを返します。

ここで、HTTP 通信自体は何らかのライブラリに任せてクライアントを実装する場合、クライアントのテストとしては通信を重要視せず、ロジックが正しく組み立てられているかに注目すると、通信部をスタブとしてテストする戦略が取れます。

一方、通信部分も含んで HTTP クライアント全体として正しく動作しているかをテストしたい場合は、スタブではなくフェイクでサーバを構築し、実際の HTTP 通信を含んだ状況でテストすることが望ましくなります。

このように、テスト対象が極小部分に閉じているか、あるいは結合を考慮したテストを行うかにより、スタブを使うかフェイクを使うかを戦略的に選択できるようになります。

おわりに

普段はあまり意識しないテストダブルの概念と、紛らわしい部分について解説しました。

概念があいまいだったり、実装方法や利用手段が似ていることから、どれをどう使えばよいかわからなかったり、間違った解釈をされることも多いテストダブルですが、それぞれの目的の違いを把握することで、より適切な手段を選択して効率的にテストを行うことが可能だと思います。

テストダブルはあくまでテストのための技法であって、一番大事なことは、テスト対象を見誤らず適切にテストをすることです!引き出しが多いに越したことはないので、今日のエントリがテストダブルを理解して道具とするためのお役に立てばと思います!