Из коробки JUnit предлагает некоторое количество assert* методов, позволяющих проверить состояние объекта. Но эти примитивные методы не позволяют написать комплексных проверок, например «значение эквивалентно X или является null» или «Значение объекста эквивалентно X и значение его свойства Z эквивалетно Y» итд.
В современных версиях JUnit кроме классических assert* методом есть и особенный метод, assertThat :
1 | assertThat([сообщение], значение, предикаты) |
Который проверяет что выражение, описанное матчерами (matchers, устоявшийся перевод, предикаты, ничем не лучше, поэтому я буду использовать кальку с английского), будучи применённым к значению, истинно:
1 | assertThat("Not a 3", someValue, is(3)); |
На самом деле, JUnit использует родственный проект Hamcrest, сфокусированный на разработке универсальных матчеров. А заключается основное преимущество матчеров перед старорежимными assert* методами в том, что матчер может быть любым (до той поры, пока он следует интерфейсу) и использование матчеров позволяет декларативно описывать условия теста.
Можно написать матчер pink, проверяющий цвет и проверить цвет пантеры:
1 | assertThat(panther, is(pink()); |
Или написать матчер, рализующий логическую операцию и проверить два свойства одним махом:
1 | assertThat(panther, both(pink).and(name("Pinky")); |
Кстати, для англоговорящих в использовании assertThat есть ещё один плюс -проверочные выражения написаны на практически разговорном языке:
1 2 | //Доказать что(assertThat) пантера(panther) розовая(is(pink()). assertThat(panther, is(pink()); |
Подготовка
Возьмём код из примера @Before/@After и скопируем класс StringUtilsTest в StringUtilsMatcherTest
JUnit включает в себя небольшое подмножество Hamcrest матчеров, но полностью совместим с остальными матчерами Hamcrest, поэтому добавим их к проекту:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <junit.version>4.12</junit.version> <hamcrest.version>1.3</hamcrest.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>${hamcrest.version}</version> <scope>test</scope> </dependency> </dependencies> |
Тесты
В новом классе перепишем тесты с использованием матчеров:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Test public void testToDouble() { //assertEquals(3.1415, StringUtils.toDouble("3.1415"), 0.0001); assertThat( StringUtils.toDouble("3.1415"), is(closeTo(3.1415, 0.0001))); //assertEquals("Not NaN for null", Double.NaN, StringUtils.toDouble(null)); assertThat( StringUtils.toDouble(null), is(Double.NaN)); } @Test public void testFromDouble() { assertThat( StringUtils.fromDouble(3.1415), is("3.1415")); } |
На мой взгляд is(closeTo()) гораздо удобнее и позволяет не помнить о третьем параметре assertEquals при сравнении чисел с плавающей запятой.
Теперь массивы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @Test public void testToArray() { //assertArrayEquals("Wrong array", testArray, StringUtils.toArray(testString, ':')); //Assert.assertEquals(0, StringUtils.toArray(null, ':').length); assertThat(StringUtils.toArray(testString, ':'), is(testArray)); assertThat(StringUtils.toArray(null, ':').length, is(0)); //Power of matchers assertThat( Arrays.asList(StringUtils.toArray(testString, ':')), containsInAnyOrder("T","T", "S","E")); } @Test public void testJoinArray() { //assertEquals(testString, StringUtils.joinArray(testArray, ':')); assertThat(StringUtils.joinArray(testArray, ':'), is(testString)); assertNull(StringUtils.joinArray(null, ':')); } |
Можно забыть и о assertArrayEquals: hamcrest и assertThat достаточно умны, чтобы сравнить массивы. Нечёткое сравнение массивов прекрасно иллюстрирует возможности матчеров:
1 2 3 | assertThat( Arrays.asList(StringUtils.toArray(testString, ':')), containsInAnyOrder("T","T", "S","E")); |
Исходный код примера доступен на github