Хорошие примеры модульных тестов для разработчиков встраиваемых Си

20

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

Я искал в Интернете хорошие примеры, но я изо всех сил пытался найти те, которые особенно применимы к нашей области разработки. Почти все программное обеспечение, которое мы пишем, представляет собой глубоко встроенные системы управления, работающие на небольших микроконтроллерах. Существует много кода на C, который легко применим к модульному тестированию (я буду говорить о модульном тестировании на ПК, а не на самой цели), если вы остаетесь в стороне от «нижнего» уровня: материала, который говорит непосредственно к периферии микроконтроллера. Тем не менее, большинство примеров, которые я обнаружил, как правило, основаны на обработке строк (например, превосходный пример римских цифр Dive Into Python), и поскольку мы почти никогда не используем строки, это не очень подходит (из-за единственных библиотечных функций, которые обычно использует наш код являются memcpy, memcmpи memset,strcat или регулярные выражения не совсем правильно).

Итак, к вопросу: пожалуйста, кто-нибудь может предложить несколько хороших примеров функций, которые я могу использовать для демонстрации модульного тестирования в режиме реального времени? Хороший ответ по моему (может быть изменен) мнению, вероятно, будет:

  • Функция, которая достаточно проста, чтобы ее мог понять каждый (даже тот, кто только иногда пишет код);
  • Функция, которая не выглядит бессмысленной (т. Е. Вычисление четности или CRC, вероятно, лучше, чем функция, которая умножает два числа вместе и добавляет случайную константу);
  • Функция достаточно короткая, чтобы писать перед комнатой людей (я могу воспользоваться множеством буферов обмена Vim, чтобы уменьшить количество ошибок ...);
  • Функция, которая принимает числа, массивы, указатели или структуры в качестве параметров и возвращает нечто подобное, а не обрабатывает строки;
  • Функция, которая имеет простую ошибку (например, >а не >=), которую легко внедрить, будет по-прежнему работать в большинстве случаев, но не справится с некоторым конкретным крайним случаем: легко идентифицировать и исправить с помощью модульного теста.

Есть предположения?

Хотя это, вероятно, не имеет значения, сами тесты, вероятно, будут написаны на C ++ с использованием Google Test Framework: все наши заголовки уже содержат #ifdef __cplusplus extern "C" {оболочку; это хорошо сработало с тестами, которые я провел до сих пор.

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

Ответы:

15

Вот простая функция, которая должна генерировать контрольную сумму по длинам байтов.

int checksum(void *p, int len)
{
    int accum = 0;
    unsigned char* pp = (unsigned char*)p;
    int i;
    for (i = 0; i <= len; i++)
    {
        accum += *pp++;
    }
    return accum;
}

У него есть ошибка забора: в операторе for должен быть тест i < len.

Что интересно, если вы примените его к текстовой строке, как это ...

char *myString = "foo";
int testval = checksum(myString, strlen(myString));

Вы получите «правильный ответ»! Это потому, что дополнительный байт, который был контрольной суммой, был нулевым ограничителем строки. Таким образом, вы можете включить эту функцию контрольной суммы в код, и, возможно, даже отправить ее вместе с ней, и никогда не заметить проблему - то есть, пока вы не начнете применять ее к чему-то, кроме текстовых строк.

Вот простой модульный тест, который помечает эту ошибку (большую часть времени ... :-)

void main()
{
    // Seed the random number generator
    srand(time(NULL));

    // Fill an array with junk bytes
    char buf[1024];
    int i;
    for (i = 0; i < 1024; i++)
    {
        buf[i] = (char)rand();
    }

    // Put the numbers 0-9 in the first ten bytes
    for (i = 0; i <= 9; i++)
    {
        buf[i] = i;
    }

    // Now, the unit test. The sum of 0 to 9 should
    // be 45. But if buf[10] isn't 0 - which it won't be,
    // 255/256 of the time - this will fail.
    int testval = checksum(buf, 10);
    if (testval == 45)
    {
        printf("Passed!\n");
    }
    else
    {
        printf("Failed! Expected 45, got %d\n", testval);
    }
}
Боб Мерфи
источник
Очень хорошо! Это именно тот ответ, на который я надеялся: спасибо.
DrAl
Когда вы создаете буфер, у вас уже есть мусор в этом куске памяти, действительно ли необходимо инициализировать его случайными числами?
Снейк Сандерс
@ SnakeSanders Я бы сказал, да, потому что вы хотите, чтобы юнит-тесты были как можно более детерминированными. Если используемый вами компилятор поместит 0 на вашем компьютере разработчика и 10 на вашем тестовом компьютере, вам будет очень трудно найти ошибку. Я действительно думаю, что по той же причине плохая идея - зависеть от времени вместо фиксированного начального числа.
Андрей говорит восстановить Монику
Полагаться на недетерминированное поведение в модульном тесте - плохая идея. Слабый тест рано или поздно доставит вам головную боль ...
Сиги
2

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

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

Еще несколько идей:

  • Модульные тесты очень помогают при рефакторинге. Так что, как только ваша пузырьковая сортировка сработает, вы можете изменить ее на более мощную сортировку qsort, и тесты все равно должны пройти, подтверждая, что ваша новая функция сортировки также работает.
  • Сортировку легко проверить, результат либо отсортирован, либо нет, что делает его хорошим кандидатом.
  • То же самое для поиска; он либо существует, либо его нет.
  • Написание тестов для сортировки открывает дискуссии о том, какой тип ввода использовать для теста (ноль элементов, случайный ввод, повторяющиеся записи, огромные массивы и т. Д.).
Мартин Викман
источник
Есть ли у вас какие-либо конкретные предложения по простой ошибке, которая показала бы, как тестирование облегчает жизнь?
DrAl
@DrAl: обновил мой ответ с этим.
Мартин Уикман,