私はモックと偽のオブジェクトについて基本的な理解を持っていますが、モックをいつどこで使用すればよいのか、特にこのシナリオに当てはまるかどうかについてはよくわかりません。ここ。
ベストアンサー1
モックオブジェクトは、テストインタラクションテスト対象のクラスと特定のインターフェースの間。
たとえば、メソッドが正確に 1 回sendInvitations(MailServer mailServer)
呼び出されMailServer.createMessage()
、また、メソッドがMailServer.sendMessage(m)
正確に 1 回呼び出され、MailServer
インターフェースで他のメソッドが呼び出されないことをテストするとします。このような場合に、モック オブジェクトを使用できます。
MailServerImpl
モック オブジェクトを使用すると、実際の またはテストを渡す代わりにTestMailServer
、インターフェイスのモック実装を渡すことができますMailServer
。モックを渡す前にMailServer
、どのメソッド呼び出しが期待され、どの戻り値が返されるかがわかるように、モックを「トレーニング」します。最後に、モック オブジェクトは、期待されるすべてのメソッドが期待どおりに呼び出されたことをアサートします。
これは理論的には良いように思えますが、欠点もいくつかあります。
模擬欠点
モックフレームワークを導入している場合は、モックオブジェクトを使用するのがよいでしょう。毎回テスト対象のクラスにインターフェースを渡す必要があります。こうすると、必要でない場合でもインタラクションをテストする残念ながら、望ましくない(偶発的な)相互作用のテストは良くありません。なぜなら、その場合、実装が必要な結果を生成したかどうかではなく、特定の要件が特定の方法で実装されているかどうかをテストすることになるからです。
以下に擬似コードの例を示します。MySorter
クラスを作成し、それをテストするとします。
// the correct way of testing
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert testList equals [1, 2, 3, 7, 8]
}
// incorrect, testing implementation
testSort() {
testList = [1, 7, 3, 8, 2]
MySorter.sort(testList)
assert that compare(1, 2) was called once
assert that compare(1, 3) was not called
assert that compare(2, 3) was called once
....
}
(この例では、テストしたいのはクイック ソートなどの特定のソート アルゴリズムではないと想定しています。その場合、後者のテストが実際に有効になります。)
このような極端な例では、後者の例が間違っている理由は明らかです。 の実装を変更するとMySorter
、最初のテストは、正しくソートされていることを確認するのに非常に役立ちます。これがテストの重要な点です。テストにより、コードを安全に変更できます。一方、後者のテストは、いつも壊れて、実際に有害であり、リファクタリングを妨げます。
スタブとしてのモック
モックフレームワークでは、メソッドを何回呼び出すか、どのようなパラメータが期待されるかを正確に指定する必要がない、あまり厳密でない使用法も許可されることが多く、モックオブジェクトを作成して、スタブ。
sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)
テストしたいメソッドがあるとします。PdfFormatter
オブジェクトは招待状の作成に使用できます。テストは次のとおりです。
testInvitations() {
// train as stub
pdfFormatter = create mock of PdfFormatter
let pdfFormatter.getCanvasWidth() returns 100
let pdfFormatter.getCanvasHeight() returns 300
let pdfFormatter.addText(x, y, text) returns true
let pdfFormatter.drawLine(line) does nothing
// train as mock
mailServer = create mock of MailServer
expect mailServer.sendMail() called exactly once
// do the test
sendInvitations(pdfFormatter, mailServer)
assert that all pdfFormatter expectations are met
assert that all mailServer expectations are met
}
この例では、オブジェクトについてはあまり気にしないのでPdfFormatter
、あらゆる呼び出しを静かに受け入れ、sendInvitation()
この時点で呼び出されるすべてのメソッドに対して、適切な定型戻り値を返すようにトレーニングするだけです。トレーニングするメソッドのリストはどのようにして思いついたのでしょうか。テストを実行し、テストが成功するまでメソッドを追加し続けました。メソッドを呼び出す必要がある理由がわからないまま、メソッドに応答するようにスタブをトレーニングしたことに注目してください。テストで問題になったものをすべて追加しただけです。テストが成功し、満足しています。
sendInvitations()
しかし、後で 、または を使用する他のクラスを変更してsendInvitations()
、より複雑な PDF を作成するとどうなるでしょうか。 のより多くのメソッドがPdfFormatter
呼び出され、スタブがそれらを想定するようにトレーニングしていないため、テストが突然失敗します。そして通常、このような状況で失敗するテストは 1 つだけではなく、 メソッドを直接的または間接的に使用するすべてのテストが失敗しますsendInvitations()
。トレーニングを追加して、これらすべてのテストを修正する必要があります。また、どのメソッドが不要かわからないため、不要になったメソッドを削除できないことにも注意してください。これもまた、リファクタリングの妨げになります。
また、テストの可読性はひどく低下しました。そこには、書きたいからではなく、書かなければならないから書いたコードがたくさんありました。そのコードをそこに置きたいのは私たちではありません。モック オブジェクトを使用するテストは非常に複雑に見え、読みにくいことがよくあります。テストは、テスト対象のクラスの使用方法を読者が理解できるようにする必要があるため、シンプルでわかりやすいものでなければなりません。読みやすくなければ、誰もそれを保守しません。実際、保守するよりも削除する方が簡単です。
それをどうやって修正するか?簡単です:
- 可能な限り、モックではなく実際のクラスを使用するようにしてください。実際のクラスを使用
PdfFormatterImpl
してください。それが不可能な場合は、実際のクラスを変更して使用可能にします。テストでクラスを使用できない場合は、通常、そのクラスに何らかの問題があることを示しています。問題を修正することは、クラスを修正してテストを簡素化できるため、双方にとってメリットのある状況です。一方、問題を修正せずにモックを使用すると、実際のクラスを修正できず、さらにリファクタリングを妨げ、テストが複雑で読みにくくなります。 - 各テストでインターフェイスをモックするのではなく、インターフェイスの簡単なテスト実装を作成し、このテスト クラスをすべてのテストで使用してみてください。
TestPdfFormatter
何も実行しないものを作成します。そうすれば、すべてのテストに対して 1 回変更するだけで済み、スタブをトレーニングする長いセットアップでテストが乱雑になることがなくなります。
全体的に、モックオブジェクトには用途があるが、慎重に使用しないと、これらは、実装の詳細をテストする悪い習慣を助長し、リファクタリングを妨げ、読みにくく保守が難しいテストを生み出すことが多い。。
モックの欠点の詳細については、以下も参照してください。モックオブジェクト: 欠点と使用例。