コンピュータプログラミングにおいて、モックオブジェクトとは、限定的な方法で実際のオブジェクトを模倣するオブジェクトのことです。プログラマーは、ソフトウェアテストのテストダブルとしてモックオブジェクトを使用することがあります。モックオブジェクトは、汎用プログラミングでも使用できます。
モックオブジェクトは、自動車設計者が衝突試験用ダミーを使って車両衝突時の人間の状態をシミュレートするのと同様に、ソフトウェアテスターにとって有用なものとなり得る。
単体テストにおいて、モックオブジェクトは複雑な実在オブジェクトの動作をシミュレートできるため、実在オブジェクトを単体テストに組み込むことが非現実的または不可能な場合に役立ちます。オブジェクトが以下のいずれかの特性を持つ場合、代わりにモックオブジェクトを使用すると有効な場合があります。
例えば、特定の時刻にベルを鳴らす目覚まし時計プログラムは、時刻サービスから現在時刻を取得する場合があります。これをテストするには、ベルが正しく鳴ったかどうかを確認するために、アラーム時刻まで待つ必要があります。リアルタイムサービスの代わりに模擬時刻サービスを使用すれば、実際の時刻に関係なくベルを鳴らす時刻(またはその他の時刻)を提供するようにプログラムできるため、目覚まし時計プログラムを単独でテストできます。
モックオブジェクトは、模倣対象の実オブジェクトと同じインターフェースを持つため、クライアントオブジェクトは、実オブジェクトを使用しているのかモックオブジェクトを使用しているのかを意識する必要がありません。多くのモックオブジェクトフレームワークでは、プログラマーがモックオブジェクトに対してどのメソッドを呼び出すか、呼び出し順序、渡されるパラメータ、および返される値を指定できます。そのため、ネットワークソケットのような複雑なオブジェクトの動作をモックオブジェクトで模倣することができ、プログラマーは、テスト対象のオブジェクトが、モックオブジェクトが取りうる多様な状態に適切に応答するかどうかを確認できます。
モック、フェイク、スタブの定義は文献によって一貫していません。[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ]それにもかかわらず、これらはすべて同じインターフェースを公開することでテスト環境における本番オブジェクトを表しています。
名前に関係なく、最も単純な形式は(メソッドスタブのように)あらかじめ用意された応答を返し、最も複雑な形式は本番オブジェクトの完全なロジックを模倣します。
このようなテストオブジェクトには、各呼び出しのコンテキストを検証するためのアサーションが含まれる場合があります。たとえば、モックオブジェクトは、メソッドが呼び出される順序をアサートしたり、メソッド呼び出し間でデータの一貫性をアサートしたりすることができます。
『ユニットテストの技法』[ 7 ]では、モックは、オブジェクトとのやり取りが発生したかどうかを確認することで、テストが失敗したか成功したかを判断するのに役立つ偽のオブジェクトとして説明されています。それ以外のすべてはスタブとして定義されています。この本では、フェイクは実在しないものすべてであり、その使用法に基づいて、スタブまたはモックのいずれかになります。
認証サブシステムがモック化されている例を考えてみましょう。モックオブジェクトは、実際の認証クラスのメソッドに一致するように[ 8 ]メソッドを実装します。実際のクラスには存在しないプロパティも公開すると、多くの利点が得られます。これにより、テストコードは、次の呼び出しでユーザーに許可が与えられるか与えられないかを簡単に想定できるため、どちらの場合でもシステムの残りの動作を容易にテストできます。isUserAllowed(task : Task) : booleanisAllowed : boolean
同様に、モックのみの設定により、サブシステムへの後続の呼び出しによって例外がスローされたり、応答せずにハングアップしたり、戻り値を返すなどの動作が確実に発生するようにすることができます。これにより、バックエンドサブシステムにおける現実的な障害状況や、それらが期待する応答に対するクライアントのnull動作を開発およびテストすることが可能になります。このようなシンプルで柔軟なモックシステムがなければ、これらの状況をそれぞれテストするのは非常に手間がかかり、適切に検討することが困難になるでしょう。
モックデータベースオブジェクトのメソッドには、実装コードがほとんど(あるいは全く)含まれていない可能性があります。保存対象として渡されたPersonオブジェクトの存在と有効性を確認する(上記の「フェイク」と「モック」に関する説明を参照)かもしれませんが、それ以外に実装がない場合もあります。save(person : Person)
これは機会損失です。モックメソッドは、公開ログ文字列にエントリを追加できます。エントリは「Person saved」[ 9 ] : 146-7だけで十分ですが、名前やIDなどの人物オブジェクトインスタンスの詳細を含めることもできます。テストコードが、モックデータベースを含む一連の操作後のログ文字列の最終的な内容もチェックすれば、各ケースでデータベースの保存が期待どおりに実行されたことを検証できます。これにより、例えば、開発者がデータの損失を恐れて、save()1回で済むはずの呼び出しを繰り返してコーディングした場合など、通常は見えないパフォーマンス低下バグを見つけることができます。
テスト駆動開発(TDD) メソッドを使用するプログラマーは、ソフトウェアを作成する際にモック オブジェクトを使用します。モック オブジェクトは、より複雑な実際のオブジェクトのインターフェース要件を満たし、その代わりとなるため、プログラマーは複雑な基盤クラスや連携クラスを呼び出すことなく、1 つの領域の機能を記述して単体テストできます。[ 9 ] : 144–5モック オブジェクトを使用すると、開発者はテスト対象システムの依存関係を気にすることなく、テストの動作に集中できます。たとえば、複数のオブジェクトが特定の状態にあることに基づく複雑なアルゴリズムのテストは、実際のオブジェクトの代わりにモック オブジェクトを使用することで明確に表現できます。
複雑性の問題や、関心の分離によって得られるメリットとは別に、実用的な速度の問題も関係してきます。TDD を用いて現実的なソフトウェアを開発する場合、数百もの単体テストが必要になる可能性があります。これらのテストの多くがデータベース、Web サービス、その他のプロセス外システムやネットワークシステムとの通信を伴う場合、単体テストスイートはすぐに実行速度が遅くなり、定期的に実行できなくなります。これは、開発者が TDD の基本原則を守ろうとしないという悪習につながります。
モックオブジェクトを実際のオブジェクトに置き換える場合、エンドツーエンドの機能についてさらにテストが必要になります。これらは単体テストではなく、統合テストになります。
モックオブジェクトを使用すると、単体テストとテスト対象コードの実装が密接に結びついてしまう可能性があります。たとえば、多くのモックオブジェクトフレームワークでは、開発者がテスト対象の実際のオブジェクトによってモックオブジェクトのメソッドが呼び出された順序と回数を確認できます。そのため、テスト対象コードのリファクタリングによって、すべてのモックオブジェクトメソッドが以前の実装の契約に従っていても、テストが失敗する可能性があります。これは、単体テストではメソッドの内部実装ではなく、外部動作をテストする必要があることを示しています。単体テストスイートの一部としてモックオブジェクトを過剰に使用すると、リファクタリングが行われるシステム進化の過程で、テスト自体のメンテナンスが劇的に増加する可能性があります。進化の過程でこのようなテストを適切にメンテナンスしないと、実際のクラスのインスタンスを使用する単体テストで検出されるはずのバグが見逃される可能性があります。逆に、1つのメソッドをモックするだけでは、実際のクラス全体を設定するよりもはるかに少ない設定で済むため、メンテナンスの必要性が軽減されます。
モックオブジェクトは、モック対象オブジェクトの動作を正確にモデル化する必要がありますが、モック対象オブジェクトが別の開発者やプロジェクトから提供されたものであったり、まだ作成されていない場合は、これを実現するのは困難です。動作が正しくモデル化されていない場合、ユニットテストは、実行時にユニットテストと同じ条件下で失敗が発生するにもかかわらず、合格と判定される可能性があり、ユニットテストが不正確になります。[ 10 ]