单元测试 - 使用不覆盖等于的对象检查方法调用

时间:2009-10-29 11:29:09

标签: java unit-testing testing mocking easymock

这是关于如何使用模拟对象对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对象。

我基本上想要模仿界面。使用此mock创建一个MyClass对象。然后验证是否使用TwoString的特定实例调用了mock方法。

我正在使用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"));

与MyClass.methodToTest()中生成的不同,因为TwoString.java不会覆盖equals。

我可以使用

跳过TwoString实例上的问题
myInterfaceMock.callMe((TwoString)anyObject());

但我想确保使用TwoString的特定实例调用接口方法,该实例包含“a”作为string1而“b”作为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 = myClass.run()使用myObjList中包含的数据创建的对象。
假设SomeObj来自第三方库,它不会覆盖equals。

我想确保使用SomeObj的特定实例调用taskExecutorMock.execute()方法

如何测试myClass.run()实际上是否使用正确的实例调用taskExecutorMock方法

5 个答案:

答案 0 :(得分: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")));

有些细节可能不正确,但从本质上讲,这就是我以前做过的。还有另一个例子和更全面的解释in the EasyMock documentation。只需搜索IArgumentMatcher

答案 1 :(得分:2)

首先 - 你可能意味着“覆盖等于”而不是实现,因为所有类都有 equals的某些实现(如果它们不覆盖任何其他东西,则它们从Object继承的那个)。 / p>

这种情况下的答案很简单 - 所有值对象确实应该实现equals和hashcode。无论是像TwoString这样的简单对象,还是你提到的更复杂的对象,它都应该由对象负责检查它是否与其他对象相等。

唯一的另一种选择是基本上解构测试代码中的对象 - 而不是

assertEquals(expected, twoStr);
你会做的

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

希望您至少可以通过三种方式看到这种情况很糟糕。首先,你基本上复制了应该在类自己的equals()方法中的逻辑;在任何你想要比较这些对象的地方,你都必须编写相同的代码。

其次,你只能看到对象的公共状态,可能会有一些私有状态导致两个明显类似的对象不相等(例如,一个Lift类可以有一个公共可访问的“楼层“属性,但私人”上升或下降“状态”。

最后,违反了德米特法则,第三方课程基本上是在弄乱TwoString的内部,试图弄清楚事情是否平等。

对象本身应该实现自己的equals()方法 - 纯粹而简单。

答案 2 :(得分:2)

看看Jakarta Commons Lang:EqualsBuilder.reflectionEquals()

虽然我同意 dtsazza 所有值对象都应该有equals()(和hashCode())方法,但它们并不总是适合测试:大多数值对象都会密钥上的基本相等,而不是所有字段。

与此同时,我对任何想要检查所有字段的测试都持谨慎态度:在我看来,“确保这种方法没有改变我不打算改变的东西。 “这是一个有效的测试,并且在某种程度上是一个非常好的测试,但是你觉得它需要它有点可怕。

答案 3 :(得分:0)

  

在这种情况下,TwoString对象非常简单,并且很容易覆盖equals方法 - 但是如果我需要检查更复杂的对象该怎么办。

一旦你的对象开始变得如此复杂以至于你无法轻易地检查它们是否与其他地方相等,你应该重构并将它们作为依赖注入。这会改变设计,但通常情况会更好。

您似乎也依赖于对类的内部行为的一些了解。以上是两个类之间的交互测试,它仍然有效,但是测试组件的集合越大,你仍然可以更少地谈论“单元”测试。在某一点上,你离开了单元测试领域并开始进行集成测试,在这种情况下,你可能会更好地使用完整的测试工具并在某些地方隔离行为......

答案 4 :(得分:-1)

您可以使用Mockito 1.8中的参数捕获器实现此目的。

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

我知道你正在使用EasyMock,但改用Mockito很容易,而且更好!