単体テスト-等しいをオーバーライドしないオブジェクトでメソッド呼び出しを確認します
-
10-07-2019 - |
質問
これは、モックオブジェクトを使用してJavaクラスを単体テストする方法に関する一般的な質問です。
この例で問題を要約できます。
MyInterface.javaと呼ばれるインターフェイスと" TwoString"があるとします。 equals()をオーバーライドしないオブジェクト
" TwoString.java"
private String string1;
private String string2;
public TwoString(String string1, String string2) {
this.string1 = string1;
this.string2 = string2;
}
...getters..setters..
" MyInterface.java"
void callMe(TwoString twoString);
[" MyClass.java" オブジェクトがあります。そのコンストラクターは、MyInterfaceの具体的な実装を受け入れます。
MyClass methodToTest()には、何らかの方法でTwoStringオブジェクトを作成するロジックが含まれています。次のように作成されるとします
new TwoString("a","b")
したがって、methodToTest()が呼び出されると、インターフェイスメソッド callMe(TwoString twoString)に渡されるこのTwoStringオブジェクトが作成されます。
基本的にインターフェイスをモックしたい。このモックでMyClassオブジェクトを作成します。次に、TwoStringの特定のインスタンスでモックメソッドが呼び出されることを確認します。
EasyMockを使用していますが、これはJavaコードです
" MyClassTest.java"
public void test() throws Exception {
MyInterface myInterfaceMock = createMock(MyInterface.class);
MyClass myClass = new MyClass(myInterfaceMock);
myInterfaceMock.callMe(new TwoString("a","b")); <--- fails here
expectLastCall();
replay(myInterfaceMock);
myClass.methodToTest();
verify(myInterfaceMock);
ここに問題があります。呼び出しで期待しているTwoStringオブジェクト
myInterfaceMock.callMe(new TwoString("a","b"));
TwoString.javaはequalsをオーバーライドしないため、はMyClass.methodToTest()で生成されたものとは異なります。
を使用してTwoStringインスタンスの問題をスキップできます
myInterfaceMock.callMe((TwoString)anyObject());
しかし、「a」を含むTwoStringの特定のインスタンスを使用して、インターフェイスメソッドが確実に呼び出されるようにします。 string1および&quot; b&quot;としてstring2として。
この場合、TwoStringオブジェクトは非常にシンプルで、equalsメソッドを簡単にオーバーライドできますが、より複雑なオブジェクトをチェックする必要がある場合はどうなりますか。
ありがとう
編集:
この例を使用して読みやすくします
public class MyClassTest {
private MyClass myClass;
private TaskExecutor taskExecutorMock;
@Before
public void setUp() throws Exception {
taskExecutorMock = createMock(TaskExecutor.class);
myClass = new MyClass(taskExecutorMock);
}
@Test
public void testRun() throws Exception {
List<MyObj> myObjList = new ArrayList<MyObj>();
myObjList.add(new MyObj("abc", referenceToSomethingElse));
taskExecutorMock.execute(new SomeTask(referenceToSomethingElse, ???new SomeObj("abc", referenceToSomethingElse, "whatever"))); <--- ??? = object created using data in myObjList
expectLastCall();
replay(taskExecutorMock);
myClass.run(myObjList);
verify(taskExecutorMock);
}
}
??? SomeObj = myObjListに含まれるデータを使用してmyClass.run()によって作成されたオブジェクト。
SomeObjがサードパーティのライブラリに由来し、equalsをオーバーライドしないとしましょう。
そのSomeObjの特定のインスタンスでtaskExecutorMock.execute()メソッドが呼び出されることを確認したい
myClass.run()が実際に正しいインスタンスでtaskExecutorMockメソッドを呼び出していることをテストするにはどうすればよいですか
解決
org.easymock.IArgumentMatcher
を使用して、カスタムの等値一致メソッドを使用できます。
次のようになります。
private static <T extends TwoString> T eqTwoString(final TwoString twoString) {
reportMatcher(new IArgumentMatcher() {
/** Required to get nice output */
public void appendTo(StringBuffer buffer) {
buffer.append("eqTwoString(" + twoString.getString1() + "," + twoString.getString2() + ")");
}
/** Implement equals basically */
public boolean matches(Object object) {
if (object instanceof TwoString) {
TwoString other = (TwoString) object;
// Consider adding null checks below
return twoString.getString1().equals(object.getString1()) && twoString.getString2().equals(object.getString2());
}
else {
return false;
}
}
});
return null;
}
次のように使用されます:
myInterfaceMock.callMe(eqTwoString(new TwoString("a","b")));
一部の詳細は正しくない場合がありますが、本質的には以前に行った方法です。別の例と、より詳細な説明が EasyMockのドキュメントにあります。 IArgumentMatcher
を検索するだけです。
他のヒント
最初に-「オーバーライドが等しい」という意味でしょう。すべてのクラスが等しい( some の実装を持っているので、実装するのではなく)(他に何もオーバーライドしない場合、Objectから継承するもの)。
この場合の答えは簡単です。すべての値オブジェクトは、実際にequalsとハッシュコードを実装する必要があります。 TwoStringのような単純なものであっても、より複雑なオブジェクトであっても、他のオブジェクトと等しいかどうかを確認するのはオブジェクトの責任です。
他の唯一の選択肢は、テストコードでオブジェクトを基本的に分解することです。そのため、
assertEquals(expected, twoStr);
する
assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());
うまくいけば、少なくとも3つの点でこれが悪いことがわかります。まず、クラスのequals()メソッドにあるはずのロジックを基本的に複製しています。これらのオブジェクトを比較したい場所ならどこでも同じコードを書く必要があります。
次に、オブジェクトのパブリック状態のみが表示されます。2つの見かけ上同様のオブジェクトが等しくないプライベート状態が存在する可能性があります(たとえば、Liftクラスはパブリックにアクセスできる&quot; floor&quot;属性ですが、プライベートな「going up or down」状態も同様です。
最後に、サードパーティクラスが基本的に物事が等しいかどうかを判断しようとするTwoStringの内部をいじるのは、デメテルの法則に違反しています。
オブジェクト自体は、純粋でシンプルな独自のequals()メソッドを実装する必要があります。
ジャカルタコモンズラングをご覧ください: EqualsBuilder.reflectionEquals()
すべての値オブジェクトには equals()
(および hashCode()
)メソッドが必要であるという dtsazza に同意しますが、常にテストに適しているわけではありません。ほとんどの値オブジェクトは、すべてのフィールドではなくキーに基づいて同等性を判断します。
同時に、私はすべてのフィールドをチェックしたいテストに不満を抱いています:&quot;このメソッドが変更する予定のないものを変更していないことを確認してください。&quot;これは有効なテストであり、あるレベルでは非常に意味のあるテストですが、その必要性を感じるのは少し怖いです。
この場合、TwoStringオブジェクトは非常にシンプルで、equalsメソッドを簡単にオーバーライドできますが、より複雑なオブジェクトをチェックする必要がある場合はどうなりますか。
オブジェクトが非常に複雑になり始めて、他のオブジェクトと同じかどうかを簡単に確認できない場合は、おそらくリファクタリングして依存関係として注入する必要があります。これによりデザインが変更されますが、通常はそれで改善されます。
また、クラスの内部動作に関する知識に依存しているようです。上記は2つのクラス間の相互作用テストであり、依然として機能しますが、テストするコンポーネントのセットが大きくなればなるほど、「ユニット」について話すことができなくなります。テスト。特定の時点で、単体テストの領域を離れて統合テストを開始します。その場合、完全なテストハーネスを使用して特定の場所で動作を分離する方が良いかもしれません...
Mockito 1.8の引数キャプターでこれを実現できます。
http:// mockito .googlecode.com / svn / branches / 1.8.0 / javadoc / org / mockito / Mockito.html#15
EasyMockを使用していることは知っていますが、Mockitoへの変更は簡単で、はるかに優れています!