Обучение и трудоустройство Java-программистов от Junior до Senior

Тестирование коллекций

 
 
 
Сообщения:279
Люди, есть ли какие-либо рекомендации по юнит-тестированию коллекций? В плане входных данных в методы, возвращающие коллекции и тестирование выходных.

Частный случай: метод парсит текст и возвращает "хитрую" коолекцию:
SortedMap<Character, SortedSet<String>>
где ключ - буква алфавита, а значения - набор слов на эту букву.

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

Вот кажется, что данные для теста какие-то рафинированные получились. Теоретически бы их генерировать как-нибудь. Но тогда и результат надо правильный генерировать?

В общем, интересно, как это правильно делать?

"Любая техническая система должна быть идиотоустойчивой" (с) один из университетских преподов
 
 
Сообщения:9570
Можно такие тесты написать, каждый из которых генерирует случайные данные (статья):
1. Взять случайный элемент из результата (или пройтись по всем) и проверить что все строки там начинаются с указанной буквы.
2. Подсчитать что сколько строк вошли, столько и вышли.
3. Сгенерировать N значений, продублировать какие-то из них в тексте и проверить что на выходе всего N значений.

Это называется Тестированием Свойствами (Property Based Testing) потому как тестируется не значения результата а свойства которым этот результат или алгоритм удовлетворяет.

Вот так можно сгенерировать текст из 100 слов от 1 до 100 символов:
String text = "";
for(String word: between(1, 100).alphanumerics(100)) text += word + " ";
Изменен:09 авг 2017 06:19
 
 
Сообщения:279
Староверъ, спасибо!
Статью почитал. Выступление на Гейзенбаг посмотрел, хороший доклад (уверенности в голосе и в поведении побольше бы). А вот документация к Datagen как-то слабовата. Не понятно, в примерах - это все возможности или есть ещё какие-то? Если есть, то приходится по исходникам ползать (прикололся над "//Public Morozov").

Чтобы тестить свойствами, надо, конечно, мозг переключить. Отсюда следующий вопрос. Написал свой компаратор для char, что бы кириллическая буква "ё" вставала на своё место при сравнении (речь пока идёт о lower case). Напомню, символ "ё" в юникоде стоит "за алфавитом" и при сортировке оказывается после "я".

Самый простой способ протестить этот компаратор - это сделать три сравнения с "ё": 1) сам символ "ё"; 2) символ до "ё"; 3) символ после "ё".

Но вот решил немного заняться садомазохизмом поэкспериментировать в сторону Property Based Testing: в тесте взять случайный символ и сравнить его сначала стандартным компаратором. Если символ меньше или равен "е", то он должен быть и меньше "ё". Если же тот символ больше или равен "ж", то, соответственно, он больше "ё". И так - миллион раз.

Есть внутреннее чувство, что перемудрил и хочу попросить оценить следующее:

    private final static char YO_CHAR = '\u0451'; //cyrillic symbol 'ё'
    private final static char E_CHAR = '\u0435'; //cyrillic symbol 'е'
    private final static char ZH_CHAR = '\u0436'; //cyrillic symbol 'ж'

    @Test
    public void CharComparatorTest() {
        Comparator<Character> comparator = Comparators.getCharComparator();
        assertTrue(comparator.compare(YO_CHAR, YO_CHAR) == 0);
        String randomString = unicode(1_000_000);
        for (int i = 0; i < randomString.length(); i++) {
            char randomChar = randomString.charAt(i);
            if (randomChar == YO_CHAR) continue;
            if (Character.compare(randomChar, E_CHAR) <= 0) {
                assertTrue(comparator.compare(randomChar, YO_CHAR) < 0);
            } else if (Character.compare(randomChar, ZH_CHAR) >= 0) {
                assertTrue(comparator.compare(randomChar, YO_CHAR) > 0);
            }
        }
    }

"Любая техническая система должна быть идиотоустойчивой" (с) один из университетских преподов
 
 
Сообщения:9570
Mihnayan:
Написал свой компаратор для char, что бы кириллическая буква "ё" вставала на своё место при сравнении (речь пока идёт о lower case). Напомню, символ "ё" в юникоде стоит "за алфавитом" и при сортировке оказывается после "я".
Так это уже совсем другая проблема и совсем другие тесты..

Сортировки по-моему в принципе сложно тестировать. Но я бы писал что-то такое (думаю можно еще свойств придумать если посидеть):
import java.util.*;
import org.junit.Test;
import static io.qala.datagen.RandomShortApi.bool;
import static io.qala.datagen.RandomShortApi.integer;
import static io.qala.datagen.RandomValue.between;
import static org.junit.Assert.assertEquals;

public class CyrillicSortingTest {
    final static char[] alphabet;
    final static char[] lowerAlphabet;
    static {
        String lower = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя";
        String all = lower + lower.toUpperCase(Locale.forLanguageTag("ru"));
        lowerAlphabet = lower.toCharArray();
        alphabet = all.toCharArray();
    }
    @Test public void equalStringsDoNotChangeOrder() {
        String str = between(1, 100).string(alphabet);
        List<String> list = shuffled(Arrays.asList(str, new String(str), new String(str)));
        assertEquals(list, sortCyrillic(list));
    }

    @Test public void sortsAccordingToPositionInAlphabet() {
        StringBuilder smaller = new StringBuilder(between(2, 100).string(alphabet));
        StringBuilder bigger  = new StringBuilder(smaller);

        int strIndexToDiffer = integer(0, smaller.length()     - 1);
        int indexInAlphabet  = integer(0, lowerAlphabet.length - 2);//last symbol is reserved for the bigger string

        smaller.setCharAt(strIndexToDiffer, randomCyrillic(indexInAlphabet, indexInAlphabet));
        bigger .setCharAt(strIndexToDiffer, randomCyrillic(indexInAlphabet + 1));

        List<String> expected = Arrays.asList(smaller.toString(), bigger.toString());
        assertEquals(expected, sortCyrillic(shuffled(expected)));
    }

    @Test public void smallLettersGoBeforeBigOnes() {
        StringBuilder smaller = new StringBuilder(between(1, 100).string(alphabet));
        StringBuilder bigger  = new StringBuilder(smaller);

        int strIndexToDiffer = integer(0, smaller.length() - 1);

        smaller.setCharAt(strIndexToDiffer, Character.toLowerCase(smaller.charAt(strIndexToDiffer)));
        bigger .setCharAt(strIndexToDiffer, Character.toUpperCase(smaller.charAt(strIndexToDiffer)));

        List<String> expected = Arrays.asList(smaller.toString(), bigger.toString());
        assertEquals(expected, sortCyrillic(shuffled(expected)));
    }

    @Test public void firstDifferentLetterDefinesOrder_while2ndHasNoImpact() {
        int indexInAlphabet  = integer(0, lowerAlphabet.length - 2);//last symbol is reserved for the bigger string
        char smaller = randomCyrillic(indexInAlphabet, indexInAlphabet);
        char bigger = randomCyrillic(indexInAlphabet + 1);

        List<String> expected = Arrays.asList("" + smaller + bigger, "" + bigger + smaller);
        assertEquals(expected, sortCyrillic(shuffled(expected)));
    }

    private char randomCyrillic(int startIndex) {
        int endIndex = integer(startIndex, lowerAlphabet.length - 1);
        return randomCyrillic(startIndex, endIndex);
    }
    private char randomCyrillic(int startIndex, int endIndex) {
        int index = integer(startIndex, endIndex);
        char result = lowerAlphabet[index];
        return bool() ? result : Character.toUpperCase(result);
    }
    private static <T> List<T> shuffled(List<T> notShuffled) {
        List<T> result = new ArrayList<>(notShuffled);
        Collections.shuffle(result);
        return result;
    }
    private static List<String> sortCyrillic(List<String> strs) {
        Collator collator = Collator.getInstance(Locale.forLanguageTag("ru"));
        List<String> result = new ArrayList<>(strs);
        result.sort(collator);
        return result;
    }

}
 
 
Сообщения:20
Mihnayan:
Староверъ, спасибо!
Статью почитал. Выступление на Гейзенбаг посмотрел, хороший доклад (уверенности в голосе и в поведении побольше бы). А вот документация к Datagen как-то слабовата. Не понятно, в примерах - это все возможности или есть ещё какие-то? Если есть, то приходится по исходникам ползать (прикололся над "//Public Morozov").

Чтобы тестить свойствами, надо, конечно, мозг переключить. Отсюда следующий вопрос. Написал свой компаратор для char, что бы кириллическая буква "ё" вставала на своё место при сравнении (речь пока идёт о lower case). Напомню, символ "ё" в юникоде стоит "за алфавитом" и при сортировке оказывается после "я".

Самый простой способ протестить этот компаратор - это сделать три сравнения с "ё": 1) сам символ "ё"; 2) символ до "ё"; 3) символ после "ё".

Но вот решил немного заняться садомазохизмом поэкспериментировать в сторону Property Based Testing: в тесте взять случайный символ и сравнить его сначала стандартным компаратором. Если символ меньше или равен "е", то он должен быть и меньше "ё". Если же тот символ больше или равен "ж", то, соответственно, он больше "ё". И так - миллион раз.

Есть внутреннее чувство, что перемудрил и хочу попросить оценить следующее:

    private final static char YO_CHAR = '\u0451'; //cyrillic symbol 'ё'
    private final static char E_CHAR = '\u0435'; //cyrillic symbol 'е'
    private final static char ZH_CHAR = '\u0436'; //cyrillic symbol 'ж'

    @Test
    public void CharComparatorTest() {
        Comparator<Character> comparator = Comparators.getCharComparator();
        assertTrue(comparator.compare(YO_CHAR, YO_CHAR) == 0);
        String randomString = unicode(1_000_000);
        for (int i = 0; i < randomString.length(); i++) {
            char randomChar = randomString.charAt(i);
            if (randomChar == YO_CHAR) continue;
            if (Character.compare(randomChar, E_CHAR) <= 0) {
                assertTrue(comparator.compare(randomChar, YO_CHAR) < 0);
            } else if (Character.compare(randomChar, ZH_CHAR) >= 0) {
                assertTrue(comparator.compare(randomChar, YO_CHAR) > 0);
            }
        }
    }
Думаю это наиболее часто используемые. Поэтому их и затронули. Но фишек то конечно больше разных.
 
Модераторы:Нет
Сейчас эту тему просматривают:Нет