假设我有一个没有equals()方法的类,没有源代码。我想在该类的两个实例上断言相等。
我可以做多个断言:
assertEquals(obj1.getFieldA(), obj2.getFieldA());
assertEquals(obj1.getFieldB(), obj2.getFieldB());
assertEquals(obj1.getFieldC(), obj2.getFieldC());
...
我不喜欢这个解决方案,因为如果早期的断言失败,我就不会得到完全相等的图片。
我可以自己手动比较并跟踪结果:
String errorStr = "";
if(!obj1.getFieldA().equals(obj2.getFieldA())) {
errorStr += "expected: " + obj1.getFieldA() + ", actual: " + obj2.getFieldA() + "\n";
}
if(!obj1.getFieldB().equals(obj2.getFieldB())) {
errorStr += "expected: " + obj1.getFieldB() + ", actual: " + obj2.getFieldB() + "\n";
}
...
assertEquals("", errorStr);
这给了我完整的平等图片,但是很笨重(我甚至没有考虑到可能的空问题)。第三种选择是使用Comparator,但compareTo()不会告诉我哪些字段不相等。
有没有更好的做法可以从对象中获得我想要的东西,而无需子类化和覆盖equals(ugh)?
答案 0 :(得分:43)
Mockito提供了一个反射匹配器:
Assert.assertThat(expected, new ReflectionEquals(actual, excludeFields));
答案 1 :(得分:35)
我通常使用org.apache.commons.lang3.builder.EqualsBuilder来实现这个用例
Assert.assertTrue(EqualsBuilder.reflectionEquals(expected,actual));
答案 2 :(得分:28)
这里有很多正确的答案,但我也想添加我的版本。这是基于Assertj。
import static org.assertj.core.api.Assertions.assertThat;
public class TestClass {
public void test() {
// do the actual test
assertThat(actualObject)
.isEqualToComparingFieldByFieldRecursively(expectedObject);
}
}
答案 3 :(得分:10)
我知道它有点旧,但我希望它有所帮助。
我遇到了与你相同的问题,因此,在调查之后,我发现了几个与此问题相似的问题,并且在找到解决方案之后,我也回答了同样的问题,因为我认为可以帮助他人。
本The most voted answer的similar question(不是作者选择的那个)是最合适的解决方案。
基本上,它包括使用名为Unitils的库。
这是用法:
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
即使班级User
没有实施equals()
,也会通过。您可以在tutorial中看到更多示例和一个名为assertLenientEquals
的非常酷的断言。
答案 4 :(得分:6)
您可以使用Apache commons lang ReflectionToStringBuilder
您可以指定要逐个测试的属性,也可以更好地排除您不想要的属性:
String s = new ReflectionToStringBuilder(o, ToStringStyle.SHORT_PREFIX_STYLE)
.setExcludeFieldNames(new String[] { "foo", "bar" }).toString()
然后将两个字符串比较为正常。关于反射速度缓慢的问题,我认为这仅用于测试,因此不应该如此重要。
答案 5 :(得分:5)
如果您正在使用hamcrest作为断言(assertThat)并且不想引入其他测试库,那么您可以使用SamePropertyValuesAs.samePropertyValuesAs
来断言没有重写的equals方法的项。
好处是你不必再引入另一个测试框架,如果你使用类似的东西,当断言失败(expected: field=<value> but was field=<something else>
)而不是expected: true but was false
时,它会给出一个有用的错误EqualsBuilder.reflectionEquals()
。
缺点是它是一个浅层比较,并且没有排除字段的选项(就像在EqualsBuilder中那样),所以你必须解决嵌套对象(例如删除它们并单独比较它们)。
最佳案例
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
assertThat(actual, is(samePropertyValuesAs(expected)));
丑陋案例
import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
SomeClass expected = buildExpected();
SomeClass actual = sut.doSomething();
NestedClass expectedSubObject = expected.getSubObject();
expected.setSubObject(null);
NestedClass actualSubObject = actual.getSubObject();
actual.setSubObject(null);
assertThat(actual, is(samePropertyValuesAs(expected)));
assertThat(actualSubObject, is(samePropertyValuesAs(expectedSubObject)));
所以,选择你的毒药。额外的框架(例如Unitils),无用的错误(例如EqualsBuilder)或浅层比较(hamcrest)。
答案 6 :(得分:3)
由于这个问题很旧,我将建议使用JUnit 5的另一种现代方法。
我不喜欢这种解决方案,因为如果早期断言失败,我将无法获得完整的平等印象。
对于JUnit 5,有一个名为:stylesheet:
https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css
的方法,该方法将允许您将测试中的所有断言组合在一起,并将执行每个断言并在最后输出所有失败的断言。这意味着任何先失败的断言都不会停止后一个断言的执行。
Assertions.assertAll()
答案 7 :(得分:3)
库Hamcrest 1.3 Utility Matchers有一个特殊的匹配器,它使用反射而不是等号。
assertThat(obj1, reflectEquals(obj2));
答案 8 :(得分:0)
我偶然发现了一个非常相似的案例。
我想在测试中比较一个对象具有与另一个对象相同的属性值,但像is()
,refEq()
等方法不适用于我的对象有null的原因id
属性中的值。
所以这就是我找到的解决方案(好吧,一位同事找到了):
import static org.apache.commons.lang.builder.CompareToBuilder.reflectionCompare;
assertThat(reflectionCompare(expectedObject, actualObject, new String[]{"fields","to","be","excluded"}), is(0));
如果从reflectionCompare
获得的值为0,则表示它们相等。如果它是-1或1,则它们在某些属性上有所不同。
答案 9 :(得分:0)
对于单元测试,我只是将对象序列化为 JSON 字符串并进行比较。 以 Gson 为例:
import Data.Function (on)
canDonateTo = canDonate `on` bloodType
由于预期对象和实际对象应该属于同一类型,因此字段顺序将相同。
优点:
缺点:
答案 10 :(得分:0)
作为纯junit的替代方法,您可以在等于声明之前将字段设置为null:
Opencsv
答案 11 :(得分:0)
AssertJ断言可用于比较没有正确覆盖#equals
方法的值,例如:
import static org.assertj.core.api.Assertions.assertThat;
// ...
assertThat(actual)
.usingRecursiveComparison()
.isEqualTo(expected);
答案 12 :(得分:0)
我尝试了所有答案,但实际上没有任何效果。
因此,我创建了自己的方法,该方法可以比较简单的Java对象,而无需深入探讨嵌套结构...
如果所有字段匹配或包含不匹配详细信息的字符串,则方法返回null。
仅比较具有getter方法的属性。
使用方法
assertNull(TestUtils.diff(obj1,obj2,ignore_field1, ignore_field2));
如果存在失配强>
<强>示例输出输出显示属性名称和比较的对象的各自的值
alert_id(1:2), city(Moscow:London)
代码(Java 8及更高版本):
public static String diff(Object x1, Object x2, String ... ignored) throws Exception{
final StringBuilder response = new StringBuilder();
for (Method m:Arrays.stream(x1.getClass().getMethods()).filter(m->m.getName().startsWith("get")
&& m.getParameterCount()==0).collect(toList())){
final String field = m.getName().substring(3).toLowerCase();
if (Arrays.stream(ignored).map(x->x.toLowerCase()).noneMatch(ignoredField->ignoredField.equals(field))){
Object v1 = m.invoke(x1);
Object v2 = m.invoke(x2);
if ( (v1!=null && !v1.equals(v2)) || (v2!=null && !v2.equals(v1))){
response.append(field).append("(").append(v1).append(":").append(v2).append(")").append(", ");
}
}
}
return response.length()==0?null:response.substring(0,response.length()-2);
}
答案 13 :(得分:0)
使用Shazamcast,您可以执行以下操作:
TableCloumn
答案 14 :(得分:0)
在对Android应用进行单元测试时,我遇到了完全相同的难题,而我提出的最简单的解决方案就是使用property modal
can be used instead将我的实际和期望值对象转换为String actual = new Gson().toJson( myObj.getValues() );
String expected = new Gson().toJson( new MyValues(true,1) );
assertEquals(expected, actual);
并进行比较作为字符串。
assertEquals()
这比逐手手动比较的优势在于您比较所有您的字段,因此即使您稍后在您的类中添加新字段,它也会自动进行测试,如与在所有字段中使用一堆Gson
的情况相比,如果您向班级添加更多字段,则需要更新。
jUnit还会为您显示字符串,以便您可以直接查看它们的不同之处。不确定{{1}}的字段排序有多可靠,这可能是一个潜在的问题。
答案 15 :(得分:0)
一些反射比较方法很浅
另一种选择是将对象转换为json并比较字符串。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public static String getJsonString(Object obj) {
try {
ObjectMapper objectMapper = new ObjectMapper();
return bjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {
LOGGER.error("Error parsing log entry", e);
return null;
}
}
...
assertEquals(getJsonString(MyexpectedObject), getJsonString(MyActualObject))
答案 16 :(得分:0)
在AssertJ的常见情况下,您可以创建自定义比较器策略:
assertThat(frodo).usingComparator(raceComparator).isEqualTo(sam)
assertThat(fellowshipOfTheRing).usingElementComparator(raceComparator).contains(sauron);
答案 17 :(得分:0)
这是一个通用的比较方法,它比较同一个类的两个对象的字段值(请记住get方法可以访问它们)
public static <T> void compare(T a, T b) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
AssertionError error = null;
Class A = a.getClass();
Class B = a.getClass();
for (Method mA : A.getDeclaredMethods()) {
if (mA.getName().startsWith("get")) {
Method mB = B.getMethod(mA.getName(),null );
try {
Assert.assertEquals("Not Matched = ",mA.invoke(a),mB.invoke(b));
}catch (AssertionError e){
if(error==null){
error = new AssertionError(e);
}
else {
error.addSuppressed(e);
}
}
}
}
if(error!=null){
throw error ;
}
}
答案 18 :(得分:0)
您可以使用反射来“自动化”完整的相等性测试。您可以实现为单个字段编写的相等“跟踪”代码,然后使用反射在对象的所有字段上运行该测试。
答案 19 :(得分:0)
逐场比较:
assertNotNull("Object 1 is null", obj1);
assertNotNull("Object 2 is null", obj2);
assertEquals("Field A differs", obj1.getFieldA(), obj2.getFieldA());
assertEquals("Field B differs", obj1.getFieldB(), obj2.getFieldB());
...
assertEquals("Objects are not equal.", obj1, obj2);
答案 20 :(得分:-1)
从你的评论到其他答案,我不明白你想要什么。
仅仅为了讨论,我们可以说该类确实覆盖了equals方法。
所以你的UT看起来像是:
SomeType expected = // bla
SomeType actual = // bli
Assert.assertEquals(expected, actual).
你完成了。而且,如果断言失败,你就无法获得“完全相等的图片”。
根据我的理解,你说即使类型确实覆盖了等于你,你也不会对它感兴趣,因为你想得到“完全平等的图片”。因此,扩展和覆盖equals也没有意义。
所以你必须选择:要么按属性比较属性,使用反射或硬编码检查,我会建议后者。或者:比较这些对象的人类可读表示。
例如,您可以创建一个帮助程序类,它将您希望与XML文档进行比较的类型序列化,然后比较生成的XML!在这种情况下,您可以直观地看到究竟什么是平等的,什么不是。
这种方法可以让你有机会看到全貌,但它也相对比较麻烦(起初有点容易出错)。
答案 21 :(得分:-1)
这对OP没有帮助,但它可能有助于任何最终在这里的C#开发人员......
与Enrique posted类似,您应该覆盖equals方法。
有没有更好的做法可以从对象中获得我想要的东西,而无需子类化和覆盖equals(ugh)?
我的建议是不要使用子类。使用部分类。
Partial Class Definitions (MSDN)
所以你的课看起来像......
public partial class TheClass
{
public override bool Equals(Object obj)
{
// your implementation here
}
}
对于Java,我同意使用反射的建议。请记住,尽可能避免使用反射。它很慢,很难调试,甚至更难维护到未来,因为IDE可能会通过执行字段重命名或类似的东西来破坏您的代码。小心!
答案 22 :(得分:-1)
你能把你发布的比较代码放到一些静态实用方法中吗?
public static String findDifference(Type obj1, Type obj2) {
String difference = "";
if (obj1.getFieldA() == null && obj2.getFieldA() != null
|| !obj1.getFieldA().equals(obj2.getFieldA())) {
difference += "Difference at field A:" + "obj1 - "
+ obj1.getFieldA() + ", obj2 - " + obj2.getFieldA();
}
if (obj1.getFieldB() == null && obj2.getFieldB() != null
|| !obj1.getFieldB().equals(obj2.getFieldB())) {
difference += "Difference at field B:" + "obj1 - "
+ obj1.getFieldB() + ", obj2 - " + obj2.getFieldB();
// (...)
}
return difference;
}
您可以在JUnit中使用此方法,如下所示:
assertEquals(&#34;对象不等于&#34;,&#34;&#34;,findDifferences(obj1,obj));
这不是笨重的,如果它们存在,则会为您提供有关差异的完整信息(通过不完全以正常形式的assertEqual,但您获得所有信息,因此它应该是好的)。
答案 23 :(得分:-3)
您可以覆盖类的equals方法,如:
@Override
public int hashCode() {
int hash = 0;
hash += (app != null ? app.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
HubRule other = (HubRule) object;
if (this.app.equals(other.app)) {
boolean operatorHubList = false;
if (other.operator != null ? this.operator != null ? this.operator
.equals(other.operator) : false : true) {
operatorHubList = true;
}
if (operatorHubList) {
return true;
} else {
return false;
}
} else {
return false;
}
}
好吧,如果你想比较一个类中的两个对象,你必须以某种方式实现equals和哈希码方法