Текстовые данные в юнит тестах

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

Основная идея была очень проста. Есть повторяющийся, очень монотонный код тестов. Чтобы сделать его читаемым, пишется универсальное тело теста, а все необходимые вариации описываются параметрами. Параметры упаковываются в таблицу, что очень важно для улучшения читаемости. Когда все параметры собраны в одном месте (в пределах одного экрана), анализировать поведение тестируемого кода легче, чем когда параметры разбросаны по файлу длиной в сотни строк.

Как сделать код еще более понятным? К примеру, возьмем таблицу из предыдущего поста. Что с ней не так?

struct {
    int x;
    int y;
    std::string that_thing;
    std::string this_key;
    bool result;
} const cases[] = {
    {0, 0, "thing1", "key2", false},
    {0, 1, "thing1", "key2", false},
    {0, 2, "thing1", "key2", false},
    {0, 3, "thing1", "key1", true },
    {1, 0, "thing2", "key1", false},
    {1, 1, "thing2", "",     false},
    {1, 2, "thing2", "",     true },
    {2, 0, "",       "",     false},
    {2, 1, "",       "",     true },
    {2, 2, "",       "",     false},
};

В этой таблице слишком много знаков препинания: скобки, запятые, кавычки. К параметрам в этой таблице неудобно писать комментарии и еще сложнее подружить удобно выровненные колонки с clang-format (clang-format off - это единственный выход). Более того, столкнувшись с реальностью, подобная таблица станет еще менее читаемой - колонки вылезут за правый край экрана, выравнивание будет принесено в жертву более компактной записи и т.д.

В такой ситуации мне нравится записывать данные как простой текст. Сравните:

    # Simple cases.
    0 0 thing1 key2 false
    0 1 thing1 key2 false
    0 2 thing1 key2 false
    0 3 thing1 key1 true

    # Try something harder.
    1 0 thing2 key1 false
    1 1 thing2 ""   false
    1 2 thing2 ""   true

    # This shouldn't break it.
    2 0 ""     ""   false
    2 1 ""     ""   true
    2 2 ""     ""   false

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

Чтобы скормить эти данные коду, потребуется написать парсер для текста. Так как формат умышленно выбран простым, его парсер может уложиться буквальнов несколько строк:

std::stringstream data(
    R"(
        0 0 thing1 key2 false
        0 1 thing1 key2 false
        0 2 thing1 key2 false
        0 3 thing1 key1 true

        1 0 thing2 key1 false
        1 1 thing2 ""   false
        1 2 thing2 ""   true

        2 0 ""     ""   false
        2 1 ""     ""   true
        2 2 ""     ""   false
    )");

while (data.good())
{
    int x, y;
    std::string thing, key, result;

    data >> x >> y >> thing >> key >> result;
    if (!data.good())
    {
        break;
    }

    std::cout << x << " " << y << " " <<  thing << " " <<  key << " "
              << result << "\n";
}

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

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

Вариант 1: сравнить заголовок побайтно с ожидаемым заголовком:

const char expected[] = {
    '\x45', '\x00', '\x00', '\x3c', '\x17', '\x47', '\x40', '\x00',
    '\x40', '\x06', '\xe7', '\xbb', '\xc0', '\xa8', '\x01', '\x92',
    '\xad', '\xc2', '\xcb', '\xbc',
};

Вариант 2: распечатать заголовок полученного пакета и сравнить с ожидаемым как текст (игнорируя лишние пробелы):

const std::string expected = R"(
    Internet Protocol Version 4, Src: 192.168.1.146, Dst: 173.194.203.188
        Differentiated Services Field: 0x00
        Total Length: 60
        Identification: 0x1747
        Flags: 0x02 (Don't Fragment)
        Fragment offset: 0
        Time to live: 64
        Protocol: TCP
        Header checksum: 0xe7bb
        Source: 192.168.1.146
        Destination: 173.194.203.188
)";

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

comments powered by Disqus