«1. Обзор

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

В этой статье мы углубимся в сопоставление чисел.

2. Настройка

Чтобы получить Hamcrest, нам просто нужно добавить следующую зависимость Maven в наш pom.xml:

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>java-hamcrest</artifactId>
    <version>2.0.0.0</version>
</dependency>

Последнюю версию Hamcrest можно найти на Maven Central.

3. Сопоставители близости

Первый набор сопоставителей, который мы собираемся рассмотреть, это те, которые проверяют, близок ли какой-либо элемент к значению +/- ошибка.

Более формально:

value - error <= element <= value + error

Если приведенное выше сравнение верно, утверждение будет выполнено.

Давайте посмотрим на это в действии!

3.1. isClose с двойными значениями

Предположим, что у нас есть число, хранящееся в двойной переменной, называемой фактической. И мы хотим проверить, близко ли фактическое значение к 1 +/- 0,5.

То есть:

1 - 0.5 <= actual <= 1 + 0.5
    0.5 <= actual <= 1.5

Теперь давайте создадим модульный тест, используя сопоставление isClose:

@Test
public void givenADouble_whenCloseTo_thenCorrect() {
    double actual = 1.3;
    double operand = 1;
    double error = 0.5;
 
    assertThat(actual, closeTo(operand, error));
}

Так как 1.3 находится между 0.5 и 1.5, тест пройдет. Точно так же мы можем протестировать негативный сценарий:

@Test
public void givenADouble_whenNotCloseTo_thenCorrect() {
    double actual = 1.6;
    double operand = 1;
    double error = 0.5;
 
    assertThat(actual, not(closeTo(operand, error)));
}

Теперь давайте рассмотрим аналогичную ситуацию с другим типом переменных.

3.2. isClose со значениями BigDecimal

isClose перегружен и может использоваться так же, как и со значениями типа double, но с объектами BigDecimal:

@Test
public void givenABigDecimal_whenCloseTo_thenCorrect() {
    BigDecimal actual = new BigDecimal("1.0003");
    BigDecimal operand = new BigDecimal("1");
    BigDecimal error = new BigDecimal("0.0005");
    
    assertThat(actual, is(closeTo(operand, error)));
}

@Test
public void givenABigDecimal_whenNotCloseTo_thenCorrect() {
    BigDecimal actual = new BigDecimal("1.0006");
    BigDecimal operand = new BigDecimal("1");
    BigDecimal error = new BigDecimal("0.0005");
    
    assertThat(actual, is(not(closeTo(operand, error))));
}

Обратите внимание, что сопоставитель is только украшает другие сопоставители без добавления дополнительной логики. Это просто делает все утверждение более читабельным.

Вот и все, что касается сопоставителей близости. Далее мы рассмотрим сопоставители ордеров.

4. Сопоставители порядка

Как следует из их названия, эти сопоставители помогают делать утверждения относительно порядка.

Их пять:

    comparesEqualTo, GreatThan, GreatThanOrEqualTo, lessThan lessThanOrEqualTo,

Они говорят сами за себя, но давайте рассмотрим несколько примеров.

4.1. Сопоставители порядка с целыми значениями

Наиболее распространенным сценарием будет использование этих сопоставителей с числами.

Итак, давайте продолжим и создадим несколько тестов:

@Test
public void given5_whenComparesEqualTo5_thenCorrect() {
    Integer five = 5;
    
    assertThat(five, comparesEqualTo(five));
}

@Test
public void given5_whenNotComparesEqualTo7_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;

    assertThat(five, not(comparesEqualTo(seven)));
}

@Test
public void given7_whenGreaterThan5_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;
 
    assertThat(seven, is(greaterThan(five)));
}

@Test
public void given7_whenGreaterThanOrEqualTo5_thenCorrect() {
    Integer seven = 7;
    Integer five = 5;
 
    assertThat(seven, is(greaterThanOrEqualTo(five)));
}

@Test
public void given5_whenGreaterThanOrEqualTo5_thenCorrect() {
    Integer five = 5;
 
    assertThat(five, is(greaterThanOrEqualTo(five)));
}

@Test
public void given3_whenLessThan5_thenCorrect() {
   Integer three = 3;
   Integer five = 5;
 
   assertThat(three, is(lessThan(five)));
}

@Test
public void given3_whenLessThanOrEqualTo5_thenCorrect() {
   Integer three = 3;
   Integer five = 5;
 
   assertThat(three, is(lessThanOrEqualTo(five)));
}

@Test
public void given5_whenLessThanOrEqualTo5_thenCorrect() {
   Integer five = 5;
 
   assertThat(five, is(lessThanOrEqualTo(five)));
}

Имеет смысл, верно? Обратите внимание, как просто понять, что утверждают предикаты.

4.2. Сопоставители порядка со строковыми значениями

Несмотря на то, что сравнение чисел имеет смысл, во многих случаях полезно сравнивать другие типы элементов. Вот почему сопоставители порядка можно применять к любому классу, реализующему интерфейс Comparable.

Давайте рассмотрим несколько примеров со строками:

@Test
public void givenBenjamin_whenGreaterThanAmanda_thenCorrect() {
    String amanda = "Amanda";
    String benjamin = "Benjamin";
 
    assertThat(benjamin, is(greaterThan(amanda)));
}

@Test
public void givenAmanda_whenLessThanBenajmin_thenCorrect() {
    String amanda = "Amanda";
    String benjamin = "Benjamin";
 
    assertThat(amanda, is(lessThan(benjamin)));
}

Строка реализует алфавитный порядок в методе compareTo интерфейса Comparable.

Итак, имеет смысл, что слово «Аманда» стоит перед словом «Бенджамин».

4.3. Сопоставители порядка со значениями LocalDate

Так же, как и со строками, мы можем сравнивать даты. Давайте посмотрим на те же примеры, которые мы создали выше, но с использованием объектов LocalDate:

@Test
public void givenToday_whenGreaterThanYesterday_thenCorrect() {
    LocalDate today = LocalDate.now();
    LocalDate yesterday = today.minusDays(1);
 
    assertThat(today, is(greaterThan(yesterday)));
}

@Test
public void givenToday_whenLessThanTomorrow_thenCorrect() {
    LocalDate today = LocalDate.now();
    LocalDate tomorrow = today.plusDays(1);
    
    assertThat(today, is(lessThan(tomorrow)));
}

Очень приятно видеть, что выражение assertThat(сегодня, есть(меньшеЧем(завтра))) близко к обычному английскому языку.

4.4. Сопоставители заказов с пользовательскими классами

Итак, почему бы не создать собственный класс и не реализовать Comparable? Таким образом, мы можем использовать сопоставители ордеров для использования с пользовательскими правилами ордеров.

Давайте начнем с создания bean-компонента Person:

public class Person {
    String name;
    int age;

    // standard constructor, getters and setters
}

Теперь давайте реализуем Comparable:

public class Person implements Comparable<Person> {
        
    // ...

    @Override
    public int compareTo(Person o) {
        if (this.age == o.getAge()) return 0;
        if (this.age > o.getAge()) return 1;
        else return -1;
    }
}

Наша реализация compareTo сравнивает двух людей по их возрасту. Давайте теперь создадим пару новых тестов:

@Test
public void givenAmanda_whenOlderThanBenjamin_thenCorrect() {
    Person amanda = new Person("Amanda", 20);
    Person benjamin = new Person("Benjamin", 18);
 
    assertThat(amanda, is(greaterThan(benjamin)));
}

@Test
public void 
givenBenjamin_whenYoungerThanAmanda_thenCorrect() {
    Person amanda = new Person("Amanda", 20);
    Person benjamin = new Person("Benjamin", 18);
 
    assertThat(benjamin, is(lessThan(amanda)));
}

Сопоставители теперь будут работать на основе нашей логики compareTo.

5. Сопоставитель NaN

Hamcrest предоставляет один дополнительный сопоставитель чисел, чтобы определить, является ли число на самом деле числом:

@Test
public void givenNaN_whenIsNotANumber_thenCorrect() {
    double zero = 0d;
    
    assertThat(zero / zero, is(notANumber()));
}

6. Выводы

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

Более того, сопоставления Hamcrest в целом не требуют пояснений и легко читаются.

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

Полную реализацию примеров из этой статьи можно найти на GitHub.