ユニットテストは初めてであり、「モックオブジェクト」という言葉が頻繁に聞かれます。素人の言葉で、誰かがモックオブジェクトとは何か、そして単体テストを書くときにそれらが一般的に使用されるものを説明できますか?
あなたはユニットテストが初めてであり、「素人の用語」でモックオブジェクトを要求したと言うので、素人の例を試してみましょう。
このシステムの単体テストを想像してください:
cook <- waiter <- customer
一般に、cook
のような低レベルコンポーネントのテストは簡単に想像できます。
cook <- test driver
テストドライバーは、単に異なる料理を注文し、料理人が注文ごとに正しい料理を返すことを確認します。
他のコンポーネントの動作を利用するウェイターなどの中間コンポーネントをテストするのはより困難です。素朴なテスターは、クックコンポーネントのテストと同じ方法でウェイターコンポーネントをテストできます。
cook <- waiter <- test driver
テストドライバーはさまざまな料理を注文し、ウェイターが正しい料理を返すようにします。残念ながら、これは、ウェイターコンポーネントのこのテストがクックコンポーネントの正しい動作に依存する可能性があることを意味します。この依存関係は、非決定的な動作(メニューに料理人の驚きがメニューに含まれる)、多くの依存関係(料理人がスタッフ全員なしで調理しない)、またはリソース(一部の料理は高価な材料を必要とするか、調理に1時間かかります)。
これはウェイターテストであるため、理想的には、クックではなくウェイターのみをテストします。具体的には、ウェイターがお客様の注文を料理人に正しく伝え、料理人の食べ物をお客様に正しく配達するようにします。
ユニットテストはユニットを個別にテストすることを意味するため、より良いアプローチは、テスト対象のコンポーネント(ウェイター)を Fowlerがテストダブル(ダミー、スタブ、フェイク、モック)を呼び出す を使用して分離することです。
-----------------------
| |
v |
test cook <- waiter <- test driver
ここでは、テストコックはテストドライバーと「インフー」です。理想的には、テスト対象のシステムは、テストコードを簡単に置き換えて( injected )、生産コードを変更せずに(たとえば、ウェイターコードを変更せずに)ウェイターと連携できるように設計されています.
これで、テストクック(テストダブル)をさまざまな方法で実装できます。
偽物対スタブ対モック対ダミーの詳細については、ファウラーの記事 を参照してください。ただし、ここでは、モッククックに焦点を当てましょう。
-----------------------
| |
v |
mock cook <- waiter <- test driver
ウェイターコンポーネントをテストするユニットの大部分は、ウェイターがクックコンポーネントと対話する方法に焦点を当てています。モックベースのアプローチは、正しい相互作用が何であるかを完全に特定し、それがいつうまくいかないかを検出することに焦点を合わせています。
モックオブジェクトは、テスト中に何が起きるか(たとえば、どのメソッド呼び出しが呼び出されるかなど)を事前に知っており、モックオブジェクトは、どのように反応するか(たとえば、どの戻り値を提供するか)を知っています。モックは、実際に起こることと起こるはずのこととが異なるかどうかを示します。カスタムのモックオブジェクトを各テストケースに対してゼロから作成して、そのテストケースに期待される動作を実行することができますが、モックフレームワークは、そのような動作仕様をテストケースで明確かつ簡単に直接示すことを可能にします。
モックベースのテストを取り巻く会話は次のようになります。
test drivertomock cook:ホットドッグを期待注文して、応答でこのダミーのホットドッグを彼に渡す
test driver(顧客としてポーズ)towaiter:ホットドッグをお願いします
waitertomock cook:ホットドッグを1つください
mock cooktowaiter:注文:ホットドッグ1枚準備完了(ダミーホットドッグをウェイターに提供)
waitertotest driver:こちらがホットドッグです(テストドライバーにダミーのホットドッグを提供します)テストドライバー:テストは成功しました!
しかし、ウェイターは新しいので、これが起こる可能性があります:
test drivertomock cook:ホットドッグを期待注文して、応答でこのダミーのホットドッグを彼に渡す
test driver(顧客としてポーズ)towaiter:ホットドッグをお願いします
waitertomock cook:ハンバーガー1個ください
mock cookはテストを停止します:ホットドッグの注文を期待するように言われました!テストドライバーは問題に注意します:テストが失敗しました! -ウェイターが注文を変更しました
または
test drivertomock cook:ホットドッグを期待注文して、応答でこのダミーのホットドッグを彼に渡す
test driver(顧客としてポーズ)towaiter:ホットドッグをお願いします
waitertomock cook:ホットドッグを1つください
mock cooktowaiter:注文:ホットドッグ1枚準備完了(ダミーホットドッグをウェイターに提供)
waitertotest driver:フライドポテトはこちらです(他の注文のフレンチフライを運転手に提供します)テストドライバーは、予期しないフレンチフライに注意します:テストが失敗しました!ウェイターが間違った料理を返した
これとは対照的なスタブベースの例がなければ、モックオブジェクトとスタブの違いをはっきりと見るのは難しいかもしれませんが、この答えはすでに長すぎます:-)
また、これは非常に単純な例であり、モックフレームワークでは、コンポーネントから予想される動作のかなり洗練された仕様により、包括的なテストをサポートできることに注意してください。詳細については、モックオブジェクトとモックフレームワークに関する資料がたくさんあります。
モックオブジェクトは、実際のオブジェクトを置き換えるオブジェクトです。オブジェクト指向プログラミングでは、モックオブジェクトは、制御された方法で実際のオブジェクトの動作を模倣するシミュレートされたオブジェクトです。
通常、コンピュータープログラマーはモックオブジェクトを作成して、他のオブジェクトの動作をテストします。これは、自動車の設計者が衝突テストダミーを使用して、車両の衝撃における人間の動的な動作をシミュレートするのとほぼ同じです。
http://en.wikipedia.org/wiki/Mock_object
モックオブジェクトを使用すると、データベースなどの扱いにくい大規模なリソースを負担することなく、テストシナリオを設定できます。テストのためにデータベースを呼び出す代わりに、単体テストでモックオブジェクトを使用してデータベースをシミュレートできます。これにより、クラス内の1つのメソッドをテストするだけで、実際のデータベースをセットアップおよび破棄する負担から解放されます。
「モック」という言葉は、「スタブ」と同じ意味で誤って使用されることがあります。 2つの単語の違いは ここで説明します 基本的に、モックは、テスト対象のオブジェクト/メソッドの適切な動作に対する期待(つまり、「アサーション」)も含むスタブオブジェクトです。
例えば:
class OrderInteractionTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
}
warehouse
およびmailer
モックオブジェクトは、期待される結果でプログラムされていることに注意してください。
モックオブジェクトは、実際のオブジェクトの動作を模倣するシミュレートされたオブジェクトです。通常、次の場合にモックオブジェクトを作成します。
Mockオブジェクトは Test Double の一種です。モックオブジェクトを使用して、テスト中のクラスと他のクラスのプロトコル/相互作用をテストおよび検証します。
通常、「プログラム」または「記録」のような期待があります。クラスが基礎となるオブジェクトに対して行うことを期待するメソッド呼び出しです。
たとえば、ウィジェット内のフィールドを更新するサービスメソッドをテストしているとします。そして、あなたのアーキテクチャには、データベースを扱うWidgetDAOがあります。データベースとの対話は遅く、セットアップとその後のクリーニングは複雑なので、WidgetDaoのモックを作成します。
サービスが何をしなければならないか考えてみましょう。データベースからウィジェットを取得し、それを使って再度保存します。
したがって、擬似モックライブラリを使用した擬似言語では、次のようになります。
Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);
// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);
expect(mock.save(sampleWidget);
// turn the dao in replay mode
replay(mock);
svc.updateWidgetPrice(id,newPrice);
verify(mock); // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());
このようにして、他のクラスに依存するクラスの開発を簡単にテストできます。
Martin Fowlerによるすばらしい記事 をお勧めします。モックとは何か、スタブとどのように違うのかを説明します。
コンピュータープログラムの一部を単体テストするときは、その特定の部分の動作のみをテストすることが理想的です。
たとえば、印刷を呼び出すために別のプログラムを使用するプログラムの架空の部分から、以下の擬似コードを見てください。
If theUserIsFred then
Call Printer(HelloFred)
Else
Call Printer(YouAreNotFred)
End
これをテストする場合、主にユーザーがフレッドかどうかを調べる部分をテストする必要があります。本当にPrinter
部分をテストする必要はありません。それは別のテストになります。
Thisは、Mockオブジェクトの出番です。他のタイプのふりをします。この場合、Mock Printer
を使用して、実際のプリンターのように機能するようにしますが、印刷などの不便なことはしません。
モックではない、使用できるふりをするオブジェクトには他にもいくつかのタイプがあります。 Mocks Mocksを作成する主なことは、ビヘイビアーと期待に基づいて構成できることです。
期待値を使用すると、モックが誤って使用されたときにエラーが発生します。したがって、上記の例では、「user is Fred」テストケースでHelloFredを使用してPrinterが確実に呼び出されるようにすることができます。そうでない場合、モックは警告を発します。
Mocksの動作は、たとえば、コードで次のようなことをしたことを意味します。
If Call Printer(HelloFred) Returned SaidHello Then
Do Something
End
ここで、プリンターが呼び出されてSaidHelloが返されたときにコードが何をするかをテストしたいので、HelloFredで呼び出されたときにSaidHelloを返すようにモックをセットアップできます。
これに関する優れたリソースの1つは、Martin Fowlersの投稿です Mocks Are n't Stubs
モックオブジェクトとスタブオブジェクトは、単体テストの重要な部分です。実際、これらは、ユニットのgroupsではなくnitsをテストしていることを確認するのに大いに役立ちます。
一言で言えば、スタブを使用して、SUT(テスト対象システム)の他のオブジェクトへの依存関係を解除し、モックを行いますおよび SUTが依存関係の特定のメソッド/プロパティを呼び出したことを確認します。これは、単体テストの基本原則に戻ります。テストは、読みやすく、高速で、構成を必要とせず、すべての実際のクラスを使用することで暗示されるはずです。
一般に、テストには複数のスタブを含めることができますが、モックは1つだけにする必要があります。これは、モックの目的は動作を検証することであり、テストでは1つのことのみをテストする必要があるためです。
C#とMoqを使用した簡単なシナリオ:
_public interface IInput {
object Read();
}
public interface IOutput {
void Write(object data);
}
class SUT {
IInput input;
IOutput output;
public SUT (IInput input, IOutput output) {
this.input = input;
this.output = output;
}
void ReadAndWrite() {
var data = input.Read();
output.Write(data);
}
}
[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
//we want to verify that SUT writes to the output interface
//input is a stub, since we don't record any expectations
Mock<IInput> input = new Mock<IInput>();
//output is a mock, because we want to verify some behavior on it.
Mock<IOutput> output = new Mock<IOutput>();
var data = new object();
input.Setup(i=>i.Read()).Returns(data);
var sut = new SUT(input.Object, output.Object);
//calling verify on a mock object makes the object a mock, with respect to method being verified.
output.Verify(o=>o.Write(data));
}
_
上記の例では、Moqを使用してスタブとモックを示しました。 Moqは両方に同じクラスを使用します-_Mock<T>
_少し混乱させます。とにかく、実行時に、データがparameter
として_output.Write
_が呼び出されない場合、テストは失敗しますが、input.Read()
の呼び出しが失敗しても失敗しません。
「 Mocks Are n't Stubs 」へのリンクを介して示唆される別の答えとして、モックは実際のオブジェクトの代わりに使用する「テストダブル」の形式です。スタブオブジェクトなどの他の形式のテストダブルと異なる点は、他のテストダブルが状態検証(およびオプションでシミュレーション)を提供し、モックが動作検証(およびオプションでシミュレーション)を提供することです。
スタブを使用すると、スタブで複数のメソッドを任意の順序で(または繰り返し)呼び出して、スタブが意図した値または状態をキャプチャした場合に成功を判断できます。対照的に、モックオブジェクトは、特定の順序で、特定の回数でさえ、非常に特定の関数が呼び出されることを期待しています。メソッドが異なるシーケンスまたはカウントで呼び出されたため、モックオブジェクトを使用したテストは「失敗」と見なされます。テストが終了したときにモックオブジェクトが正しい状態にあったとしてもです。
このように、モックオブジェクトは、スタブオブジェクトよりもSUTコードに密接に結合していると見なされることがよくあります。検証しようとしているものに応じて、それは良いことも悪いこともあります。
モックオブジェクトを使用するポイントの一部は、仕様に従って実際に実装する必要がないことです。彼らは単にダミーの応答を与えることができます。例えば。コンポーネントAとBを実装する必要があり、両方が互いに「呼び出し」(相互作用)する場合、Bが実装されるまでAをテストすることはできません。逆も同様です。テスト駆動開発では、これは問題です。したがって、AとBのモック(「ダミー」)オブジェクトを作成します。これらは非常に単純ですが、やり取りされるとsome一種の応答を与えます。そうすれば、Bのモックオブジェクトを使用してAを実装およびテストできます。
Phpとphpunitについては、phpunitのドキュメントで詳しく説明されています。こちらをご覧ください phpunit documentation
単純なWordでは、モックオブジェクトは元のオブジェクトの単なるダミーオブジェクトであり、その戻り値を返します。この戻り値はテストクラスで使用できます。