«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, поэтому его легко импортировать и запускать как есть.