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


167

私はユニットテストに不慣れです。「モックオブジェクト」という言葉が頻繁に飛び交います。素人の言葉で、誰かがモックオブジェクトとは何か、そしてユニットテストを書くときにそれらが通常使用されるものを説明できますか?


12
それらは、手元の問題に必要のない柔軟性を備えたものを大規模にオーバーエンジニアリングするためのツールです。
dsimcha

2
可能性のある重複モックは何?
nawfal 14

回答:


361

ユニットテストが初めてであり、「素人の言葉」で模擬オブジェクトを求められたと言ったので、素人の例を試してみましょう。

ユニットテスト

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

cook <- waiter <- customer

一般に、cook次のような低レベルのコンポーネントのテストを想定するのは簡単です。

cook <- test driver

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

ウェイターのように、他のコンポーネントの動作を利用する中間コンポーネントをテストするのは困難です。単純なテスターは、クックコンポーネントをテストしたのと同じ方法で、ウェイターコンポーネントをテストできます。

cook <- waiter <- test driver

テストドライバーは別の料理を注文し、ウェイターが正しい料理を返すことを確認します。残念ながら、これはウェイターコンポーネントのこのテストがクックコンポーネントの正しい動作に依存している可能性があることを意味します。この依存関係は、非決定的な動作(メニューにシェフの驚きが料理に含まれている)、依存関係が多い(料理人はスタッフ全員がいないと料理できない)など、テストに不向きな特性がある場合はさらに悪化します。リソース(一部の料理には高価な食材が必要か、調理に1時間かかります)。

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

ユニットテストはユニットを個別にテストすることを意味するため、Fowlerがテストダブル(ダミー、スタブ、偽物、モック)と呼ぶものを使用して、テスト中のコンポーネント(ウェイター)を分離することをお勧めします。

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

ここでは、テストコックがテストドライバーと「対話」しています。理想的には、テスト中のシステムは、プロダクションコードを変更せずに(たとえば、ウェイターコードを変更せずに)テストクックを簡単に置き換え(インジェクション)してウェイターと連携できるように設計されています。

モックオブジェクト

ここで、テストクック(テストダブル)はさまざまな方法で実装できます。

  • 偽の料理人-冷凍ディナーと電子レンジを使用して料理人のふりをする人
  • スタブクック-注文に関係なく常にホットドッグを提供するホットドッグベンダー、または
  • モッククック-スティング操作のクックを装ったスクリプトに続く覆面警官。

フェイクvsスタブvsモックvsダミーの詳細については、ファウラーの記事を参照してください。

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

ウェイターコンポーネントのユニットテストの大部分は、ウェイターがクックコンポーネントと対話する方法に焦点を当てています。モックベースのアプローチは、正しい相互作用が何であるかを完全に特定し、それがうまくいかないときを検出することに焦点を当てています。

モックオブジェクトは、テスト中に何が発生するか(たとえば、どのメソッド呼び出しが呼び出されるかなど)を事前に知っており、モックオブジェクトはそれがどのように反応するか(たとえば、提供する戻り値)を知っています。モックは、実際に何が発生するかが、想定されるものと異なるかどうかを示します。カスタムモックオブジェクトは、テストケースごとにゼロから作成して、そのテストケースの予想される動作を実行できますが、モックフレームワークは、そのような動作仕様をテストケースで直接かつ簡単に直接表示できるように努めています。

モックベースのテストを取り巻く会話は次のようになります。

クック模擬するテストドライバーホットドッグの注文を期待し、このダミーのホットドッグを返答する

ウェイターにテストドライバー(顧客を装う)ホットドッグが
ウェイターモック調理をお願いします:1ホットドッグが
クックウェイターに模擬してください注文:1ホットドッグ準備完了(ダミーホットドッグをウェイターに提供)
ウェイターテストドライバーここにあなたのホットドッグがあります(テストドライバーにダミーのホットドッグを与えます)

テストドライバー:TEST SUCCCEEDED!

しかし、ウェイターは新しいので、次のことが起こります。

クック模擬するテストドライバーホットドッグの注文を期待し、このダミーのホットドッグを返答する

テストドライバー(お客様を装って)をウェイターにホットドッグに
ウェイター模擬調理をお願いします:1ハンバーガーに
模擬クックがテストを停止します:ホットドッグの注文を期待するように言われました!

テストドライバーは問題を記録します:テストが失敗しました!-ウェイターが注文を変更した

または

クック模擬するテストドライバーホットドッグの注文を期待し、このダミーのホットドッグを返答する

テストドライバー(お客様を装ってウェイターにホットドッグをお願いします
ウェイタークック模擬してくださいホットドッグ1つ
クックウェイターにモックして注文:1ホットドッグ準備完了(ダミーホットドッグをウェイターに提供)
ウェイターテストドライバーこちらがフライドポテトです(ドライバーをテストするために、他の注文からのフライドポテトを提供します)。

テストドライバーは、予期しないフライドポテトを記録します。テストが失敗しました!ウェイターが間違った料理を返した

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

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


12
これは素晴らしい説明ですが、ウェイターの実装をある程度テストしていませんか?あなたのケースでは、正しいAPIを使用していることを確認しているので、おそらく問題はありませんが、それを行うためのさまざまな方法があり、ウェイターがどちらか一方を選択する場合はどうでしょうか。単体テストのポイントは、実装ではなくAPIをテストすることだと思いました。(これは、私がモックについて読んだときにいつも尋ねる自分の質問です。)
davidtbernal

8
ありがとう。ウェイターの仕様を確認(または定義)せずに「実装」をテストしているかどうかはわかりません。ウェイターは自分で料理したり、注文を通りで注文したりできると思いますが、ウェイターの仕様には意図したシェフの使用が含まれていると思います-結局のところ、プロダクションシェフは高価なグルメシェフであり、 dウェイターが彼を使うことを好む。その仕様がなければ、私はあなたが正しいと結論付ける必要があると思います-ウェイターは「正しい」ために望みどおりに注文を満たすことができます。OTOH、仕様がなければ、テストは無意味です。[続き...]
Bert F

8
絶対に、あなたはホワイトボックスとブラックボックスのユニットテストの素晴らしいトピックにつながる素晴らしいポイントを作ります。ユニットテストはホワイトボックスではなくブラックボックスでなければならないという業界のコンセンサスはないと思います(「実装ではなくAPIをテストする」)。コードのカバレッジとテストケースの完全性に対してテストの脆弱性のバランスをとるためには、おそらく最高のユニットテストが2つの組み合わせである必要があると思います。
Bert F

1
この答えは、技術的に十分ではないと私によればです。実際のオブジェクトを使用できるときにモックオブジェクトを使用する理由を知りたい。
Niklas

1
素晴らしい説明!! ありがとうございました!!@BertF
Bharath Murali

28

モックオブジェクトは、実際のオブジェクトの代わりとなるオブジェクトです。オブジェクト指向プログラミングでは、モックオブジェクトは、実際のオブジェクトの動作を制御された方法で模倣するシミュレーションオブジェクトです。

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

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

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

「モック」という言葉は、「スタブ」と同じ意味で誤って使用されることがあります。ここでは、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());
  }
}

warehousemailerモックオブジェクトが期待どおりの結果でプログラムされていることに注意してください。


2
ここで指定した定義は、「スタブオブジェクト」と少なくとも異なります。したがって、モックオブジェクトが何であるかを説明していません。
ブレントアリアス2010

別の修正「単語「モック」が「スタブ」と同じ意味で誤って使用されることがある」。
ブレントアリアス

@Myst:2つの単語の用法は普遍的ではありません。著者によって異なります。ファウラーはそう言っており、ウィキペディアの記事もそう言っています。ただし、変更を自由に編集して、反対票を削除してください。:)
ロバートハーベイ、

1
単語「モック」の使用は、業界全体で変化する傾向があるが、そこではありません:私はロバートに同意なし実際に使用して場所をテスト容易にするために、そのは通常、実際にテストされているオブジェクトではない、むしろそれが存在することを除いて、私の経験に基づいて設定された定義は、オブジェクトまたはそのすべての部分は非常に不便であり、ほとんど影響がありません。
mkelley33

15

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

  • 実際のオブジェクトは複雑すぎて単体テストに組み込むことはできません(たとえば、ネットワーク通信では、他のピアをシミュレートするモックオブジェクトを使用できます)
  • オブジェクトの結果は非決定的です
  • 実際のオブジェクトはまだ利用できません

12

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());

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


11

Martin Fowlerによる、モックとは正確に何であり、スタブとの違いを説明すばらしい記事を強くお勧めします。


10
初心者にやさしいとは言えませんね。
Robert Harvey

@ロバート・ハーヴェイ:たぶん、とにかくそれがあなたの答えを明確にするのに役立ったことを見て良かった:)
Adam Byrtek

Martin Fowlerの記事はRFCの方法で書かれています:ドライとコールド。
REVO

9

コンピュータプログラムの一部を単体テストする場合、その特定の部分の動作だけをテストするのが理想的です。

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

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

これをテストする場合、主に、ユーザーがFredかどうかを確認する部分をテストします。Printer物事の一部を実際にテストする必要はありません。それは別のテストになります。

これは、モックオブジェクトが来るところである。彼らは物事の他のタイプであることをふりをします。この場合、モックを使用してPrinter、実際のプリンターと同じように動作しますが、印刷などの不便なことは行いません。


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

Expectationsを使用すると、Mockを誤って使用したときにエラーが発生する可能性があります。したがって、上記の例では、「user is Fred」テストケースでHelloFredを使用してプリンターが呼び出されることを確認できます。それが起こらない場合、あなたのモックは警告することができます。

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

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

ここで、Printerが呼び出されてSaidHelloを返すときにコードが何を行うかをテストするため、HelloFredで呼び出されたときにSaidHelloを返すようにMockを設定できます。

これに関する優れたリソースの1つは、Martin FowlersがMocks Are n't Stubsを投稿していることです。


7

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

簡単に言えば、スタブを使用して他のオブジェクトへのSUT(システムアンダーテスト)の依存関係を解除し、モックを実行して SUTが依存関係の特定のメソッド/プロパティを呼び出したこと確認します。これは、単体テストの基本原則に戻ります。つまり、テストは簡単に読み取り可能で、高速で、構成を必要としない必要があります。これは、すべての実際のクラスを使用することで意味する可能性があります。

通常、テストには複数のスタブを含めることができますが、モックは1つだけにする必要があります。これは、mockの目的が動作を検証することであり、テストでは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>これは少し混乱します。いずれにせよ、実行時にoutput.Writeデータがparameterで呼び出されない場合、テストは失敗しますが、呼び出しinput.Read()に失敗しても失敗しません。


4

モックはスタブではない」へのリンクを介して提案された別の答えとして、モックは実際のオブジェクトの代わりに使用する「テストダブル」の形式です。それらがスタブオブジェクトなどの他の形式のテストダブルスと異なる点は、他のテストダブルスは状態検証(およびオプションでシミュレーション)を提供し、モックは動作検証(およびオプションでシミュレーション)を提供することです。

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

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


3

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


1

phpとphpunitについては、phpunitのドキュメントで詳しく説明されています。phpunitのドキュメントはこちら

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


0

これは、単体テストの主要な視点の1つです。はい、コードの単一ユニットをテストしようとしているので、テスト結果は他のBeanやオブジェクトの動作に関係するべきではありません。したがって、対応する応答を単純化したMockオブジェクトを使用して、それらをモックする必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.