Перейти к основному содержимому

Утвердить два списка для равенства, игнорируя порядок в Java

· 4 мин. чтения

1. Обзор

Иногда при написании модульных тестов нам нужно сделать независимое от порядка сравнение списков. В этом коротком руководстве мы рассмотрим различные примеры написания таких модульных тестов.

2. Настройка

Согласно документации Java List#equals , два списка равны, если они содержат одни и те же элементы в одном и том же порядке. Следовательно, мы не можем просто использовать метод 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.

Мы можем использовать приведенную ниже логику, чтобы сравнить равенство двух списков, используя методы assertTrue и assertFalse .

Здесь мы проверяем размер обоих списков и проверяем, содержит ли первый список все элементы второго списка и наоборот. Хотя это решение работает, оно не очень читабельно. Итак, теперь давайте рассмотрим некоторые альтернативы:

@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, давайте добавим зависимость assertj-core в файл pom.xml :

<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 или хотим использовать его для написания модульных тестов, вот как мы можем использовать метод Matchers#containsInAnyOrder для независимого от порядка сравнения.

Чтобы использовать Hamcrest в нашем проекте maven, давайте добавим зависимость hamcrest-all в файл pom.xml :

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

Здесь метод containsInAnyOrder создает независимый от порядка сопоставитель для Iterables , который выполняет сопоставление с проверяемыми элементами Iterable . Этот тест сопоставляет элементы двух списков, игнорируя порядок элементов в списке.

К счастью, это решение не страдает от той же проблемы, которая описана в предыдущем разделе, поэтому нам не нужно явно сравнивать размеры.

6. Использование Apache Commons

Другой библиотекой или фреймворком помимо JUnit, Hamcrest или AssertJ, который мы можем использовать, является Apache CollectionUtils . Он предоставляет служебные методы для общих операций, которые охватывают широкий спектр вариантов использования и помогают нам избежать написания стандартного кода.

Чтобы использовать его в нашем проекте maven, добавим зависимость commons-collections4 в файл pom.xml :

<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 .

7. Заключение

В этой статье мы рассмотрели, как проверить равенство двух экземпляров List , где элементы обоих списков упорядочены по-разному.

Все эти примеры можно найти на GitHub .