EasyMock & EasyMock ClassExtension [Test]
■はじめに
-
EasyMockもJDK5.0に対応されたみたいなので、変更点を含め検証してみます。
■EasyMock & EasyMock ClassExtension 概要
JUnitは、テストケースの実行前後の状態の変化の検証をおこなうテストスタイルです。これに対し、EasyMock は、モックオブジェクトを組み合わせることにより、テストケースを実行することにより期待されるインタラクションも検証することを実現できます。
EasyMock は、Java Proxy メカニズムを利用し、インターフェイスに対するモックオブジェクトを動的に生成します。EasyMockはテストケースの実行結果、期待される動作(expectation)を設定するために、記録/再現(record/replay)のメタファを使用します。
まず、モックを生成したいオブジェクトのそれぞれに対してコントロールオブジェクトとモックオブジェクトを生成します。モックは代理となるオブジェクトのインターフェースを満たし、コントロールオブジェクトは追加機能を提供します。メソッド呼び出し時に期待される結果を設定するには、モックに予期される引数を指定することにより実現します。戻り値が必要であれば、この後にコントロールを呼び出すことになります。期待される結果の設定を終えたら、コントロールのreplayを呼び出します。この時点でモックは記録(recording)を完了してメインオブジェクトに応答する準備が整います。最後に、コントロールのverify()を呼び出すことにより、想定どおりの動作をするかを検証することができます。
(参考:Mocks Aren't Stubs http://www.martinfowler.com/articles/mocksArentStubs.html#UsingEasymock)
EasyMockは、具体的には以下のような特徴を持ちます。
詳細はEasyMockのドキュメントをご覧ください。
-
モックオブジェクトを作成する必要はありません。動的に生成します。
-
想定される戻り値、例外を設定できます。
-
同一のメソッド呼出に対する結果を変更することが可能です。
-
呼び出し回数を検証することができます。
-
メソッド呼出のディフォルト値を設定可能です。
-
メソッド呼出の順序を検証可能です。
EasyMock は、指定したインターフェイス(!)を持つモックオブジェクトを生成しますが、拡張ツール(EasyMock ClassExtension)を導入することにより、指定したクラス(!)のインターフェイスを持つモックオブジェクトを生成することが可能になります。
■サンプルコード
EasyMockのZIPファイルには、テストケースのサンプルが同梱されています。このサンプルに関して、以下で少し説明してみます。
テスト対象のクラスは、JDK5.0に対応していることを確認できます。addListenerメソッドでは、setter injection pattern を用い、ClassUnderTestから呼び出されるクラスをセットします。リリース用のコードでは、Collaboratorインターフェイスを持つ実際の実装クラスをセットし、テストケースクラスでは、代理のモックオブジェクトを渡すことになります。(ExampleTest16行目)
public class ClassUnderTest { private Set<Collaborator> listeners = new HashSet<Collaborator>();
private Map<String, byte[]> documents = new HashMap<String, byte[]>();
public void addListener(Collaborator listener) {
listeners.add(listener);
}
・・・・・
下記はテスト対象のクラスが使用するCollaboratorインターフェイスです。このインターフェイスを元にモックオブジェクトを生成します。
public interface Collaborator { void documentAdded(String title); void documentChanged(String title); void documentRemoved(String title); byte voteForRemoval(String title); byte voteForRemovals(String[] title); }
EasyMockを使用したテストケースクラスは、以下のとおりです。
まず、setUpメソッドで、モックを生成し、テスト対象のクラスがモックを利用できるようセットします。
テストケースメソッドでは、テストケースの実行に伴い期待される動作を設定します。次に、モックコントール対しreplayメソッドを呼び出し、テストケースを実行可能状態に変更します。そして、テストケースを実行後、モックコントロールに対し verifyメソッドを呼び出します。これで、テストケースが想定どおりの動作をするかを検証することができます。もちろん、JUnit のassertionメソッドでテストケースの実行結果想定どおりの状態変化が得られたか検証することができます。
public class ExampleTest extends TestCase { private ClassUnderTest classUnderTest; private MockControl control; private Collaborator mock; protected void setUp() { //上記のCollaboratorインターフェイスを指定し、モックコントローラとして生成します。 control = MockControl.createControl(Collaborator.class); //モックオブジェクトをコントローラから取得します mock = (Collaborator) control.getMock(); //テスト対象のクラスを生成します。 classUnderTest = new ClassUnderTest(); //テスト対象のクラスが実際のクラスの代替としてモックオブジェクトを使用 //できるように、テスト対象のクラスに生成したモックオブジェクトをセットします。 classUnderTest.addListener(mock); } ・・・・・ public void testVoteForRemoval() { //テストケースの実行結果、期待される動作を宣言します。 //"Document"が追加されることを期待することを宣言します。 mock.documentAdded("Document"); //期待される戻り値を宣言します。 control.expectAndReturn(mock.voteForRemoval("Document"), 42); //モックのdocumentRemovedメソッドが呼び出されることを期待することを宣言します。 mock.documentRemoved("Document"); //設定状態から呼び出し可能状態に変更します。 control.replay(); //テストケースを実行します。 classUnderTest.addDocument("Document", new byte[0]); //JUnitを用い、テストケースの実行結果を検証します。 assertTrue(classUnderTest.removeDocument("Document")); //呼び出し可能状態から検証可能状態に戻します。 control.verify(); } }
本バージョンでは、JDK5.0に対応されましたが、基本的な文法は変わりませんね。バージョンアップに伴う詳細な変更点を確認していきましょう。
■EasyMock 1.2RC2に関する変更点(since1.1) 2つのバージョンを提供
-
JDK1.3以降に対応
-
JDK5.0に対応
-
order of arguments for ArgumentsMatcher is like in EasyMock 1.0 now
-
stack traces are now only cut if the exception is thrown from EasyMock
-
convenience methods expectAndDefaultReturn() and expectAndDefaultThrow() allow setting default return values and throwables in one line of code
-
hashCode() implementation is changed for better performance
-
added Clover coverage reports
-
support for VarArgs
-
type safety: expectAndReturn() and expectAndDefaultReturn() for Objects now check whether the given return value is applicable. Trying control.expectAndReturn(mock.getString(), new Object())
will now generate an error at compile time instead of runtime. -
MockControl uses generics now, the mock object needs not to be casted anymore
■EasyMock ClassExtension の使用方法に関して
指定したクラスのモックオブジェクトを生成するには、 モックオブジェクトの生成の際に、org.easymock.MockControl の代わりに org.easymock.classextension.MockClassControl を使用してください。ソースコードは以下のようになります。上がインターフェイスを指定してモックオブジェクトを生成する例で、下がクラスを指定してモックオブジェクトを生成する例となります。
control = MockControl.createControl(CollaboratorInterface.class);
↓
control = MockClassControl.createControl(CollaboratorClass.class);
■Easymock class extenstion Requirements
The EasyMock class extension requires Java 1.3.1 or above, Easymock 1.2, JUnit 3.8.1, cglib 2.1.0 (2.0.x also works but cannot mock a class without visible constructor) and asm (use the version that matches your cglib version, you can also use cglib-nodep-2.1 which includes asm)
■References
-
Mocks Aren't Stub
http://www.martinfowler.com/articles/mocksArentStubs.html#UsingEasymock -
EasyMock と jMockの比較
http://www.jmock.org/easymock-comparison.html - Mock Objects in Unit Tests
インターフェイスだけでなくクラスを元にモックオブジェクトを生成可能なMocquerというツールに関して言及されています。
http://www.onjava.com/lpt/a/5550 - Approaches to Mocking
static mocking, dynamic mocking, AOPを利用したアプローチに関して言及されています。
http://www.onjava.com/lpt/a/4526 -
mockobjects(最近更新されていません。)
http://www.mockobjects.com/FrontPage.html
参考にさせていただきました。
そのお返しに補足させていただきたいと思います。
★JDK5.0 と EasyMock Class Extension-1.2を使用する場合
control = MockClassControl.createControl((Class)CollaboratorClass.class);
という風に引数をキャストしてやる必要があります。
簡単なことですが、参考までに。
by th (2005-11-01 14:22)
ご丁寧にご指摘ありがとうございました。
EasyMock2.0 もそろそろ正式版がリリースされそうですね。今後の動向に期待しています。
by tomo008 (2005-11-08 18:01)
突然、質問で済みません。
「EasyMock ClassExtension」の解説について、
私の環境では、
java.lang.NoClassDefFoundError: net/sf/cglib/proxy/Enhancer
が出てしまいます。
最初はデフォルトコンストラクタがないせいかとも思いましたが、
テスト用のクラスを作ってみても同じでした。
因みに、MockControl.createControlにインターフェイスを渡した場合は、
うまく動いています。
また、コンストラクタに引数を渡したい場合はどうすれば良いのでしょうか。
プログラムの設計に問題があるところもあるかと思いますが、
今回はテストのほうで対応したいと思っています。
環境:
JDK 1.4.2_12
JUnit 3.8.1
EasyMock 1.2_Java1.3
by あぶ (2006-10-07 23:48)
>>java.lang.NoClassDefFoundError : net/sf/cglib/proxy/Enhancer
環境:
Eclipse
JDK 1.4.2_10
JUnit 3.8.1
EasyMock1.2_Java1.3
EasyMockExtention1.2
本日、あぶさんと同じエラーをだしてしまいました。><
おなじ条件の場合の解決策は
http://sourceforge.net/ から
cglib-nodep-2.1_3.jar を検索し ダウンロードして追加していただくと正常に動作しました。(__)。
by アブサンへ (2006-10-12 15:11)