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

Rya*_*son 82 java junit unit-testing

假设我有一个没有equals()方法的类,没有源.我想在该类的两个实例上断言相等.

我可以做多个断言:

assertEquals(obj1.getFieldA(), obj2.getFieldA());
assertEquals(obj1.getFieldB(), obj2.getFieldB());
assertEquals(obj1.getFieldC(), obj2.getFieldC());
...
Run Code Online (Sandbox Code Playgroud)

我不喜欢这个解决方案,因为如果早期的断言失败,我就不会得到完全相等的图片.

我可以自己手动比较并跟踪结果:

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);
Run Code Online (Sandbox Code Playgroud)

这给了我完全平等的图片,但是很笨重(我甚至没有考虑到可能的零问题).第三种选择是使用Comparator,但compareTo()不会告诉我哪些字段失败相等.

有没有更好的做法从对象获得我想要的东西,没有子类化和覆盖equals(ugh)?

fel*_*age 50

Mockito提供了一个反射匹配器:

Assert.assertTrue(new ReflectionEquals(expected, excludeFields).matches(actual));
Run Code Online (Sandbox Code Playgroud)

  • 这个类在包'org.mockito.internal.matchers.apachecommons`中.Mockito docs陈述:`org.mockito.internal` - >"内部类,不被客户使用." 您将使用此项目将项目置于风险之中.这可以在任何Mockito版本中更改.请阅读:http://site.mockito.org/mockito/docs/current/overview-summary.html (14认同)
  • 请改用[`Mockito.refEq()`](https://static.javadoc.io/org.mockito/mockito-core/2.7.22/org/mockito/Mockito.html#methods_inherited_from_class_org.mockito.ArgumentMatchers). (5认同)
  • 它工作正常,但预期和实际的顺序错误.它应该是相反的方式. (3认同)
  • 当对象没有设置 id 时`Mockito.refEq()`会失败=( (2认同)

Abh*_*she 41

我通常使用org.apache.commons.lang3.builder.EqualsBuilder实现这个用例

Assert.assertTrue(EqualsBuilder.reflectionEquals(expected,actual));
Run Code Online (Sandbox Code Playgroud)

  • 这并没有给出任何确切字段实际上不匹配的任何提示. (3认同)
  • 这需要图中所有的节点都实现“相等”和“哈希码”,这基本上使该方法几乎无用。AssertJ 的 isEqualToComparingFieldByFieldRecursively 在我的情况下是完美的。 (2认同)

Tho*_*mas 34

这里有很多正确的答案,但我也想添加我的版本.这是基于Assertj.

import static org.assertj.core.api.Assertions.assertThat;

public class TestClass {

    public void test() {
        // do the actual test
        assertThat(actualObject)
            .isEqualToComparingFieldByFieldRecursively(expectedObject);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在assertj v3.13.2中,此方法已被弃用,现在建议将“usingRecursiveComparison()”与“isEqualTo()”一起使用,这样该行就是“assertThat(actualObject).usingRecursiveComparison().isEqualTo(expectedObject);” (5认同)

lop*_*vit 12

我知道它有点旧,但我希望它有所帮助.

我遇到了与你相同的问题,因此,在调查之后,我发现了几个与此问题相似的问题,并且在找到解决方案之后,我在回答这些问题,因为我认为它可以帮助其他人.

这个类似问题的最多投票答案(不是作者选择的答案)是最合适的解决方案.

基本上,它包括使用名为Unitils的库.

这是用途:

User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
Run Code Online (Sandbox Code Playgroud)

即使该类User没有实现,它也会通过equals().您可以assertLenientEquals在他们的教程中看到更多示例和一个非常酷的断言.

  • 不幸的是 `Unitils` 似乎已经死了,请参阅 /sf/ask/2426064721/。 (2认同)

pat*_*elb 9

由于这个问题很旧,我将建议使用 JUnit 5 的另一种现代方法。

我不喜欢这个解决方案,因为如果早期断言失败,我就无法获得完整的平等图。

在 JUnit 5 中,有一种方法Assertions.assertAll()可以让您将测试中的所有断言分组在一起,并且它将执行每个断言并在最后输出任何失败的断言。这意味着任何先失败的断言都不会停止后面断言的执行。

assertAll("Test obj1 with obj2 equality",
    () -> assertEquals(obj1.getFieldA(), obj2.getFieldA()),
    () -> assertEquals(obj1.getFieldB(), obj2.getFieldB()),
    () -> assertEquals(obj1.getFieldC(), obj2.getFieldC()));
Run Code Online (Sandbox Code Playgroud)


Mat*_*ell 8

您可以使用Apache commons lang ReflectionToStringBuilder

您可以指定要逐个测试的属性,也可以更好地排除您不想要的属性:

String s = new ReflectionToStringBuilder(o, ToStringStyle.SHORT_PREFIX_STYLE)
                .setExcludeFieldNames(new String[] { "foo", "bar" }).toString()
Run Code Online (Sandbox Code Playgroud)

然后,您可以正常比较两个字符串.关于反射速度慢的问题,我认为这仅用于测试,因此不应该如此重要.


Mar*_*uee 6

如果您在断言(assertThat)中使用hamcrest,并且不想引入其他测试库,则可以SamePropertyValuesAs.samePropertyValuesAs断言没有覆盖equals方法的项目。

好处是,您不必引入另一个测试框架,并且当assert失败(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)));
Run Code Online (Sandbox Code Playgroud)

丑案:

import static org.hamcrest.beans.SamePropertyValuesAs.samePropertyValuesAs;
...
SomeClass expected = buildExpected(); 
SomeClass actual = sut.doSomething();

assertThat(actual.getSubObject(), is(samePropertyValuesAs(expected.getSubObject())));    
expected.setSubObject(null);
actual.setSubObject(null);

assertThat(actual, is(samePropertyValuesAs(expected)));
Run Code Online (Sandbox Code Playgroud)

所以,选择你的毒药。其他框架(例如Unitils),无用的错误(例如EqualsBuilder)或浅比较(hamcrest)。

  • 我喜欢这个解决方案,因为它没有添加“完全*不同的附加依赖项!” (2认同)
  • 另一个缺点:它使用属性(getter)而不是比较字段。 (2认同)

Enr*_*tín -6

您可以重写类的 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

好吧,如果你想比较一个类中的两个对象,你必须以某种方式实现 equals 和 hash code 方法

  • 但OP说他不想推翻equals,他想要一个更好的方法 (3认同)