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

Пример проверки свойств с помощью Vavr

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

1. Обзор

В этой статье мы рассмотрим концепцию тестирования свойств и ее реализацию в библиотеке vavr-test .

Тестирование на основе свойств (PBT) позволяет нам указать высокоуровневое поведение программы в отношении инвариантов, которых она должна придерживаться.

2. Что такое тестирование свойств?

Свойство представляет собой комбинацию инварианта с генератором входных значений . Для каждого сгенерированного значения инвариант обрабатывается как предикат и проверяется, дает ли он истинное или ложное значение для этого значения.

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

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

3. Зависимость от Maven

Во- первых, нам нужно добавить зависимость Maven в библиотеку vavr-test :

<dependency>
<groupId>io.vavr</groupId>
<artifactId>jvavr-test</artifactId>
<version>${vavr.test.version}</version>
</dependency>

<properties>
<vavr.test.version>2.0.5</vavr.test.version>
</properties>

4. Написание тестов на основе свойств

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

private static Predicate<Integer> divisibleByTwo = i -> i % 2 == 0;
private static Predicate<Integer> divisibleByFive = i -> i % 5 == 0;

private Stream<String> stringsSupplier() {
return Stream.from(0).map(i -> Match(i).of(
Case($(divisibleByFive.and(divisibleByTwo)), "DividedByTwoAndFiveWithoutRemainder"),
Case($(divisibleByFive), "DividedByFiveWithoutRemainder"),
Case($(divisibleByTwo), "DividedByTwoWithoutRemainder"),
Case($(), "")));
}

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

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

Arbitrary<Integer> multiplesOf2 = Arbitrary.integer()
.filter(i -> i > 0)
.filter(i -> i % 2 == 0 && i % 5 != 0);

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

Затем нам нужно определить условие, которое проверяет, возвращает ли тестируемая функция правильное значение для данного аргумента:

CheckedFunction1<Integer, Boolean> mustEquals
= i -> stringsSupplier().get(i).equals("DividedByTwoWithoutRemainder");

Чтобы запустить тест на основе свойств, нам нужно использовать класс Property :

CheckResult result = Property
.def("Every second element must equal to DividedByTwoWithoutRemainder")
.forAll(multiplesOf2)
.suchThat(mustEquals)
.check(10_000, 100);

result.assertIsSatisfied();

Мы указываем, что для всех произвольных целых чисел, кратных 2, должен выполняться предикат mustEquals . Метод check() принимает размер сгенерированного ввода и количество запусков этого теста.

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

Необходимо изменить Произвольный поставщик и CheckedFunction :

Arbitrary<Integer> multiplesOf5 = Arbitrary.integer()
.filter(i -> i > 0)
.filter(i -> i % 5 == 0 && i % 2 == 0);

CheckedFunction1<Integer, Boolean> mustEquals
= i -> stringsSupplier().get(i).endsWith("DividedByTwoAndFiveWithoutRemainder");

Затем мы можем запустить тест на основе свойств для тысячи итераций:

Property.def("Every fifth element must equal to DividedByTwoAndFiveWithoutRemainder")
.forAll(multiplesOf5)
.suchThat(mustEquals)
.check(10_000, 1_000)
.assertIsSatisfied();

5. Вывод

В этой быстрой статье мы рассмотрели концепцию тестирования на основе свойств.

Мы создали тесты с помощью библиотеки vavr -test ; мы использовали классы Arbitrary, CheckedFunction и Property для определения теста на основе свойств с помощью vavr-test.

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.