如何在测试中比较两个没有实现equals方法的对象并基于它们的字段值进行“深度”比较?
原问题(因缺乏准确性而被关闭,为了记录目的而保留):
我正在尝试为大型项目内的各种clone()操作编写单元测试,并想知道是否有现有的类能够接受两个相同类型的对象,进行深度比较,并判断它们是否相同?
如何在测试中比较两个没有实现equals方法的对象并基于它们的字段值进行“深度”比较?
原问题(因缺乏准确性而被关闭,为了记录目的而保留):
我正在尝试为大型项目内的各种clone()操作编写单元测试,并想知道是否有现有的类能够接受两个相同类型的对象,进行深度比较,并判断它们是否相同?
Unitils拥有以下功能:
通过反射进行等式断言,提供不同选项,如忽略Java默认/ null值和忽略集合的顺序。
unitils
中的深度比较工具存在缺陷,因为它即使在变量没有可观察影响时也会进行比较。比较变量的另一个(不良)后果是不支持纯闭包(没有自己状态的闭包)。此外,它要求比较对象具有相同的运行时类型。我卷起袖子,创建了自己的版本的深度比较工具,解决了这些问题。 - beluchin我很喜欢这个问题!主要是因为它几乎从未被解答或者解答得很差。就像没有人想出来一样。是处于未开垦领域 :)
首先,不要考虑使用equals
方法。如javado中定义的那样,equals
方法的契约是等价关系(自反、对称和传递),而不是相等关系。若是相等关系,就必须还具有反对称性质。唯一一个实现equals
的方式,即使一个真正的相等关系,是在java.lang.Object
中的。即使你使用equals
来比较图中的所有内容,违反契约的风险仍然很高。如Josh Bloch所指出的在Effective Java中,equals的契约非常容易被破坏:
"根本没有办法扩展一个可实例化的类并添加一个方面同时保持equals合同"
此外,布尔方法到底有什么好处呢?实际上,将原始对象与克隆对象之间的所有区别封装起来也是不错的选择,您不认为吗?此外,在这里我假设您不想麻烦地编写/维护每个对象在图中的比较代码,而是想要找到一些随着源对象随时间变化而扩展的东西。
所以,您真正需要的是某种状态比较工具。该工具的实现方式取决于您的领域模型的性质和性能限制。根据我的经验,没有通用的魔力武器。对于大量迭代,它将会慢。但是对于测试克隆操作的完整性,它会做得相当好。您的两个最佳选项是序列化和反射。
您将遇到的一些问题:
XStream非常快速,并且与XMLUnit结合使用可以只需几行代码即可完成工作。 XMLUnit很好用,因为它可以报告所有差异,或者仅停在它发现的第一个差异处。并且其输出包括不同节点的xpath,这很好。默认情况下,它不允许无序集合,但可以配置为执行此操作。注入一个特殊的差异处理程序(称为)允许您指定处理差异的方式,包括忽略顺序。然而,一旦要做任何比最简单的自定义更复杂的事情,编写起来就变得困难,细节倾向于与特定领域对象相关联。
我个人喜欢使用反射循环遍历所有声明的字段,并深入每个字段,同时跟踪不同之处。警告:除非您喜欢堆栈溢出异常,否则不要使用递归。使用LinkedList
或类似的东西让事
@StatePolicy(unordered=true, ignore=false, exactTypesOnly=true)
private List<StringyThing> _mylist;
我认为这实际上是一个非常困难的问题,但完全可以解决!一旦你找到适合自己的方法,它就非常非常方便 :)
因此,祝你好运。如果你想出了什么绝妙的方法,请不要忘记分享!
在AssertJ中,您可以执行以下操作:
Assertions.assertThat(expectedObject).isEqualToComparingFieldByFieldRecursively(actualObject);
可能并非所有情况都适用,但它可以适用于比你想象的更多的情况。
以下是文档中的说明:
断言被测试的对象(实际值)基于递归的属性/字段比较(包括继承的属性/字段),与给定对象相等。如果实际值的equals实现不适合您,则这可能很有用。对具有自定义equals实现的字段不应用递归属性/字段比较,即将使用重写的equals方法而不是逐个字段比较。
递归比较处理循环。默认情况下,浮点数精度为1.0E-6,双精度为1.0E-15。
您可以分别使用usingComparatorForFields(Comparator,String ...)和usingComparatorForType(Comparator,Class)为每个(嵌套的)字段或类型指定自定义比较器。
要比较的对象可以是不同类型的,但必须具有相同的属性/字段。例如,如果实际对象有一个名为name的字符串字段,则希望其他对象也有一个该字段。如果一个对象有一个字段和一个具有相同名称的属性,则属性值将优先于字段。
isEqualToComparingFieldByFieldRecursively
е·Іиў«ејѓз”ЁпјЊиЇ·дЅїз”Ё assertThat(expectedObject).usingRecursiveComparison().isEqualTo(actualObject);
д»Јж›ї :) - dargmuesli你可以使用EqualsBuilder.reflectionEquals()来简单地覆盖类的equals()方法,具体请参考这里:
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
我刚刚需要实现Hibernate Envers修订的两个实体实例之间的比较。我开始编写自己的区别,但后来发现了以下框架。
https://github.com/SQiShER/java-object-diff
你可以比较两个相同类型的对象,它会显示出改动、添加和移除。如果没有改动,则对象相等(理论上)。在检查中提供了应该忽略的getter的注释。该框架的应用远不止于相等性检查,例如我正在使用它生成变更日志。对于比较JPA实体,它的性能还可以,但请确保首先将它们从实体管理器中分离。
http://www.unitils.org/tutorial-reflectionassert.html
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Hamcrest拥有Matcher samePropertyValuesAs。但它依赖于JavaBeans Convention(使用getter和setter)。如果要比较的对象没有属性的getter和setter,这将无法工作。
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class UserTest {
@Test
public void asfd() {
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertThat(user1, samePropertyValuesAs(user2)); // all good
user2 = new User(1, "John", "Do");
assertThat(user1, samePropertyValuesAs(user2)); // will fail
}
}
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
isFoo
读取方法来读取Boolean
属性的POJO。自2016年以来一直有一个PR来修复它。https://github.com/hamcrest/JavaHamcrest/pull/136 - Snekse我正在使用XStream:
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
XStream xstream = new XStream();
String oxml = xstream.toXML(o);
String myxml = xstream.toXML(this);
return myxml.equals(oxml);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
XStream xstream = new XStream();
String myxml = xstream.toXML(this);
return myxml.hashCode();
}
public static boolean deepCompare(Object o1, Object o2) {
try {
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ObjectOutputStream oos1 = new ObjectOutputStream(baos1);
oos1.writeObject(o1);
oos1.close();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(baos2);
oos2.writeObject(o2);
oos2.close();
return Arrays.equals(baos1.toByteArray(), baos2.toByteArray());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@ToString @Getter @Setter
class foo{
boolean foo1;
String foo2;
public boolean deepCompare(Object other) { //for cohesiveness
return other != null && this.toString().equals(other.toString());
}
}