繁体   English   中英

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

[英]Unit Test - Check a method call with an object that doesn't override equals

这是一个关于如何使用模拟对象对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()时,它将创建此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不会覆盖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方法

可以通过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

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

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

唯一的其他选择是基本上在测试代码中解构对象-因此,代替

assertEquals(expected, twoStr);

你会做

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

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

其次,您只能看到对象的公共状态,很可能有一些私有状态导致两个看似相似的对象不相等(例如,Lift类可以具有公共可访问的“ floor”属性,而私有“向上或向下” ”)。

最后,对于第三方类而言,基本上是在与TwoString的内部弄混以试图确定事物是否相等,这违反了Demeter的定律。

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

看看Jakarta Commons Lang: EqualsBuilder.reflectionEquals()

尽管我同意dtsazza的观点 ,即所有值对象都应具有equals() (和hashCode() )方法,但它们并不总是适合于测试:大多数值对象将基于键而不是基于所有字段。

同时,我对要检查所有字段的任何测试都持反对态度:在我看来,这是“确保此方法不会更改我不打算更改的内容”。 这是一个有效的测试,在某种程度上说是一个非常好的测试,但是您感到有一点恐惧的感觉。

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

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

您似乎还依赖于有关类的内部行为的一些知识。 上面是两个类之间的交互测试,它们仍然可以工作,但是您所测试的组件集越大,您实际上谈论“单元”测试的内容就越少。 在某个时候,您离开了单元测试的领域,开始进行集成测试,在这种情况下,最好使用全面的测试工具,并在某些位置隔离行为,从而更好。

您可以在Mockito 1.8中使用参数捕获器来实现。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM