«1. Обзор

В этой статье мы обсудим новую среду тестирования на основе Java под названием Lambda Behave.

Как следует из названия, эта среда тестирования предназначена для работы с Java 8 Lambdas. Далее в этой статье мы рассмотрим спецификации и рассмотрим пример для каждой из них.

Зависимость Maven, которую нам нужно включить:

<dependency>           
    <groupId>com.insightfullogic</groupId>
    <artifactId>lambda-behave</artifactId>
    <version>0.4</version>
</dependency>

Последнюю версию можно найти здесь.

2. Основы

Одной из целей фреймворка является достижение высокой удобочитаемости. Синтаксис побуждает нас описывать тестовые случаи, используя полные предложения, а не несколько слов.

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

3. Реализация Lambda Behave Test

Каждый набор спецификаций начинается с Suite.describe. На данный момент у нас есть несколько встроенных методов для объявления нашей спецификации. Таким образом, Suite подобен тестовому классу JUnit, а спецификации подобны методам, аннотированным @Test в JUnit.

Для описания теста мы используем should(). Точно так же, если мы назовем параметр ожидания лямбда как «ожидание», мы могли бы сказать, какой результат мы ожидаем от теста, с помощью expect.that().

Если мы хотим настроить или удалить какие-либо данные до и после спецификации, мы можем использовать it.isSetupWith() и it.isConcludedWith(). Таким же образом, чтобы сделать что-то до и после Suite, мы будем использовать it.initiatizesWith() и it.completesWith().

Давайте рассмотрим пример простой тестовой спецификации для класса Calculator:

public class Calculator {

    public int add() {
        return this.x + this.y;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException();
        }
        return a / b;
    }
}

Мы начнем с Suite.describe, а затем добавим код для инициализации Calculator.

Далее мы протестируем метод add(), написав спецификацию:

{
    Suite.describe("Lambda behave example tests", it -> {
        it.isSetupWith(() -> {
            calculator = new Calculator(1, 2);
        });
 
        it.should("Add the given numbers", expect -> {
            expect.that(calculator.add()).is(3);
        });
}

Здесь мы назвали переменные «it» и «expect» для удобства чтения. Поскольку это имена параметров лямбда, мы можем заменить их любыми именами по нашему выбору.

Первый аргумент функции should() описывает на простом английском языке, что должен проверять этот тест. Второй аргумент — это лямбда, указывающая на наше ожидание, что метод add() должен вернуть 3.

Давайте добавим еще один тестовый пример для деления на 0 и проверим, получим ли мы исключение:

it.should("Throw an exception if divide by 0", expect -> {
    expect.exception(ArithmeticException.class, () -> {
        calculator.divide(1, 0);
    });
});

В этом В этом случае мы ожидаем исключения, поэтому мы объявляем expect.exception() и внутри этого пишем код, который должен генерировать исключение.

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

4. Спецификации, управляемые данными

Эта структура позволяет параметризовать тесты на уровне спецификации.

Чтобы создать пример, давайте добавим метод в наш класс Calculator:

public int add(int a, int b) {
    return a + b;
}

Давайте напишем для него управляемый данными тест:

it.uses(2, 3, 5)
  .and(23, 10, 33)
  .toShow("%d + %d = %d", (expect, a, b, c) -> {
    expect.that(calculator.add(a, b)).is(c);
});

Метод uses() используется для указать входные данные в разное количество столбцов. Первые два аргумента — это параметры функции add(), а третий — ожидаемый результат. Эти параметры также можно использовать в описании, как показано в тесте.

toShow() используется для описания теста с использованием параметров – со следующим выводом:

0: 2 + 3 = 5 (seed: 42562700892554)(Lambda behave example tests)
1: 23 + 10 = 33 (seed: 42562700892554)(Lambda behave example tests)

5. Сгенерированные спецификации – тестирование на основе свойств

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

Например, когда мы тестируем функцию реверсирования строки, мы можем проверить, что если мы реверсируем конкретную строку дважды, мы получим исходную строку.

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

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

Итак, наш тест, основанный на реверсивном свойстве String, будет выглядеть так:

it.requires(2)
  .example(Generator.asciiStrings())
  .toShow("Reversing a String twice returns the original String", 
    (expect, str) -> {
        String same = new StringBuilder(str)
          .reverse().reverse().toString();
        expect.that(same).isEqualTo(str);
   });

Мы указали количество необходимых тестов с помощью метода require(). Мы используем предложение example(), чтобы указать, какой тип объектов нам нужен и как.

Результат для этой спецификации:

0: Reversing a String twice returns the original String(ljL+qz2) 
  (seed: 42562700892554)(Lambda behave example tests)
1: Reversing a String twice returns the original String(g) 
  (seed: 42562700892554)(Lambda behave example tests)

5.1. Генерация детерминированного теста

«Когда мы используем автоматически сгенерированные тестовые примеры, становится довольно сложно изолировать сбои тестов. Например, если наша функциональность дает сбой один раз из 1000, спецификацию, которая автоматически генерирует только 10 случаев, придется запускать снова и снова, чтобы обнаружить ошибку.

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

Lambda Behave может справиться с этой проблемой. Как показано в выводе предыдущего тестового примера, он выводит начальное число, которое использовалось для генерации случайного набора тестовых случаев. Так что, если что-то пойдет не так, мы можем использовать начальное число для воссоздания ранее сгенерированных тестовых случаев.

Мы можем посмотреть на результат теста и определить начальное число: (начальное число: 42562700892554). Теперь, чтобы снова сгенерировать тот же набор тестов, мы можем использовать SourceGenerator.

SourceGenerator содержит метод deterministicNumbers(), который принимает в качестве аргумента только начальное число:

 it.requires(2)
   .withSource(SourceGenerator.deterministicNumbers(42562700892554L))
   .example(Generator.asciiStrings())
   .toShow("Reversing a String twice returns the original String", 
     (expect, str) -> {
       String same = new StringBuilder(str).reverse()
         .reverse()
         .toString();
       expect.that(same).isEqualTo(str);
});

При выполнении этого теста мы получим тот же результат, что и ранее.

6. Заключение

В этой статье мы увидели, как писать модульные тесты с использованием лямбда-выражений Java 8 в новой среде свободного тестирования под названием Lambda Behave.

Как всегда, код этих примеров можно найти на GitHub.