私はユニットテストに不慣れです。「モックオブジェクト」という言葉が頻繁に飛び交います。素人の言葉で、誰かがモックオブジェクトとは何か、そしてユニットテストを書くときにそれらが通常使用されるものを説明できますか?
私はユニットテストに不慣れです。「モックオブジェクト」という言葉が頻繁に飛び交います。素人の言葉で、誰かがモックオブジェクトとは何か、そしてユニットテストを書くときにそれらが通常使用されるものを説明できますか?
回答:
ユニットテストが初めてであり、「素人の言葉」で模擬オブジェクトを求められたと言ったので、素人の例を試してみましょう。
このシステムの単体テストを想像してください:
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ホットドッグ準備完了(ダミーホットドッグをウェイターに提供)
ウェイターがテストドライバー:こちらがフライドポテトです(ドライバーをテストするために、他の注文からのフライドポテトを提供します)。テストドライバーは、予期しないフライドポテトを記録します。テストが失敗しました!ウェイターが間違った料理を返した
モックオブジェクトとスタブの違いを明確に理解するのは難しいかもしれませんが、これに対応するスタブベースの例を対照的に使わないと、この答えはすでに長すぎます:-)
また、これはかなり単純な例であり、モックフレームワークにより、コンポーネントからの予想される動作のかなり洗練された仕様で包括的なテストをサポートできることにも注意してください。詳細については、モックオブジェクトとモックフレームワークに関する資料がたくさんあります。
モックオブジェクトは、実際のオブジェクトの代わりとなるオブジェクトです。オブジェクト指向プログラミングでは、モックオブジェクトは、実際のオブジェクトの動作を制御された方法で模倣するシミュレーションオブジェクトです。
通常、コンピュータープログラマーはモックオブジェクトを作成して他のオブジェクトの動作をテストします。これは、自動車の設計者がクラッシュテストダミーを使用して、車両の衝撃における人間の動的動作をシミュレートするのとほぼ同じです。
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());
}
}
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による、モックとは正確に何であり、スタブとの違いを説明したすばらしい記事を強くお勧めします。
コンピュータプログラムの一部を単体テストする場合、その特定の部分の動作だけをテストするのが理想的です。
たとえば、別のプログラムを使用して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を投稿していることです。
モックおよびスタブオブジェクトは、単体テストの重要な部分です。実際、それらは、ユニットのグループではなく、ユニットをテストしていることを確認するのに大いに役立ちます。
簡単に言えば、スタブを使用して他のオブジェクトへの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()
に失敗しても失敗しません。
「モックはスタブではない」へのリンクを介して提案された別の答えとして、モックは実際のオブジェクトの代わりに使用する「テストダブル」の形式です。それらがスタブオブジェクトなどの他の形式のテストダブルスと異なる点は、他のテストダブルスは状態検証(およびオプションでシミュレーション)を提供し、モックは動作検証(およびオプションでシミュレーション)を提供することです。
スタブを使用すると、スタブのいくつかのメソッドを任意の順序で(または不快にさえ)呼び出して、スタブが意図した値または状態をキャプチャしたかどうかを判断できます。対照的に、モックオブジェクトは、非常に特定の関数が特定の順序で、特定の回数で呼び出されることを期待しています。テストが終了したときにモックオブジェクトが正しい状態であったとしても、メソッドが異なるシーケンスまたはカウントで呼び出されたために、モックオブジェクトを使用したテストは「失敗」と見なされます。
このように、モックオブジェクトは、スタブオブジェクトよりもSUTコードに密接に結合していると見なされることがよくあります。確認しようとしていることに応じて、それは良いことでも悪いことでもあります。
phpとphpunitについては、phpunitのドキュメントで詳しく説明されています。phpunitのドキュメントはこちら
単純な言葉では、モックオブジェクトは元のオブジェクトの単なるダミーオブジェクトであり、その戻り値を返します。この戻り値はテストクラスで使用できます