Перейти к основному содержимому

Введение в лямбда-поведение

· 5 мин. чтения

1. Обзор

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

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

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

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

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

2. Основы

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

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

3. Реализация лямбда-поведенческого теста

Каждый набор спецификаций начинается с 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, а затем добавим код для инициализации калькулятора.

Далее мы протестируем метод 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 , мы можем проверить, что, если мы реверсируем конкретную String дважды, мы получим исходную String.

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

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

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

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 .