1

これは、モック オブジェクトを使用して Java クラスを単体テストする方法に関する一般的な質問です。
この例で問題を要約できます。
MyInterface.java というインターフェイスと、equals() をオーバーライドしない「TwoString」オブジェクトがあるとします。

"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());

しかし、インターフェイス メソッドが、文字列 1 として "a"、文字列 2 として "b" を含む TwoString の特定のインスタンスで呼び出されることを確認したいと思います。

この場合、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 メソッドを実際に呼び出していることをテストするにはどうすればよいですか

4

5 に答える 5

5

を使用して、カスタムの等しいマッチング方法を使用することができます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

于 2009-10-29T14:59:09.513 に答える
2

最初に-おそらく、実装ではなく「equalsをオーバーライドする」ことを意味します。これは、すべてのクラスにequalsの実装があるためです(他に何もオーバーライドしない場合、Objectから継承するもの)。

この場合の答えは簡単です。すべての値オブジェクトは、equals と hashcode を実装する必要があります。それが TwoString のような単純なものであろうと、あなたが言及したより複雑なオブジェクトであろうと、それが他のオブジェクトと等しいかどうかをチェックするのはオブジェクトの責任であるべきです。

他の唯一の代替手段は、基本的にテストコードでオブジェクトを分解することです-したがって、代わりに

assertEquals(expected, twoStr);

あなたがするだろう

assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());

うまくいけば、これが少なくとも 3 つの点で悪いことがわかると思います。まず、基本的に、クラス独自の equals() メソッドにあるはずのロジックを複製しています。これらのオブジェクトを比較したい場所にはどこでも、同じコードを書かなければなりません。

第 2 に、オブジェクトの公開状態のみを確認できます。明らかに似ている 2 つのオブジェクトが等しくないプライベート状態が存在する可能性があります (たとえば、Lift クラスはパブリックにアクセス可能な「フロア」属性を持つことができますが、プライベートな「上昇または下降」属性を持つ可能性があります)。状態も)。

最後に、サードパーティのクラスが基本的に TwoString の内部をいじって、物事が等しいかどうかを判断しようとすることは、デメテルの法則に違反しています。

オブジェクト自体は、純粋でシンプルな独自の equals() メソッドを実装する必要があります。

于 2009-10-29T11:41:17.740 に答える
2

Jakarta Commons Lang を見てみましょう: EqualsBuilder.reflectionEquals()

すべての値オブジェクトに(and ) メソッドが必要であるというdtsazzaの意見には同意しますが、それらは常にテストに適しているとは限りません。ほとんどの値オブジェクトは、すべてのフィールドではなく、キーに基づいて等価性を判断します。equals()hashCode()

同時に、私はすべてのフィールドをチェックしたいテストには懐疑的です: 「このメソッドが変更を計画していないものを変更していないことを確認してください」と言っているように思えます。これは有効なテストであり、あるレベルでは非常に意味のあるテストですが、その必要性を感じるのは少し怖いです。

于 2009-10-29T12:02:59.717 に答える
0

この場合、TwoString オブジェクトは非常に単純で、equals メソッドを簡単にオーバーライドできますが、より複雑なオブジェクトをチェックする必要がある場合はどうすればよいでしょうか。

オブジェクトが非常に複雑になり始めて、それらが他の場所から等しいかどうかを自明にチェックできなくなったら、おそらくリファクタリングして依存関係として注入する必要があります。これによりデザインが変更されますが、通常はそのほうがよいでしょう。

また、クラスの内部動作に関するある程度の知識に依存しているようです。上記は 2 つのクラス間の相互作用テストであり、ある程度は機能しますが、テストされるコンポーネントのセットが大きくなるほど、「単体」テストについて実際に話すことはできなくなります。ある時点で、単体テストの領域を離れ、統合テストを開始します。この場合、本格的なテスト ハーネスを使用し、特定の場所で動作を分離する方がよい場合があります...

于 2009-10-29T13:15:47.077 に答える
-1

これは、Mockito 1.8 の引数キャプターで実現できます。

http://mockito.googlecode.com/svn/branches/1.8.0/javadoc/org/mockito/Mockito.html#15

EasyMock を使用していることは知っていますが、Mockito への変更は簡単で、はるかに優れています。

于 2009-10-29T11:43:01.833 に答える