这个问题很接近,但仍然不是我想要的。我想以一种通用的方式断言两个 bean 对象是等价的。如果不是,我想要一个详细的错误消息来解释差异,而不是布尔“等于”或“不等于”。
12 回答
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
import static org.junit.Assert.assertThat;
@Test
public void beansAreTheSame(){
MyDomianClass bean1 = new MyDomainClass();
MyDomianClass bean2 = new MyDomainClass();
//TODO - some more test logic
assertThat(bean1, samePropertyValuesAs(bean2));
}
我建议你使用unitils库:
http://www.unitils.org/tutorial-reflectionassert.html
公共类用户{ 私人长ID; 私有字符串优先; 私有字符串最后; 公共用户(长ID,字符串第一,字符串最后){ 这个.id = id; this.first = 第一个; this.last = 最后一个; } }
用户 user1 = new User(1, "John", "Doe"); 用户 user2 = new User(1, "John", "Doe"); assertReflectionEquals(user1, user2);
也可以看看:
您可以使用Commons Lang将它们都ToStringBuilder
转换为可读字符串,然后assertEquals()
在两个字符串上使用。
如果您喜欢 XML,可以使用java.lang.XMLEncoder将您的 bean 转换为 XML,然后比较两个 XML 文档。
就个人而言,我更喜欢它,ToStringBuilder
因为它可以让您更好地控制格式并允许您执行诸如对集合中的元素进行排序以避免误报之类的事情。
我建议将 bean 的每个字段放在不同的行中,以便更简单地比较它们(有关详细信息,请参阅我的博客)。
您可以像这样设置所有字段:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.hamcrest.Matchers.is;
@Test
public void test_returnBean(){
arrange();
MyBean myBean = act();
assertThat(myBean, allOf(hasProperty("id", is(7L)),
hasProperty("name", is("testName1")),
hasProperty("description", is("testDesc1"))));
}
我认为,最通用的方法是反映 bean 成员并逐个测试它们是否相等。通用语言EqualsBuilder
是一个好的开始,它应该不是什么大问题,将它(在源代码级别)适应您的要求(报告差异而不是返回等于结果)。
对于单元测试,这可以通过 JUnit 和 Mockito 使用 ReflectionEquals 来完成。当以以下方式实现时,当任何字段不相等时,它将转储对象的 JSON 表示,这使得很容易找到有问题的差异。
import static org.junit.Assert.assertThat;
import org.mockito.internal.matchers.apachecommons.ReflectionEquals;
assertThat("Validating field equivalence of objects", expectedObjectValues, new ReflectionEquals(actualObjectValues));
既然你不喜欢你引用的问题中的答案,为什么不在toXml
每个bean中都有一个方法,将它们变成一个xml文件,然后使用xmlUnit进行比较。
您可以在此处获取有关比较 xml 文件的更多信息:
您并没有真正主张平等,而是在做“差异”。显然,“相同”的含义取决于每种类型的特定逻辑,并且差异的表示也可能有所不同。此要求与传统的 equals() 之间的一个主要区别是,通常 equals() 会在看到第一个差异时立即停止,您将需要继续并比较每个字段。
我会考虑重用一些 equals() 模式,但我怀疑您需要编写自己的代码。
我在这里假设两个 bean 属于同一类型,在这种情况下,只有成员变量值在 bean 实例之间会有所不同。
定义一个名为 BeanAssertEquals 的 util 类(带有私有 ctor 的 public static final)。使用Java反射来获取每个bean中每个成员变量的值。然后在不同bean中相同成员变量的值之间执行equals()。如果相等失败,请提及字段名称。
注意:成员变量通常是私有的,因此您需要使用反射来临时更改私有成员的可访问性。
此外,根据您希望断言工作的细粒度,您应该考虑以下几点:
成员变量的相等性不在 bean 类中而是在所有超类中。
数组中元素的相等性,以防成员变量是数组类型。
对于跨 bean 的给定成员的两个值,您可能会考虑使用 BeanAssertEquals.assertEquals(value1, value2) 而不是 value1.equals(value2)。
(以我上面对Andreas_D的评论为基础)
/** Asserts two objects are equals using a reflective equals.
*
* @param message The message to display.
* @param expected The expected result.
* @param actual The actual result
*/
public static void assertReflectiveEquals(final String message,
final Object expected, final Object actual) {
if (!EqualsBuilder.reflectionEquals(expected, actual)) {
assertEquals(message,
reflectionToString(expected, ToStringStyle.SHORT_PREFIX_STYLE),
reflectionToString(actual, ToStringStyle.SHORT_PREFIX_STYLE));
fail(message + "expected: <" + expected + "> actual: <" + actual + ">");
}
}
这是我使用的,我相信它满足所有基本要求。通过在反射 ToString 上执行断言,Eclipse 将突出显示差异。
虽然 Hamcrest可以提供更好的信息,但这确实需要更少的代码。
我要问的第一个问题是,你想在 Bean 上做“深度”等于吗?它是否有需要测试的子 bean?您可以覆盖 equals 方法,但这仅返回一个布尔值,因此您可以创建一个“比较器”,它可能会引发异常,并显示有关不相等的消息。
在以下示例中,我列出了几种实现 equals 方法的方法。
如果你想检查它们是否是同一个对象实例,那么来自 Object 的普通 equals 方法会告诉你。
objectA.equals(objectB);
如果您想编写一个客户equals方法来检查对象的所有成员变量是否使它们相等,那么您可以像这样覆盖equals方法...
/**
* Method to check the following...
* <br>
* <ul>
* <li>getTitle</li>
* <li>getInitials</li>
* <li>getForename</li>
* <li>getSurname</li>
* <li>getSurnamePrefix</li>
* </ul>
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if ( (!compare(((ICustomer) obj).getTitle(), this.getTitle()))
|| (!compare(((ICustomer) obj).getInitials(), this.getInitials()))
|| (!compare(((ICustomer) obj).getForename(), this.getForename()))
|| (!compare(((ICustomer) obj).getSurname(), this.getSurname()))
|| (!compare(((ICustomer) obj).getSurnamePrefix(), this.getSurnamePrefix()))
|| (!compare(((ICustomer) obj).getSalutation(), this.getSalutation())) ){
return false;
}
return true;
}
最后一个选项是使用 java 反射检查 equals 方法中的所有成员变量。如果您真的想通过其 bean get/set 方法检查每个成员变量,这非常好。当两个对象的测试相同时,它不会(我不认为)允许您检查私有成员变量。(如果你的对象模型有循环依赖,不要这样做,它永远不会返回)
注意:这不是我的代码,它来自...
Java Reflection equals public static boolean equals(Object bean1, Object bean2) { // 处理琐碎的情况 if (bean1 == bean2) return true;
if (bean1 == null)
return false;
if (bean2 == null)
return false;
// Get the class of one of the parameters
Class clazz = bean1.getClass();
// Make sure bean1 and bean2 are the same class
if (!clazz.equals(bean2.getClass()))
{
return false;
}
// Iterate through each field looking for differences
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
// setAccessible is great (encapsulation
// purists will disagree), setting to true
// allows reflection to have access to
// private members.
fields[i].setAccessible(true);
try
{
Object value1 = fields[i].get(bean1);
Object value2 = fields[i].get(bean2);
if ((value1 == null && value2 != null) ||
(value1 != null && value2 == null))
{
return false;
}
if (value1 != null &&
value2 != null &&
!value1.equals(value2))
{
return false;
}
}
catch (IllegalArgumentException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
}
return true;
这样做并不能告诉您差异的原因的一件事,但是当您发现不相等的部分时,可以通过向 Log4J 发送消息来完成。
xtendbeans库可能对这种情况感兴趣:
AssertBeans.assertEqualBeans(expectedBean, actualBean);
这会产生一个 JUnit ComparisonFailure
à la:
expected:
new Person => [
firstName = 'Homer'
lastName = 'Simpson'
address = new Address => [
street = '742 Evergreen Terrace'
city = 'SpringField'
]
]
but was:
new Person => [
firstName = 'Marge'
lastName = 'Simpson'
address = new Address => [
street = '742 Evergreen Terrace Road'
city = 'SpringField'
]
]
您也可以使用它来获取用于其他目的的文本表示:
String beanAsLiteralText = new XtendBeanGenerator().getExpression(yourBean)
With this library you can use the above syntactically valid object initialization code fragment to copy/paste it into a (Xtend) source class for the expectedBean
, but you don't not have to, it can perfectly well be used without Xtend as well.