146


113

モックオブジェクトの目的は何ですか?

ユニットテストは初めてであり、「モックオブジェクト」という言葉が頻繁に聞かれます。 素人の言葉で、誰かがモックオブジェクトとは何か、そして単体テストを書くときにそれらが一般的に使用されるものを説明できますか?

10 Answer


313


あなたはユニットテストが初めてであり、「素人の用語」でモックオブジェクトを要求したと言うので、素人の例を試してみましょう。

単体テスト

このシステムの単体テストを想像してみてください。

cook <- waiter <- customer

一般的に、 `cook`のような低レベルのコンポーネントのテストは簡単に想像できます。

cook <- test driver

テストドライバーは単に異なる料理を注文し、料理人が注文ごとに正しい料理を返すことを確認します。

ウェイターなど、他のコンポーネントの動作を利用する中間コンポーネントをテストするのは困難です。 素朴なテスターは、料理人コンポーネントをテストしたのと同じ方法でウェイターコンポーネントをテストするかもしれません。

cook <- waiter <- test driver

テスト運転手は異なる皿を注文し、ウェイターが正しい皿を返すようにします。 残念ながら、これはウェイターコンポーネントのこのテストがクックコンポーネントの正しい動作に依存している可能性があることを意味します。 非依存的な振る舞い(料理人としてのシェフの驚きがメニューに含まれている)、たくさんの依存関係(料理人全員がいなければ料理はできない)、またはたくさんの資源(料理の中には高価な食材を必要とするものや、調理に1時間かかるものがあります)。

これはウェイターテストであるため、理想的には、クックではなくウェイターのみをテストする必要があります。 具体的には、ウェイターが顧客の注文を料理人に正しく伝え、料理人の食べ物を正しく顧客に届けるようにします。

ユニットテストはユニットを独立してテストすることを意味するので、より良い方法はhttp://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs.Fowlerがテストダブルス(ダミー、スタブ、と呼ぶ)を使ってテスト中のコンポーネント(ウェイター)を分離することでしょう。偽物、モック)]。

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

ここでは、テスト料理人はテストドライバーと一緒に「だまされた」状態です。 理想的には、テスト中のシステムは、テストコードがプロダクションコードを変更することなくウェイターを操作するために容易に置き換えることができるように設計されている(http://en.wikipedia.org/wiki/Dependency_injection [injected])。 ウェイターコードを変更せずに)。

モックオブジェクト

さて、テストクック(test double)はさまざまな方法で実装できます。

  • 偽の料理人-冷凍を使用して料理人のふりをする人 夕食と電子レンジ、

  • スタブクック-常にホットドッグを提供するホットドッグベンダー 注文内容に関係なく、または

  • 模擬料理人-ふりをするスクリプトに従った覆面警官 スティング操作で調理します。

偽物とスタブの対モックとダミーの詳細については、http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs [Fowlerの記事]を参照してください。

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

ウェイターコンポーネントの単体テストの大部分は、ウェイターがクックコンポーネントとどのように対話するかに焦点を当てています。 モックベースのアプローチは、正しい対話が何であるかを完全に指定し、それがいつうまくいかなくなったかを検出することに焦点を当てています。

モックオブジェクトは、テスト中に何が起こるべきかを事前に知っています(例: どのメソッド呼び出しが呼び出されるかなど)、モックオブジェクトはそれがどのように反応することになっているかを知っています(例: 提供する戻り値 モックは、実際に起こることが起こるべきことと異なるかどうかを示します。 各テストケースのカスタムモックオブジェクトを最初から作成して、そのテストケースに期待される動作を実行できますが、モックフレームワークは、そのような動作仕様をテストケースで直接明確かつ簡単に示すことができるように努めています。

モックベースのテストに関する会話は、次のようになります。

_ _ テストドライバー*から*モッククックホットドッグの注文を期待し、応答でこのダミーのホットドッグを彼に渡します

テストドライバー(顧客のふりをして)ウェイターホットドッグをお願いしますplease ウェイター*から*モッククック1ホットドッグしてください* モッククック*から*ウェイター:_注文準備:1ホットドッグ(ダミーのホットドッグをウェイターに渡します) *ウェイター*から*テストドライバー*へ:あなたのホットドッグはどこにあります(ダミーのホットドッグをテストドライバーに渡します)

テストドライバ:テストは成功しました! _ _

しかし、私たちのウェイターは新しいので、これは起こり得ることです:

_ _ テストドライバー*から*モッククックホットドッグの注文を期待し、応答でこのダミーのホットドッグを彼に渡します

テストドライバー(顧客のふりをして)ウェイターホットドッグをお願いします ウェイター* - モッククック:_1ハンバーガーしてください* *モッククック*はテストを中止します:_ホットドッグの注文を期待するよう言われました!

  • test driver *は問題を指摘しています:TEST FAILED! -ウェイターが注文を変更しました _ _

or

_ _ テストドライバー*から*モッククックホットドッグの注文を期待し、応答でこのダミーのホットドッグを彼に渡します

テストドライバー(顧客のふりをして)ウェイターホットドッグをお願いしますplease ウェイター*から*モッククック1ホットドッグしてください* モッククック*から*ウェイター:_注文準備:1ホットドッグ(ダミーのホットドッグをウェイターに渡す) ウェイター*から*テストドライバーあなたのフライドポテトはどこにありますか(テストドライバーに他の注文からのフライドポテトを与える)

*テストドライバー*は予期しないフライドポテトに注意してください:TEST FAILED! ウェイターが間違った料理を返した _ _

これとは対照的なスタブベースの例がないと、モックオブジェクトとスタブの違いを明確に理解するのは難しいかもしれませんが、この答えはもう長すぎる:-)

また、これはかなり単純な例であり、モックフレームワークは包括的なテストをサポートするためにコンポーネントから予想される動作のかなり洗練された仕様を可能にすることにも注意してください。 より多くの情報については、モックオブジェクトとモックフレームワークに関する資料がたくさんあります。


26


モックオブジェクトは、実際のオブジェクトを置き換えるオブジェクトです。 オブジェクト指向プログラミングでは、モックオブジェクトはシミュレートされたオブジェクトであり、制御された方法で実際のオブジェクトの動作を模倣します。

コンピュータープログラマーは通常、モックオブジェクトを作成して他のオブジェクトの動作をテストします。これは、自動車の設計者が衝突テストダミーを使用して、車両の衝撃における人間の動的な動作をシミュレートするのとほぼ同じです。

モックオブジェクトを使用すると、データベースなどの扱いにくい大規模なリソースを負担することなく、テストシナリオを設定できます。 テストのためにデータベースを呼び出す代わりに、単体テストでモックオブジェクトを使用してデータベースをシミュレートできます。 これにより、クラス内の単一のメソッドをテストするだけで、実際のデータベースをセットアップおよび破棄する負担から解放されます。

「モック」という単語は、「スタブ」と同じ意味で誤って使用されることがあります。 2つの単語の違いはhttp://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs [ここで説明します。]基本的に、モックは期待も含むスタブオブジェクトです。 「アサーション」)テスト中のオブジェクト/メソッドの適切な動作。

例えば:

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`のモックオブジェクトは期待される結果でプログラムされていることに注意してください。


14


モックオブジェクトは、実際のオブジェクトの動作を模倣するシミュレートされたオブジェクトです。 通常、次の場合にモックオブジェクトを作成します。

  • 実際のオブジェクトは複雑すぎて、単体テストに組み込むことができません (たとえば、ネットワーク通信の場合、他のピアであるとシミュレートする模擬オブジェクトを使用できます)

  • オブジェクトの結果は非決定的です

  • 実際のオブジェクトはまだ利用できません


11


Mockオブジェクトは、http://xunitpatterns.com/Test%20Double.html [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());

このようにして、他のクラスに依存するクラスの開発を簡単にテストできます。


10


Martin Fowlerによるすばらしい記事を参照して、モックとは何か、スタブとの違いを説明することを強くお勧めします。


8


コンピュータープログラムの一部を単体テストするときは、その特定の部分の動作のみをテストすることが理想的です。

たとえば、印刷を呼び出すために別のプログラムを使用するプログラムの架空の部分から、以下の擬似コードを見てください。

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

これをテストする場合、主にユーザーがフレッドかどうかを調べる部分をテストする必要があります。 物事の `Printer`部分を実際にテストする必要はありません。 それは別のテストになります。

*これ*は、モックオブジェクトが入る場所です。 他のタイプのふりをします。 この場合、モックの `Printer`を使用して、実際のプリンターのように動作するようにしますが、印刷などの不都合なことはしません。

'' '' '

モックではない、使用できるふりをするオブジェクトには他にもいくつかのタイプがあります。 Mocks Mocksを作成する主なことは、ビヘイビアーと期待に基づいて構成できることです。

*期待*を使用すると、Mockを誤って使用するとエラーが発生します。 したがって、上記の例では、「user is Fred」テストケースでHelloFredを使用してPrinterが確実に呼び出されるようにすることができます。 それが起こらない場合、モックは警告することができます。

  • Mocksの動作*は、たとえば、コードで次のようなことをしたことを意味します。

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

ここで、プリンターが呼び出されてSaidHelloが返されたときにコードが何をするかをテストしたいので、HelloFredで呼び出されたときにSaidHelloを返すようにモックをセットアップできます。

これに関する優れたリソースの1つは、Martin Fowlersの投稿http://martinfowler.com/articles/mocksArentStubs.html[Mocks Aren’s Stubs]です。


6


モックオブジェクトとスタブオブジェクトは、単体テストの重要な部分です。 実際、それらは* groups のユニットではなく、 units *をテストしていることを確認するのに大いに役立ちます。

一言で言えば、スタブを使用して、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 input = new Mock();
  //output is a mock, because we want to verify some behavior on it.
  Mock output = new Mock();

  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は両方に同じクラスを使用します-「モック」は少し混乱させます。 とにかく、実行時に、「output.Write」が「parameter」としてデータとともに呼び出されない場合、テストは失敗しますが、「input.Read()」を呼び出せなくても失敗しません。


4


「http://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs[Mocks Are n’t Stubs]」へのリンクを介して提案された別の答えとして、モックは実際のオブジェクトの代わりに使用する「テストダブル」の形式です。 スタブオブジェクトなどの他の形式のテストダブルと異なる点は、他のテストダブルが状態検証(およびオプションでシミュレーション)を提供し、モックが動作検証(およびオプションでシミュレーション)を提供することです。

スタブを使用すると、スタブの複数のメソッドを任意の順序で(または繰り返し)呼び出して、スタブが意図した値または状態をキャプチャした場合に成功を判断できます。 対照的に、モックオブジェクトは、特定の順序で、特定の回数でさえ、非常に特定の関数が呼び出されることを期待しています。 メソッドが異なるシーケンスまたはカウントで呼び出されたため、モックオブジェクトを使用したテストは「失敗」と見なされます。テストが終了したときにモックオブジェクトが正しい状態にあったとしてもです。

このように、モックオブジェクトは、スタブオブジェクトよりもSUTコードに密接に結合していると見なされることがよくあります。 検証しようとしているものに応じて、それは良いことも悪いこともあります。


3


モックオブジェクトを使用するポイントの一部は、仕様に従って実際に実装する必要がないことです。 彼らは単にダミーの応答を与えることができます。 E.g. コンポーネントAとBを実装する必要があり、両方が互いに「呼び出し」(相互作用)する場合、Bが実装されるまでAをテストすることはできません。逆も同様です。 テスト駆動開発では、これは問題です。 したがって、AとBのモック(「ダミー」)オブジェクトを作成します。これらは非常に単純ですが、やり取りされると何らかの反応を示します。 そうすれば、Bのモックオブジェクトを使用してAを実装およびテストできます。


1


phpとphpunitについては、phpunitのドキュメントで詳しく説明されています。 こちらhttps://phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects[phpunit documentation]をご覧ください

単純な単語では、モックオブジェクトは元のオブジェクトの単なるダミーオブジェクトであり、戻り値を返します。この戻り値はテストクラスで使用できます