JMockとEasyMock:例だけでなく比較とハウツー

いくつかの外部コンポーネントを使用するコードをテストするときに、モックオブジェクトアプローチを使用することがよくあります。 まだ知らない人のために、簡単に説明します。これらは使用されるコンポーネントと同じインターフェースを持つオブジェクトですが、その動作はテストで完全に指定されており、その使用によりアプリケーションの実行に必要なインフラストラクチャ全体を上げることを回避できます。 さらに重要なことは、コードが特定の引数を使用してモックオブジェクトの特定のメソッドを呼び出すことを簡単かつ自然に制御できることです。



この記事では、Javaでモックを操作するための2つの一般的なライブラリーであるEasyMockJMockの比較分析を行います。 JUnitの基本的な知識は理解に十分であり、この記事を読んだ後、これらのライブラリの両方を使用する方法について非常に良いアイデアを得ることができます。



問題のタスク

テストする必要があるものの例として、およそ次の構造を持つアプリケーションを検討します。
1 2 3 4 5 6 7 8 9
      
      



 public class WayTooComplexClass { public WayTooComplexClass(String serverAddress) {/*...*/} public boolean save(long id, String data) {/*...*/} public String get(long id) {/*...*/} }
      
      



省略記号によって隠された実装で、ストレージとして単純なHTTP API(たとえば、 elliptics )を使用したある種のサービスを使用できるようにします。 このサーバーを常にテストのためにどこかに保管するには、少なくとも2つの問題があります。
  1. テストを実行するすべてのマシンからこのサーバーにアクセスできる必要があります。
  2. モックサーバーの動作の説明はコードの外部にあるため、特に1人の開発者がコードとサーバーでテストを更新し、もう1人の開発者が更新しない場合、さまざまなトラブルが発生する可能性があります。
この時点で、 「コードはユニットテストには複雑すぎます」™であると言って、あきらめており、書き込みで詰まっています。 幸いなことに、私たちはそれらの1つではないため、必要な要求に適切に応答する小さなHTTPサーバーをテストから直接生成します。 この例では、そのような目的で桟橋を使用しました。 このようなコードは、両方のモックライブラリを使用する場合に一般的です。



テスト用の模擬サーバー(読むことができません)

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
      
      



 public class WayTooComplexClassTest { static interface RequestHandler { String handle(String target, String data); } private static class MockHttpServerHandler extends AbstractHandler { private RequestHandler handler; public void setRequestHandler(RequestHandler handler) { this.handler = handler; } @Override public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch ) throws IOException, ServletException { String req = IOUtils.toString(request.getInputStream(), "UTF-8"); String result = handler.handle(target, req); response.setStatus(HttpStatus.ORDINAL_200_OK); response.setContentType("text/plain"); final ServletOutputStream outputStream = response.getOutputStream(); try { outputStream.print(result); } finally { outputStream.close(); } } } private static final MockHttpServerHandler SERVER_HANDLER = new MockHttpServerHandler(); @BeforeClass public static void startServer() throws Exception { org.mortbay.jetty.Server server = new org.mortbay.jetty.Server(); server.setHandler(SERVER_HANDLER); server.start(); } private final WayTooComplexClass wayTooComplex = new WayTooComplexClass("http://localhost/9001"); //Tests go here }
      
      



ここには、3つの興味深いポイントがあります。 1行目は3〜5行目で、 RequestHandler



インターフェイスを説明するRequestHandler



インターフェイスを示しています( たとえば、アドレスhttp: // habrahabr .ru / blogs / java / 136466 /ターゲットはボールド/ blogs / java / 136466 /になります )。リクエスト本文でユーザーが送信したデータ。 2行目は、7行目から32行目MockHttpServerHandler



クラスです。 RequestHandler



インストールされ、「ビジネスロジック」全体が委任され、その作業の結果がHTTP応答に記録されます。 3行目(36〜41行目)はstartServer



メソッドです。このメソッドは、アノテーションと名前から推測できるように、このクラスにリストされているテストが実行を開始してHTTPサーバーを開始する前に呼び出されます。



最初の最も簡単なテスト

理論的には、saveメソッドに隠されたコードがURL {serverAddress}/upload/{id}



を通過し、そこにdata



を渡すと仮定します。 これが実際に起こっているかどうかを確認してください。



ジョモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
      
      



 Mockery context = new JUnit4Mockery(); @Test public void testSaveWithJMock() { final long id = 13; final String data = "nanofilters"; final RequestHandler requestHandler = context.mock(RequestHandler.class); context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(returnValue("Saved " + id)); }}); SERVER_HANDLER.setRequestHandler(requestHandler); wayTooComplex.save(id, data); context.assertIsSatisfied(); }
      
      





最初の行では、テストを実行するJMockコンテキストを作成する必要があります。 このコンテキストはテスト全体には十分ですが、それなしではできません 。 同じモックのいくつかの問題を回避するために、各テストの前にコンテキストを再作成する必要があります(つまり、 @Before



アノテーションでマークされたメソッド内) @Before



行目では、インターフェイスのモックを簡単かつ自然に作成します。 次に、10〜13行目で、どの呼び出しが発生するかを説明します。 一見構文はあまり直感的ではありませんが、時間が経てば慣れます。 11行目では、引数("/upload/" + id)



および(data)



("/upload/" + id)



してhandle



メソッドを1回だけ呼び出すことを想定しています。 12行目では、最後の呼び出しが値("Saved " + id)



を返すと言います。 ここでは、ご想像のとおり、 一般的なセキュリティはありません 。 誤って間違った型の値をそこに渡し、shlopotat例外を使用して、実行時にのみそれを知ることができます。 ただし、戻り値が重要でない場合、これをまったく記述できません。JMockは自動的にデフォルト値( 0



false



null



または空の文字列)を返します。 次に、新しく作成したモックハンドラーを使用し、テスト対象のアプリケーションを呼び出して、19行目ですべての予想される呼び出しが行われたことを確認する必要があることをモックサーバーに伝えます。 テストクラス@RunWith(JMock.class)



アノテーション@RunWith(JMock.class)



を追加することで、後者を取り除くことができます



イージーモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
      
      



 IMocksControl control = EasyMock.createControl(); @Test public void testSaveWithEasyMock() { final long id = 15; final String data = "cold fusion reactor"; final RequestHandler requestHandler = control.createMock(RequestHandler.class); expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id); control.replay(); SERVER_HANDLER.setRequestHandler(requestHandler); wayTooComplex.save(id, data); control.verify(); }
      
      



最初の行では、JMockコンテキストに類似したコントロールを作成します。 次に、8行目でモックオブジェクトを作成します。10行目では、特定の引数を使用してhandleメソッドを呼び出し、この場合は特定の値を返すことを示します。 ここには典型的なセキュリティがあります。String以外の型の引数をandReturnに渡そうとすると、コンパイルエラーが発生します。

EasyMockでは、私の観点から、 予想される動作の仕様がより明確になっています。 ただし、このアプローチには欠点があります。11行目でわかるように、予想される動作の記録が終了したことを明示的に示す必要があります 。 すべての「ビジネスロジック」の後、17行目に進み、予想されるすべてのメソッドが呼び出されたことを確認します。 ちなみに、メソッドが何を返すかを気にしない場合、voidメソッドの場合は、 expect



コンストラクト
省略して、 requestHandler.handle("/upload/" + id, data)



呼び出すだけです。 さらに、 control



使用はオプション
であり、簡単にこれを実行できます。
 1 2 3 4 5
      
      



 final RequestHandler requestHandler = EasyMock.createMock(RequestHandler.class); //... EasyMock.replay(requestHandler); //... EasyMock.verify(requestHandler);
      
      







メソッド呼び出しに対するより複雑な応答



ここで、外部コンポーネントに障害が発生したときにアプリケーションが正しく動作することをテストする必要があるとします。 これを行うには、 handle



メソッドで例外を整理するだけで十分です。その後、jettyはhttpステータス500自体を設定します。



ジョモック

 1 2 3 4 5 6 7 8
      
      



 @Test public void testErrorHandlingWithJMock() { //... context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(throwException(new RuntimeException("Somebody set up us the bomb."))); }}); }
      
      



複雑なことは何もありません。 コメントは不要だと思います。 returnIterator



そのまま追加することもできますが、 Action



インターフェースを実装することで独自に調整することもできます。 確かに、それはあまり明確ではなく、ドキュメントはそれほど熱くありません



イージーモック

 1 2 3 4 5 6
      
      



 @Test public void testErrorHandlingWithEasyMock() { //... expect(requestHandler.handle("/upload/" + id, data)) .andThrow(new RuntimeException("All your base are belong to us.")); }
      
      



ここのすべてもシンプルですが、おそらく疑うように、メソッドが何も返さない場合(つまりvoid型を持つ場合)、別の方法で記述する必要があり、 スタイルのコミュニティは失われます。 次のようになります。
 1 2 3 4 5 6
      
      



 @Test public void testErrorHandlingWithEasyMock() { //... requestHandler.handle("/upload/" + id, data); expectLastCall() .andThrow(new RuntimeException("You have no chance to survive make your time.")); }
      
      



andThrow



加えて、 andThrow



使用することもできandDelegateTo



。これは、ごandDelegateTo



メソッド呼び出しを他のオブジェクト委任しますコンパイル中に一般的なセキュリティはありません! )And andAnswer



。 後者の場合、 IAnswer



インターフェイスを実装し、 answer



メソッド内にコードを記述する必要があります。 JMockよりも少し電力が少ないですが、はるかに便利です。



メソッドを呼び出すときの引数の一致



ここで、モックされたメソッドの呼び出しにどの引数を渡すべきか正確にはわからないとします。 唯一確実なことは、 target



引数にはどこかにid



が含まれている必要があるということです。



ジョモック

 1 2 3 4 5 6 7
      
      



 @Test public void testArgumentMatchingWithJMock() { //... context.checking(new Expectations() {{ one(requestHandler).handle(with(containsString(String.valueOf(id))), anything()); }}); }
      
      



JMock はハムクレストマッチャーを使用し、必要に応じて非標準のブラックジャックを追加してマッチャーを作成できます。



イージーモック

 1 2 3 4 5 6
      
      



 @Test public void testArgumentMatchingWithEasyMock() { //... expect(requestHandler.handle(contains(String.valueOf(id)), anyObject(String.class))) .andReturn(null); }
      
      



独自のマッチャーを使用するため、 既製のマッチャーのセットは小さくなります。 ただし、すでに存在するものは非常によく行われ、すべての基本的なニーズを満たします。残りは、自分で必要なマッチャーを実装することで、JMockと同様に満たすことができます。



呼び出し回数

ここで、条件を少し変更して、いくつかのメソッドを数回呼び出す必要があると言ってみましょう。 この例は現実からかなり遠ざかりましたが、単純なものを示すためにより重要なものを発明したくありません。



ジョモック

 1 2 3 4 5 6 7
      
      



 @Test public void testMultipleInvocationsWithJMock() { //... context.checking(new Expectations() {{ between(2, 5).of(requestHandler).handle(anything(), anything()); }}); }
      
      



気配りのある読者が推測できるように、前のすべての例で、単語one



はそれだけではなく、予想される呼び出しの数を示していました。 必要なコールの数はすべてサポートされています:ゼロであっても、いくつであっても。



イージーモック

 1 2 3 4 5 6
      
      



 @Test public void testMultipleInvocationsWithEasyMock() { //... expect(requestHandler.handle(anyObject(String.class), anyObject(String.class))) .andReturn(null).times(2, 5); }
      
      



ここでも、すべてが非常に単純ですが、わずかなマイナスがあります。 上からの制限なしに「少なくともn回」と言う方法はありませんatLeastOnce



メソッドがatLeastOnce



ことを考えると、これは少し奇妙です。 少なくとも3つの呼び出しを期待するには、次のコードのようなものを書く必要があります。



 1 2 3 4 5 6 7
      
      



 @Test public void testMultipleInvocationsWithEasyMock() { //... expect(requestHandler.handle(anyObject(String.class), anyObject(String.class))) .andReturn(null).times(3); expectLastCall().andReturn(null).anyTimes(); }
      
      







スタブ



メソッドがいつどのように呼び出されるかは原則としてあまり重要ではないことがよくありますが、メソッドが存在して何かを返すのは興味深いことです。



ジョモック

 1 2 3 4 5 6 7 8
      
      



 @Test public void testStubMethodsWithJMock() { //... context.checking(new Expectations() {{ allowing(requestHandler).handle(anything(), anything()); will(returnValue("There will be cake")); }}); }
      
      



これは、回数を決定するのと同じ方法で行われることが注目に値します。 allowing



代わりにallowing



ignoring



すると言うことができます。 また、必要に応じて、1つの大きなスタブでMockオブジェクト全体を簡単に作成できます。
 1 2 3 4 5 6 7
      
      



 @Test public void testStubObjectsWithJMock() { //... context.checking(new Expectations() {{ allowing(requestHandler); }}); }
      
      





イージーモック

 1 2 3 4 5 6
      
      



 @Test public void testStubMethodsWithEasyMock() { //... expect(requestHandler.handle(anyObject(String.class), anyObject(String.class))) .andStubReturn("Greetings, human."); }
      
      



スタイルがやや均一ではありませんが、非常に快適です。 ただし、オブジェクトを作成するときにのみオブジェクトをギャグにすることができます。
 1 2 3 4 5
      
      



 @Test public void testStubObjectsWithEasyMock() { final RequestHandler requestHandler = createNiceMock(RequestHandler.class); //... }
      
      



メソッドは、呼び出されると、そのタイプのデフォルト値( 0



false



またはnull



)を返します。 String



を含むnull



が返されnull



、このクラスのJMockでは、デフォルト値は空の文字列です。



メソッド呼び出し順序の確認



多くの場合、メソッドが呼び出される順序は重要であることが判明しています。 妄想に陥ったと仮定し、ファイルをダウンロードした直後に、ダウンロードして参照ファイルと比較することにしました。 アプリケーションが実際にこれを行うことを確認するには(そうでない場合、あまり信頼しません...) 、これを行うことができます:



ジョモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
      
      



 @Test public void testStrictOrderingWithJMock() { //... final Sequence paranoia = context.sequence("shhh-they-are-watching-us"); context.checking(new Expectations() {{ one(requestHandler).handle("/upload/" + id, data); will(returnValue("Saved " + id)); inSequence(paranoia); one(requestHandler).handle("/get/" + id, ""); will(returnValue(data)); inSequence(paranoia); }}); }
      
      



ここでは、実際に実行順序を確認する9行目と13行目に興味があります。 inSequence



は、モックオブジェクトへの呼び出しの順序のみを監視し、その直後にそれが示されることを覚えてinSequence



ことが重要です。 したがって、10個の異なる呼び出しが厳密な順序で行われることを追跡するには、 inSequence



10回記述
する必要があります。 しかし、 あなたは一度にいくつかのシーケンスで電話を切ることができます



イージーモック

 1 2 3 4 5 6 7
      
      



 @Test public void testStrictOrderingWithEasyMock() { //... EasyMock.checkOrder(requestHandler, true); EasyMock.expect(requestHandler.handle("/upload/" + id, data)).andReturn("Saved " + id); EasyMock.expect(requestHandler.handle("/get/" + id, "")).andReturn(data); }
      
      



ここでは、すべてがやや単純になりました各モックごとにテストを数回オン/オフできます 。 さらに、 control



全体を厳密にcontrol



ことができ
(最初の例を参照)、その後、含まれるすべてのモックの一般的な順序がチェックされます。 さらに、 createStrictMock



と言うことで、作成時にすぐにモックまたは制御を厳密にすることができます 。 もちろん、そのような量の緑は深刻なマイナスで希釈する必要があります。1つのモック(またはコントロール) が複数のシーケンスに参加することはできません 。 そのようなことすらありません。



メソッド呼び出しが許可される条件



場合によっては、アプリケーションの状態をシミュレートする必要があります。これは、いくつかのメソッドが呼び出されると変化し、他のメソッド(または同じメソッド)が呼び出されるとチェックされます。 アプリケーションを2つの楕円インスタンス(インスタンスはpanolaおよびyarboと呼ばれます)のいずれかに移動してファイルをダウンロードし、ダウンロードした同じインスタンスからダウンロードする必要があります。 これも確認できます。



ジョモック

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
      
      



 @Test public void testStatesWithJMock() { //... final States progress = context.states("progress").startsAs("none"); context.checking(new Expectations() {{ one(requestHandler).handle("/panola/upload/" + id, data); will(returnValue("Saved " + id)); when(progress.is("none")); then(progress.is("panola")); one(requestHandler).handle("/yarbo/upload/" + id, data); will(returnValue("Saved " + id)); when(progress.is("none")); then(progress.is("yarbo")); one(requestHandler).handle("/panola/get/" + id, ""); will(returnValue(data)); when(progress.is("panola")); then(progress.is("done")); one(requestHandler).handle("/yarbo/get/" + id, ""); will(returnValue(data)); when(progress.is("yarbo")); then(progress.is("done")); }}); }
      
      



9行目、13行目、17行目、および21行目を見る必要があります。それぞれの行で、アプリケーションが正しい状態になっwhen



を確認し、thenを使用して新しいアプリケーションを設定します。 とても快適 。 オートマトンプログラミングパラダイムの支持者は、おそらく自分の夢をテストする方法を見つけたと考えています。



EasyMock: アナログなし





マルチスレッドコードテスト



一般的に、マルチスレッドコードのテストは非常に困難です。特定のスレッドが1つまたは別のアクションを実行するタイミングにはさまざまな組み合わせがあるためです。 特に、すべてをクリティカルセクションとして宣言せず、最小限のロックで対処しようとする場合。 私たちは今、すばらしいアプリケーションから抽象化し、さまざまなライブラリーでのマルチスレッドのサポートがどのように行われているかを直接見ていきます。



ジョモック



正直なところ、私にとってそれはひどい苦痛でした:この問題に関するドキュメントとにかく、 他の多くのものについて)はかなり比fig的であり、したがって、私は試行錯誤で行動しなければなりませんでした、それほど単純ではありませんでした) 。 その結果、手動でダウンロードしたバージョンのhamcrest-core 1.3.0RC1と次のコードを使用して、手動でダウンロードしたバージョンのJMock 2.6.0-RC2によって保存されました。
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
      
      



 private Synchroniser synchroniser = new Synchroniser(); private Mockery context = new JUnit4Mockery() {{ this.setThreadingPolicy(synchroniser); }}; @Test public void testConcurrencyWithJMock() { //... final States progress = context.states("progress").startsAs("none"); context.checking(new Expectations() {{ //check something then(progress.is("done")); }}); //do something in multiple threads synchroniser.waitUntil(states.is("done"), TimeUnit.SECONDS.toMillis(1)); }
      
      



ここで最初の行で新しいシンクロナイザーを作成し、3番目で使用する必要があることをコンテキストに伝え、17番目で必要なすべての操作が完了するまで待機します。 Statesがこれを行うことを許可しているという事実は非常にクールであり、場合によっては、アプリケーションが作業を完了するのを待つ必要がなくなります。



イージーモック



すぐに使用できるマルチスレッド化を完全にサポートしています。 さらに、記録中にmakeThreadSafe(mock, false)



を使用して無効にし、必要に応じて、 checkIsUsedInOneThread(mock, true)



と言って、1つのスレッドからモックが使用されたことを確認し checkIsUsedInOneThread(mock, true)







模擬クラス



テストでモックに置き換える必要があるモジュールの開発者は、インターフェイスを作成することを気にせず、特定のクラスしか持っていないことが判明する場合があります。 幸いなことに、このクラスがfinalでなく、finalメソッドを持たない場合、モックを作成できます。



ジョモック

 1 2 3 4 5 6 7 8
      
      



 private Mockery context = new JUnit4Mockery() {{ this.setImposteriser(ClassImposteriser.INSTANCE); }}; @Test public void testClassMockingWithJMock() { //... }
      
      



必要なのは、コンテキストでsetImposteriser



メソッドを呼び出すことsetImposteriser



です。 例では、これは2行目で発生します。



イージーモック

 1 2 3 4 5 6
      
      



 import static org.easymock.classextension.EasyMock.*; @Test public void testClassMockingWithEasyMock() { //... }
      
      



別のクラスのメソッドを使用するだけで十分ですが、これは別のMavenアーティファクトにあります。 ただし、クラス拡張を使用すると、クラスとインターフェイスの両方で安全にmokaを作成できます。



部分的なモック



それでも時々、クラスメソッドの一部でのみモックを作成し、残りの部分には手を触れないようにする必要がある場合があります。



JMock: そのような機会はありません





イージーモック

 1 2 3 4 5 6 7
      
      



 @Test public void testPartialMockingWithEasyMock() { //... IntArraySorter sorter = EasyMock.createMockBuilder(IntArraySorter.class) .addMockedMethod("sort", int[].class).createMock(); //... }
      
      



すべてがシンプルで明確です。 また、どのパラメーターをどのコンストラクターに渡すかを指定することもできます。 モックされていないメソッドはすべて、このクラスに委任されます。



おわりに

これで例と比較が完了しました。 これまで読んだすべての人が、両方のライブラリの使用方法を知っており、将来、習得したスキルを使用して、多くの間違いから自分自身を救うことを願っています。



おそらく読者は、「プロジェクトで何を使うべきですか?」という質問に対する私からの回答を待っているのでしょう。 JMockまたはEasyMock?」 ここでの答えは非常にシンプルで明確です。「プロジェクトの要件に依存します。 これらの各ライブラリはツールであり、各ライブラリを使用できる必要があり、特定のタスクに使用するライブラリを選択する必要があります。



それだけです 興味深い質問やコメントを待っています!



All Articles