Assert.assertEquals 在两个列表中

Assert.assertEquals on two lists

我有一个 class MySchool:

public class MySchool{
    private long timestamp;
    private SchoolEvent event;
    private Object value;

    //getter & setters
    ...

    @Override
    public String toString() {
        return "MySchool [timestamp=" + timestamp + ", event="
                + event + ", value=" + value + "]";
    }
}

SchoolEvent 是枚举类型:

public static enum SchoolEvent {
        CEREMONY, HOLIDAY
    }

我尝试使用 Assert.assertEquals() 来比较两个学校列表:

List<MySchool> schoolList1 = generateSchools();
List<MySchool> schoolList2 = readSchoolsFromFile();

Assert.assertEquals(schoolList1, schoolList2);

失败并出现以下错误:

expected: java.util.ArrayList<[MySchool [timestamp=0, event=CEREMONY, value=null], MySchool [timestamp=0, event=HOLIDAY, value=null]]> 

but was:  java.util.ArrayList<[MySchool [timestamp=0, event=CEREMONY, value=null], MySchool [timestamp=0, event=HOLIDAY, value=null]]>

我不明白为什么错误听起来不像错误,我的意思是检查错误信息,两个列表中每个元素对象的每个字段都是相等的。

我还检查了 Java 关于 List#equal 的文档,它还说:

two lists are defined to be equal if they contain the same elements in the same order.

为什么 assertEquals() 失败了?

I don't understand why the error doesn't sound like an error, I mean just check the error message, every field of every element object in two lists are euqal.

是的,但这并不意味着对象被认为是相等的。

您需要重写 equals()(和 hashCode())才能将不同的对象视为相等。默认情况下,equals 方法只检查对象 identity... 换句话说,x.equals(y) 等同于检查 x 和 [=默认情况下,16=] 指的是 完全相同的对象 。如果您想要不同的行为 - 以便它检查某些字段是否相等 - 那么您需要在 equals() 中指定该行为,并以与此一致的方式实施 hashCode()

注意这个问题根本不依赖于集合。如果你比较单个对象,你会遇到同样的问题:

MySchool school1 = schoolList1.get(0);
MySchool school2 = schoolList2.get(0);
Assert.areEqual(school1, school2); // This will fail...

要使两个对象相等,它们的 equals() 方法必须 return 为真。

如果两个对象是同一对象,equals() 的默认实现仅 return 为真,但如果它们是具有相同 "content".

的不同对象,则不是

所以,x.equals(y);可以是true,即使x == y("are x and y the same object")是false,如果x的equals()方法 returns true 参数 y.

您的 class 必须实施 equals()。我将在下面添加一个示例实现:

    @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    MySchool mySchool = (MySchool) o;

    if (timestamp != mySchool.timestamp) return false;
    if (event != mySchool.event) return false;
    return !(value != null ? !value.equals(mySchool.value) : mySchool.value != null);

}

然后我会使用 hamcrest 对集合进行断言:

public void testTwoEventsAreEquals() throws Exception {
    List<MySchool> schoolList1 = Arrays.asList(new MySchool(SchoolEvent.CEREMONY), new MySchool(SchoolEvent.HOLIDAY));
    List<MySchool> schoolList2 = Arrays.asList(new MySchool(SchoolEvent.CEREMONY), new MySchool(SchoolEvent.HOLIDAY));

    assertThat(schoolList1, containsInAnyOrder(schoolList2.toArray()));
}

如果您使用的是 Maven,则必须将 Hamcrest 添加为依赖项,以便编译上述代码。

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

不要按照其他人的建议放置 equals 方法(对不起,伙计们)。这将是生产中的测试代码,它不仅本身是错误的,而且如果您向 class 添加另一个字段但忘记更新 equals 方法怎么办?你的测试不会测试你认为他们正在测试的东西。如果 equals 方法错误怎么办?如果它总是 returns 真怎么办?您的测试将始终为绿色,并且您会错误地认为一切都很好。因为当然谁会为 equals 方法编写测试。

我强烈建议使用 shazamcrest 等工具。这允许您只写 assertThat(actual, sameBeanAs(expected)) 而您不必担心任何事情。它不需要对您的生产代码进行任何更改。它将遍历所有字段,无论它们的访问修饰符如何。还提供了非常好的诊断。它抛出 ComparisonFailure(而不是 AssertionError),IDE 将向您展示很好的并排比较。

代码:

List<MySchool> schoolList1 = generateSchools();
List<MySchool> schoolList2 = readSchoolsFromFile();

assertThat(schoolList2, sameBeanAs(schoolList2));

需要依赖项:

<dependency>
    <groupId>com.shazam</groupId>
    <artifactId>shazamcrest</artifactId>
    <version>0.11</version>
</dependency>

您得到的诊断:

注:

记得使用 com.shazam.shazamcrest.MatcherAssert.assertThat,而不是 hamcrestjunit 中的一个。它的工作方式与其他 assertThat 方法完全相同,但是如果与 sameBeanAs 一起使用,它将抛出 ComparisonFailure,而不是其他 AssertionError

如果您使用 lombok 构建 classes 添加

@EqualsAndHashCode 注释在 class 定义之前也解决了这个问题

示例片段

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.EqualsAndHashCode;
import lombok.NonNull;

import java.math.BigDecimal;

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Myclass {
    @NonNull
    private String var1;
    @NonNull
    private String var2;
    @NonNull
    private String var3;
    @NonNull
    private String var4;
    @NonNull
    private BigDecimal var5;
}