1

这是关于如何使用模拟对象对 Java 类进行单元测试的一般问题。
我可以用这个例子来总结我的问题。
假设我有一个名为 MyInterface.java 的接口和一个不覆盖 equals() 的“TwoString”对象

“双字符串.java”

   private String string1;
   private String string2;

   public TwoString(String string1, String string2) {
     this.string1 = string1;
     this.string2 = string2;
   }
   ...getters..setters..

“我的接口.java”

void callMe(TwoString twoString);

然后我有“MyClass.java”对象。它的构造函数接受 MyInterface 的具体实现。
MyClass methodToTest() 包含以某种方式创建 TwoString 对象的逻辑。假设它将被创建为

new TwoString("a","b")

因此,当调用 methodToTest() 时,它会创建这个 TwoString 对象,该对象将传递给接口方法callMe(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"));

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

我可以使用 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。

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

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

4

5 回答 5

5

可以使用自定义的 equals 匹配方法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

首先-您可能是指“覆盖等于”而不是实现,因为所有类都有一些等于的实现(如果它们不覆盖其他任何东西,它们会从 Object 继承)。

在这种情况下,答案很简单——所有值对象都应该真正实现 equals 和 hashcode。无论是像 TwoString 这样简单的对象,还是您提到的更复杂的对象,检查它是否等于其他对象应该是对象的责任。

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

assertEquals(expected, twoStr);

你会的

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

希望你能看到这至少在三个方面是不好的。首先,您基本上是在复制应该在类自己的 equals() 方法中的逻辑;在任何你想比较这些对象的地方,你都必须编写相同的代码。

其次,您只能看到对象的公共状态,很可能有一些私有状态导致两个明显相似的对象不相等(例如,电梯类可能具有可公开访问的“地板”属性,但私有“上升或下降” ”状态太)。

最后,第三方类基本上是在搞乱 TwoString 的内部结构,试图弄清楚事情是否相等,这违反了得墨忒耳法则。

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

于 2009-10-29T11:41:17.740 回答
2

看看 Jakarta Commons Lang:EqualsBuilder.reflectionEquals()

虽然我同意dtsazza的观点,即所有值对象都应该有一个equals()(and hashCode()) 方法,但它们并不总是适合测试:大多数值对象将基于键而不是所有字段的相等性。

同时,我对任何想要检查所有字段的测试持怀疑态度:在我看来,这似乎是在说“确保这种方法没有改变我不打算改变的东西”。这是一个有效的测试,在某种程度上是一个非常善意的测试,但你觉得需要它有点可怕。

于 2009-10-29T12:02:59.717 回答
0

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

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

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

于 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 回答