Тестирование на основе свойств для пользовательского упорядоченного списка в Java

При следующих требованиях к заказу:

Все строки, начинающиеся с foo, должны быть первыми.

Все строки, начинающиеся с бара, должны быть последними.

Строки, которые не начинаются с foo или bar, также могут присутствовать в списке.

Как можно использовать тестирование на основе свойств для проверки реализации вышеуказанных требований без головной боли?

Есть ли что-то более элегантное, чем следующее:

List<String> strings = Arrays.asList("foo", "bar", "bar1", "jar");
Collections.shuffle(strings);
assertListStartWith(strings, "foo");
assertListEndsWith(strings, "bar", "bar1");
assertThat(strings, hasItem( "jar"));

person Erez Ben Harush    schedule 29.06.2020    source источник
comment
Вы уже пробовали что-нибудь??   -  person Sabareesh Muralidharan    schedule 29.06.2020
comment
возможно, вы можете отсортировать по методу сортировки потока.   -  person melody zhou    schedule 29.06.2020
comment
@SabareeshMuralidharan У меня есть решение, включающее 2 функции assertListStartsWith(List‹String› lst, String... shouldStarWith) и assertListEndsWith(List‹String› lst, String... shouldEndWith), но оно кажется очень сложным, мне было интересно, есть лучшее решение   -  person Erez Ben Harush    schedule 29.06.2020
comment
@melodyzhou, на самом деле это то, как работает решение, я предпочитаю не тестировать его так же, как работает решение.   -  person Erez Ben Harush    schedule 29.06.2020
comment
Подобно strings.stream().sorted((s1, s2) -> { if(s1.startWith(foo)) return -1;}) , возвращает результат сравнения.   -  person melody zhou    schedule 29.06.2020
comment
Существует много исследований на эту тему, и это звучит очень интересно. Это может быть вариант для исследовательских проектов, я думаю, это может быть применимо к бизнес-кейсам, когда множество разных разработчиков с разным опытом и опытом должны поддерживать тестовые примеры. johanneslink.net/how-to-specify-it   -  person Armando Ballaci    schedule 29.06.2020


Ответы (1)


Я предполагаю, что у вас есть какая-то функция сортировки с подписью

List<String> sortFooBar(List<String> list)

Я вижу как минимум пять свойств, которым должен соответствовать sortFooBar(list):

  1. Сохранить все элементы — и только те — в списке
  2. Нет элемента перед first foo
  3. Нет других элементов между first и last foo
  4. Нет элемента после последней строки
  5. Нет другого элемента между первой и последней полосой

В реальном функциональном языке все эти свойства довольно легко сформулировать на Java, для этого требуется немного кода. Итак, вот мой взгляд на проблему с использованием jqwik в качестве платформы PBT и AssertJ для утверждений:

import java.util.*;
import java.util.function.*;
import org.assertj.core.api.*;
import net.jqwik.api.*;

class MySorterProperties {
    
    @Property
    void allItemsAreKept(@ForAll List<@From("withFooBars") String> list) {
        List<String> sorted = MySorter.sortFooBar(list);
        Assertions.assertThat(sorted).containsExactlyInAnyOrderElementsOf(list);
    }
    
    @Property
    void noItemBeforeFoo(@ForAll List<@From("withFooBars") String> list) {
        List<String> sorted = MySorter.sortFooBar(list);
        int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
        if (firstFoo < 0) return;
        Assertions.assertThat(sorted.stream().limit(firstFoo)).isEmpty();
    }
    
    @Property
    void noItemBetweenFoos(@ForAll List<@From("withFooBars") String> list) {
        List<String> sorted = MySorter.sortFooBar(list);
        int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
        int lastFoo = findLast(sorted, item -> item.startsWith("foo"));
        if (firstFoo < 0 && lastFoo < 0) return;
        List<String> allFoos = sorted.subList(
            Math.max(firstFoo, 0),
            lastFoo >= 0 ? lastFoo + 1 : sorted.size()
        );
        Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("foo"));
    }
    
    @Property
    void noItemAfterBar(@ForAll List<@From("withFooBars") String> list) {
        List<String> sorted = MySorter.sortFooBar(list);
        int lastBar = findLast(sorted, item -> item.startsWith("bar"));
        if (lastBar < 0) return;
        Assertions.assertThat(sorted.stream().skip(lastBar + 1)).isEmpty();
    }
    
    @Property
    void noItemBetweenBars(@ForAll List<@From("withFooBars") String> list) {
        List<String> sorted = MySorter.sortFooBar(list);
        int firstBar = findFirst(sorted, item -> item.startsWith("bar"));
        int lastBar = findLast(sorted, item -> item.startsWith("bar"));
        if (firstBar < 0 && lastBar < 0) return;
        List<String> allFoos = sorted.subList(
            Math.max(firstBar, 0),
            lastBar >= 0 ? lastBar + 1 : sorted.size()
        );
        Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("bar"));
    }
    
    @Provide
    Arbitrary<String> withFooBars() {
        Arbitrary<String> postFix = Arbitraries.strings().alpha().ofMaxLength(10);
        return Arbitraries.oneOf(
            postFix, postFix.map(post -> "foo" + post), postFix.map(post -> "bar" + post)
        );
    }
    
    int findFirst(List<String> list, Predicate<String> condition) {
        for (int i = 0; i < list.size(); i++) {
            String item = list.get(i);
            if (condition.test(item)) {
                return i;
            }
        }
        return -1;
    }
    
    int findLast(List<String> list, Predicate<String> condition) {
        for (int i = list.size() - 1; i >= 0; i--) {
            String item = list.get(i);
            if (condition.test(item)) {
                return i;
            }
        }
        return -1;
    }
}

И это наивная реализация, соответствующая спецификации:

class MySorter {
    static List<String> sortFooBar(List<String> in) {
        ArrayList<String> result = new ArrayList<>();
        int countFoos = 0;
        for (String item : in) {
            if (item.startsWith("foo")) {
                result.add(0, item);
                countFoos++;
            } else if (item.startsWith("bar")) {
                result.add(result.size(), item);
            } else {
                result.add(countFoos, item);
            }
        }
        return result;
    }
}

В этом примере код для свойств превышает объем кода для реализации. Это может быть хорошо или плохо в зависимости от того, насколько сложно желаемое поведение.

person johanneslink    schedule 29.06.2020
comment
Java — один из наименее выразительных языков, с которыми я работал. Несмотря на это, приведенный выше Java-код открыл мне глаза на установление порядка в общем виде. - person Erez Ben Harush; 29.06.2020