Как Java8 может помочь в написании юнит-тестов

Проблема

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

И вот тут неожиданно на помощь приходит Java 8! Казалось бы, при чём тут?

А вот при чём.

Как решается сейчас

Допустим, есть у вас класс Employee:

public class Employee {
  public String firstName;
  public String lastName;
  public Date birthDate;
  public String birthPlace;
  public Gender gender;
}

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

Как создавать кучу таких разных объектов в тестах?

Обычно есть четыре варианта, и все они не очень.

Вариант 1: засилье конструкторов

Создаётся куча разных конструкторов на все случаи жизни.

public Employee(String lastName) {
  this(null, lastName, null, null, null);
}

public Employee(Date birthDay, String birthPlace) {
  this(null, null, birthDay, birthPlace, null);
}

и так далее.

Минус этого решения понятен: слишком много конструкторов. Учитывая, что используются они только в тестах - сомнительная идея…

Вариант 2: засилье null

Второй вариант - иметь один “общий” конструктор со всеми возможными параметрами. Тогда ваши тесты превращаются в кучу “null” через запятую:

@Test public void test1() {
  new Employee(null, "Smith", null, null, null);
}

@Test public void test2() {
  new Employee(null, new Date(11, 11, 2011), "Happiness", null);
}

Минус и тут очевиден: такие тесты плохо читаются. Непонятно, “Happiness” - это какое именно поле? Фамилия? Место предыдущей работы? Место отсидки? Ожидания от работы? Приходится заглядывать внутрь, чтобы понять. IDE, конечно, чуть облегчают эту задачу, но всё же.

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

Вариант 3: засилье вспомогательных методов

Можно ещё плодить в тестах вспомогательные методы-фабрики для создания нужных объектов:

public void GenderTest {
  private static Employee withBirthdate(Date birthDate, String birthPlace) {...}
  private static Employee withGender(Gender gender) {...}
}

Это вполне себе нормальный вариант. Но всё-таки приходится писать много вспомогательного кода, и часто такие методы дублируются в разных тестах.

Вариант 4: засилье билдеров

Все любят паттерны. Паттерны-фигаттерны. Паттерн “билдер” не исключение. Его часто советуют.

  @Test public void test1() {
    Employee employee = Employee.builder()
      .withFirstName("john")
      .withLastName("Smith")
      .withGender(MALE)
      .build();
  }

Выглядит красиво, но на самом деле многовато усилий ради тестов, не? Кода для билдеров придётся написать довольно прилично.

Наверное, есть даже библиотеки, автоматизирующие создание билдеров. Это здорово. Но есть вариант проще - Java 8 предлагает решение из коробки.

И что же даст Java 8?

В Java 8 появились лямбды. Никто не знает, зачем они нужны, но типа круто. Так вот теперь настало время лямбд - от них появилась реальная польза!

Смотрите, как теперь можно создавать объекты в тестах:

  @Test public void test1() {
    Employee employee = new Employee(e -> e.lastName = "Smith");
  }

  @Test public void test2() {
    Employee employee = new Employee(e -> {
      e.birthDate = dateX; 
      e.birthPlace = "PlaceX";
    });
  }

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

И для этого не надо писать кучу конструкторов, вспомогательных методов или билдеров. Нужен всего-навсего один конструктор:

public Employee(Consumer<Employee> builder) {
  builder.accept(this);
}

И всё! Тесты читаемые, лишнего кода почти ноль.

Будущее уже здесь, йо.

Андрей Солнцев

ru.selenide.org