Сравнение карт с помощью Hamcrest
Проблема с использованием Hamcrest для сравнения двух карт
Я хотел бы использовать Hamcrest для проверки равенства двух карт, то есть чтобы они имели одинаковый набор ключей, указывающих на одинаковые значения.
Мой текущий вариант выглядит так:
assertThat(affA.entrySet(), hasItems(affB.entrySet()));
Однако, я получаю ошибку:
Метод assertThat(T, Matcher<T>) в типе Assert не применим для аргументов (Set<Map.Entry<Householdtypes, Double>>, Matcher<Iterable<Set<Map.Entry<Householdtypes, Double>>>).
Я также смотрел на различные вариации containsAll
и другие методы, предоставляемые пакетами Hamcrest. Можете ли вы указать мне правильное направление? Или мне нужно написать пользовательский матчер?
5 ответ(ов)
Самый короткий способ, который я придумал, состоит из двух утверждений:
assertThat(affA.entrySet(), everyItem(isIn(affB.entrySet())));
assertThat(affB.entrySet(), everyItem(isIn(affA.entrySet())));
Но вы также можете сделать это:
assertThat(affA.entrySet(), equalTo(affB.entrySet()));
в зависимости от реализаций карт и жертвуя ясностью отчета о различиях: это просто скажет вам, что есть разница, в то время как предыдущие утверждения также укажут, в чем именно она заключается.
Обновление: на самом деле есть одно утверждение, которое работает независимо от типов коллекций:
assertThat(affA.entrySet(), both(everyItem(isIn(affB.entrySet()))).and(containsInAnyOrder(affB.entrySet().toArray())));
Вопрос: Когда следует использовать Map.equals()
для сравнения карт, а когда лучше подойти к этому более гибко?
Ответ:
В некоторых случаях Map.equals()
вполне достаточно для проверки равенства карт. Однако бывает, что типы карт, возвращаемых кодом, который мы тестируем, неизвестны. В таких ситуациях может возникнуть вопрос, правильно ли equals()
сравнит невиданную карту с той, которую вы сами создали. Кроме того, может возникнуть желание не привязывать тесты к жестким условиям.
Лично мне кажется, что создание карты отдельно для сравнения с результатом — это не самый элегантный подход:
Map<MyKey, MyValue> actual = methodUnderTest();
Map<MyKey, MyValue> expected = new HashMap<MyKey, MyValue>();
expected.put(new MyKey(1), new MyValue(10));
expected.put(new MyKey(2), new MyValue(20));
expected.put(new MyKey(3), new MyValue(30));
assertThat(actual, equalTo(expected));
Я предпочитаю использовать матчеры:
import static org.hamcrest.Matchers.hasEntry;
Map<MyKey, MyValue> actual = methodUnderTest();
assertThat(actual, allOf(
hasSize(3), // проверяем, чтобы в карте не было лишних пар ключ/значение
hasEntry(new MyKey(1), new MyValue(10)),
hasEntry(new MyKey(2), new MyValue(20)),
hasEntry(new MyKey(3), new MyValue(30))
));
Мне пришлось самому определить hasSize()
:
public static <K, V> Matcher<Map<K, V>> hasSize(final int size) {
return new TypeSafeMatcher<Map<K, V>>() {
@Override
public boolean matchesSafely(Map<K, V> kvMap) {
return kvMap.size() == size;
}
@Override
public void describeTo(Description description) {
description.appendText(" has ").appendValue(size).appendText(" key/value pairs");
}
};
}
Кроме того, существует еще одна версия hasEntry()
, которая принимает матчеры в качестве параметров вместо конкретных значений ключа и значения. Это может быть полезно, если вам нужно проверить условия, отличные от простого тестирования на равенство для каждого ключа и значения.
Этот подход работает безупречно и не требует двух утверждений, как в принятом ответе.
assertThat(actualData.entrySet().toArray(),
arrayContainingInAnyOrder(expectedData.entrySet().toArray()));
В Hamcrest теперь есть Matcher
для проверки размера коллекции — org.hamcrest.collection.IsCollectionWithSize
.
Этот Matcher
позволяет удобно проверять, содержит ли коллекция ожидаемое количество элементов. Например, вы можете использовать его следующим образом:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import java.util.List;
List<String> myList = List.of("one", "two", "three");
assertThat(myList, hasSize(3));
В этом примере производится проверка, что myList
содержит ровно 3 элемента. Использование IsCollectionWithSize
делает код читаемым и понятным.
При тестировании с использованием Groovy и библиотеки org.hamcrest:hamcrest:2.2
, я столкнулся с проблемой: метод isIn
устарел, и рекомендуется использовать is(in(...))
вместо него. Однако in
является зарезервированным словом в Groovy!
В итоге, я решил сделать алиас для импорта, чтобы использовать следующий код:
import static org.hamcrest.Matchers.*
import static org.hamcrest.Matchers.in as matchIn
....
....
@Test
void myTestMethod() {
Map expectedSubMap = [
one: "One",
three: "Three"
]
Map result = getMapToTest()
assertThat(expectedSubMap.entrySet(), everyItem(is(matchIn(result.entrySet()))))
}
Таким образом, я смог обойти эту проблему с зарезервированным словом и успешно использовать новый синтаксис is(in(...))
в своих тестах.
Эффективный способ итерации по каждой записи в Java Map?
Инициализация ArrayList в одну строчку
Итерация по коллекции: избегаем ConcurrentModificationException при удалении объектов в цикле
Преобразование 'ArrayList<String>' в 'String[]' в Java
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?