Assert Two Lists for Equality Ignoring Order in Java – 在Java中断言两个列表相等而忽略顺序

最后修改: 2020年 8月 31日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Sometimes when writing unit tests, we need to make order agnostic comparison of lists. In this short tutorial, we’ll take a look at different examples of how we can write such unit tests.

有时在编写单元测试时,我们需要对列表进行顺序无关的比较。在这个简短的教程中,我们将看一下如何编写这种单元测试的不同例子。

2. Setup

2.设置

As per the List#equals Java documentation, two lists are equal if they contain the same elements in the same order. Therefore we can’t merely use the equals method as we want to do order agnostic comparison.

根据List#equalsJava 文档,如果两个列表包含相同顺序的元素,它们就是相等的。因此,我们不能仅仅使用equals方法,因为我们想做不考虑顺序的比较。

Throughout this tutorial, we’ll use these three lists as example inputs for our tests:

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

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

There are different ways to do order agnostic comparison. Let’s take a look at them one by one.

有不同的方法来做订单不可知的比较。让我们逐一看一下。

3. Using JUnit

3.使用JUnit

JUnit is a well-know framework used for unit testing in the Java ecosystem.

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

We can use the logic below to compare the equality of two lists using the assertTrue and assertFalse methods.

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

Here we check the size of both lists and check if the first list contains all elements of the second list and vice versa. Although this solution works, it’s not very readable. So now let’s look at some alternatives:

这里我们检查两个列表的大小,并检查第一个列表是否包含第二个列表的所有元素,反之亦然。虽然这个方案可行,但它的可读性不强。所以现在让我们看一下一些替代方案。

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

In this first test, the size of both lists is compared before we check if the elements in both lists are the same. As both of these conditions return true, our test will pass.

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

Let’s now take a look at a failing test:

现在我们来看看一个失败的测试。

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

Contrastingly, in this version of the test, although the size of both lists is the same, all elements don’t match.

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

4. Using AssertJ

4.使用AssertJ

AssertJ is an opensource community-driven library used for writing fluent and rich assertions in Java tests.

AssertJ是一个开源的社区驱动库,用于在Java测试中编写流畅而丰富的断言。

To use it in our maven project, let’s add the assertj-core dependency in the pom.xml file:

为了在我们的maven项目中使用它,让我们在pom.xml文件中添加assertj-core依赖项。

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

Let’s write a test to compare the equality of two list instance of the same element and same size:

让我们写一个测试来比较两个相同元素和相同大小的列表实例是否相等。

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

In this example, we verify first contains all the elements of the given iterable and nothing else, in any order. The main limitation of this approach is the hasSameElementsAs method ignores duplicates.

在这个例子中,我们验证了first包含了给定的可迭代的所有元素,而没有其他的,任何顺序。这种方法的主要限制是hasSameElementsAs方法会忽略重复的元素。

Let’s look at this in practice to see what we mean:

让我们在实践中看看这一点,看看我们的意思。

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

In this test, although we have the same elements, the size of both lists is not equal, but the assertion will still be true, as it ignores the duplicates. To make it work we need to add a size check for both lists:

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

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

Adding a check for the size of both our lists followed by the method hasSameElementsAs will indeed fail as expected.

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

5. Using Hamcrest

5.使用Hamcrest

If we are already using Hamcrest or want to use it for writing unit tests, here is how we can use the Matchers#containsInAnyOrder method for order agnostic comparison.

如果我们已经在使用Hamcrest或者想用它来编写单元测试,这里我们可以使用Matchers#containsInAnyOrder方法来进行顺序无关的比较。

To use Hamcrest in our maven project, let’s add the hamcrest-all dependency in pom.xml file:

为了在我们的maven项目中使用Hamcrest,让我们在pom.xml文件中添加hamcrest-all依赖项。

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

Let’s look at the test:

让我们来看看这个测试。

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

Here the method containsInAnyOrder creates an order agnostic matcher for Iterables, which does matching with examined Iterable elements. This test matches the elements of two lists, ignoring the order of elements in the list.

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

Thankfully this solution doesn’t suffer from the same problem as explained in the previous section, so we don’t need to compare the sizes explicitly.

值得庆幸的是,这个解决方案没有出现上一节中解释的问题,所以我们不需要明确地比较大小。

6. Using Apache Commons

6.使用Apache Commons

Another library or framework apart from JUnit, Hamcrest, or AssertJ, we can use is Apache CollectionUtils. It provides utility methods for common operations that cover a wide range of use cases and helps us avoid writing boilerplate code.

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

To use it in our maven project, let’s add the commons-collections4 dependency in pom.xml file:

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

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

Here is a test using CollectionUtils:

这里是一个使用CollectionUtils的测试。

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

The isEqualCollection method returns true if the given collections contain precisely the same elements with the same cardinalities. Otherwise, it returns false.

isEqualCollection方法的返回值是true,如果给定的集合正好包含相同的元素,并且具有相同的cardinalities。否则,它返回false

7. Conclusion

7.结语

In this article, we have explored how to check the equality of two List instances, where the elements of both lists are ordered differently.

在这篇文章中,我们探讨了如何检查两个List实例的平等性,其中两个列表的元素的排序是不同的。

All these examples can be found over on GitHub.

所有这些例子都可以在GitHub上找到over