如何在没有equals方法的情况下断言两个类的相等性?

时间:2012-08-27 18:15:51

标签: java unit-testing junit

假设我有一个没有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)?

24 个答案:

答案 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

由于预期对象和实际对象应该属于同一类型,因此字段顺序将相同。

优点:

  • 您将得到一个很好的字符串比较,突出显示差异的确切位置。
  • 没有额外的库(前提是您已经有一个 JSON 库)

缺点:

  • 不同类型的对象可能产生相同的 JSON(但如果它们产生了,您可能会考虑为什么对于相同的数据有不同的类......以及它们最终如何被比较在测试方法中:-)

答案 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);

Using a custom comparison strategy in assertions

AssertJ examples

答案 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和哈希码方法