Из коробки 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