Java 单元测试-使用不';t等于

Java 单元测试-使用不';t等于,java,unit-testing,testing,mocking,easymock,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

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

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

new TwoString("a","b")
因此,当调用methodToTest()时,它将创建这个TwoString对象,该对象将被传递给接口方法callMe(TwoString TwoString)

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

我正在使用EasyMock,这是一些java代码

“MyClassTest.java”

与MyClass.methodToTest()中生成的不同,因为TwoString.java不重写equals。

我可以使用

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);
    }
}
公共类MyClassTest{
私人MyClass MyClass;
私有TaskExecutor taskExecutorMock;
@以前
public void setUp()引发异常{
taskExecutorMock=createMock(TaskExecutor.class);
myClass=新的myClass(taskExecutorMock);
}
@试验
public void testRun()引发异常{
List myObjList=newarraylist();
添加(新的MyObj(“abc”,referencetoMethingElse));

taskExecutorMock.execute(new-SomeTask(referencetosmethingElse,new-SomeObj(“abc”,referencetosmethingElse,“whatever”));首先—您可能指的是“覆盖等于”,而不是实现,因为所有类都有一些等于的实现(如果不覆盖任何其他内容,则从对象继承)

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

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

assertEquals(expected, twoStr);
你会的

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

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

最后,第三方类基本上在TwoString的内部进行捣乱,试图弄清楚两者是否相等,这违反了Demeter定律


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

首先,您可能指的是“覆盖equals”,而不是实现,因为所有类都有一些equals的实现(如果不覆盖任何其他内容,则从对象继承)

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

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

assertEquals(expected, twoStr);
你会的

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

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

最后,第三方类基本上在TwoString的内部进行捣乱,试图弄清楚两者是否相等,这违反了Demeter定律


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

您可以通过Mockito 1.8中的参数捕捉器实现这一点


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

您可以通过Mockito 1.8中的参数捕捉器实现这一点


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

看看雅加达公共场所语言:

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

同时,我对任何想要检查所有字段的测试都持怀疑态度:在我看来,这似乎是在说“确保这个方法没有改变我不打算改变的东西。”这是一个有效的测试,在某种程度上是一个非常善意的测试,但这有点可怕
assertEquals(expected.getStringOne(), twoStr.getStringOne());
assertEquals(expected.getStringTwo(), twoStr.getStringTwo());
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")));