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 .