Contents

Java检查两个列表的相等性

1. 概述

有时在编写单元测试时,我们需要对列表进行顺序不可知的比较。在这个简短的教程中,我们将看看如何编写此类单元测试的不同示例。

2. 设置

根据*List#equals * Java 文档,如果两个列表以相同的顺序包含相同的元素,则它们是相等的。因此,我们不能仅仅使用equals方法来进行与顺序无关的比较。

在本教程中,我们将使用这三个列表作为测试的示例输入:

List first = Arrays.asList(1, 3, 4, 6, 8);
List second = Arrays.asList(8, 1, 6, 3, 4);
List third = Arrays.asList(1, 3, 3, 6, 6);

有不同的方法可以进行与顺序无关的比较。让我们一一看看。

3. 使用 JUnit

JUnit 是 Java 生态系统中用于单元测试的众所周知的框架。

我们可以使用下面的逻辑来使用assertTrueassertFalse 方法比较两个列表的相等性。

这里我们检查两个列表的大小,并检查第一个列表是否包含第二个列表的所有元素,反之亦然。尽管此解决方案有效,但可读性不高。那么现在让我们看看一些替代方案:

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeTrue() {
    assertTrue(first.size() == second.size() && first.containsAll(second) && second.containsAll(first));
}

在第一个测试中,我们先比较两个列表的大小,然后再检查两个列表中的元素是否相同。由于这两个条件都返回true,我们的测试将通过。

现在让我们看一下失败的测试:

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeFalse() {
    assertFalse(first.size() == third.size() && first.containsAll(third) && third.containsAll(first));
}

相比之下,在这个版本的测试中,虽然两个列表的大小相同,但所有元素都不匹配。

4. 使用AssertJ

AssertJ 是一个开源社区驱动的库,用于在 Java 测试中编写流畅且丰富的断言。 要在我们的 maven 项目中使用它,让我们在pom.xml文件中添加assertj-core 依赖项:

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.16.1</version>
</dependency>

让我们编写一个测试来比较具有相同元素和相同大小的两个列表实例的相等性:

@Test
void whenTestingForOrderAgnosticEqualityBothList_ShouldBeEqual() {
    assertThat(first).hasSameElementsAs(second);
}

在这个例子中,我们首先验证包含给定可迭代对象的所有元素,没有其他元素,顺序不限。这种方法的主要限制是hasSameElementsAs方法会忽略重复项。

让我们在实践中看看这个,看看我们的意思:

@Test
void whenTestingForOrderAgnosticEqualityBothList_ShouldNotBeEqual() {
    List a = Arrays.asList("a", "a", "b", "c");
    List b = Arrays.asList("a", "b", "c");
    assertThat(a).hasSameElementsAs(b);
}

在这个测试中,虽然我们有相同的元素,但两个列表的大小不相等,但断言仍然为真,因为它忽略了重复项。为了让它工作,我们需要为两个列表添加一个大小检查:

assertThat(a).hasSize(b.size()).hasSameElementsAs(b);

在方法hasSameElementsAs之后添加对我们两个列表的大小的检查确实会像预期的那样失败。

5. 使用 Hamcrest

如果我们已经在使用 Hamcrest 或想用它来编写单元测试,这里是我们如何使用Matchers#containsInAnyOrder 方法进行与顺序无关的比较。 要在我们的 maven 项目中使用 Hamcrest,让我们在pom.xml文件中添加hamcrest-all 依赖项:

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

让我们看一下测试:

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeEqual() {
    assertThat(first, Matchers.containsInAnyOrder(second.toArray()));
}

这里的方法containsInAnyOrderIterables创建了一个与顺序无关的匹配器,它与检查过的Iterable元素进行匹配。该测试匹配两个列表的元素,忽略列表中元素的顺序。

值得庆幸的是,这个解决方案不会遇到上一节中解释的相同问题,因此我们不需要明确比较大小。

6. 使用 Apache Commons

除了 JUnit、Hamcrest 或 AssertJ,我们可以使用的另一个库或框架是Apache CollectionUtils 。它为涵盖广泛用例的常见操作提供实用方法,并帮助我们避免编写样板代码。

要在我们的 maven 项目中使用它,让我们在pom.xml文件中添加commons-collections4 依赖项:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

这是一个使用CollectionUtils的测试:

@Test
public void whenTestingForOrderAgnosticEquality_ShouldBeTrueIfEqualOtherwiseFalse() {
    assertTrue(CollectionUtils.isEqualCollection(first, second));
    assertFalse(CollectionUtils.isEqualCollection(first, third));
}

如果给定的集合包含具有相同基数的完全相同的元素 ,则 isEqualCollection方法返回true。否则,它返回false