Написание модульных тестов для кода C
Я разработчик С++, и когда дело доходит до тестирования, легко протестировать класс, введя зависимости, переопределяя функции-члены и т.д., чтобы вы могли легко проверить края. Однако в C вы не можете использовать эти замечательные функции. Мне сложно добавить модульные тесты для кода из-за некоторых "стандартных" способов написания кода C. Каковы наилучшие способы решения следующих задач:
Передача большого указателя структуры контекста:
void some_func( global_context_t *ctx, .... )
{
/* lots of code, depending on the state of context */
}
Нет простого способа проверить отказ на зависимых функциях:
void some_func( .... )
{
if (!get_network_state() && !some_other_func()) {
do_something_func();
....
}
...
}
Функции с большим количеством параметров:
void some_func( global_context_t *, int i, int j, other_struct_t *t, out_param_t **out, ...)
{
/* hundreds and hundreds of lines of code */
}
Статические или скрытые функции:
static void foo( ... )
{
/* some code */
}
void some_public_func( ... }
{
/* call static functions */
foo( ... );
}
Ответы
Ответ 1
В целом, я согласен с ответом Уэса - будет намного сложнее добавить тесты на код, который не написан с учетом тестов. Там ничего не присуще C, что делает невозможным проверку - но, поскольку C не заставляет вас писать в определенном стиле, очень легко написать C-код, который трудно проверить.
На мой взгляд, написание кода с учетом тестов будет способствовать более коротким функциям, с несколькими аргументами, которые помогут смягчить некоторые из этих проблем в ваших примерах.
Во-первых, вам нужно выбрать инфраструктуру модульного тестирования. В этом вопросе есть много примеров (хотя, к сожалению, многие ответы - это С++-фреймворки - я бы посоветовал использовать С++ для тестирования C).
Я лично использую TestDept, потому что он прост в использовании, облегчен и позволяет выполнять stubbing. Тем не менее, я не думаю, что он очень широко используется. Если вы ищете более популярную структуру, многие рекомендуют Check - это здорово, если вы используете automake.
Вот несколько конкретных ответов для ваших случаев использования:
Передача большого указателя структуры контекста
В этом случае вы можете создать экземпляр структуры с предварительно заданными условиями вручную, а затем проверить статус структуры после выполнения функции. С короткими функциями каждый тест будет достаточно прост.
Нет простого способа проверить отказ на зависимых функциях
Я думаю, что это одно из самых больших препятствий с модульным тестированием C.
У меня был успех с помощью TestDept, что позволяет выполнять время выполнения зависимых функций. Это отлично подходит для разрыва жестко связанного кода. Вот пример из их документации:
void test_stringify_cannot_malloc_returns_sane_result() {
replace_function(&malloc, &always_failing_malloc);
char *h = stringify('h');
assert_string_equals("cannot_stringify", h);
}
В зависимости от вашей целевой среды это может работать или не работать. Подробнее см. их документацию.
Функции с большим количеством параметров
Это, вероятно, не тот ответ, который вы ищете, но я бы просто разбил их на более мелкие функции с меньшим количеством параметров. Намного легче проверить.
Статические или скрытые функции
Это не супер чисто, но я тестировал статические функции, включая исходный файл напрямую, позволяя вызвать статические функции. В сочетании с TestDept для создания чего-либо не тестируемого, это работает достаточно хорошо.
#include "implementation.c"
/* Now I can call foo(), defined static in implementation.c */
Много кода C - это устаревший код с небольшим количеством тестов, и в таких случаях обычно проще добавлять интеграционные тесты, которые сначала проверяют большие части кода, а не мелкозернистые модульные тесты. Это позволяет начать рефакторинг кода под интеграционным тестом до состояния, проверяемого блоком, хотя оно может или не может стоить инвестиций в зависимости от вашей ситуации. Конечно, вы захотите добавить модульные тесты к любому новому коду, написанному в течение этого периода, поэтому хорошая разработка и работа на ранней стадии - хорошая идея.
Если вы работаете с устаревшим кодом, эта книга (Эффективно работает с устаревшим кодом Майкла Перса), это отличное чтение.
Ответ 2
Это был очень хороший вопрос, призванный заманить людей в убеждение, что С++ лучше, чем C, потому что это более проверяемо. Однако это вряд ли так просто.
Написав много тестируемых C + + и C-кодов, и одинаково впечатляющее количество незаменимых С++ и C-кода, я могу с уверенностью сказать, что вы можете обернуть crappy untestable code на обоих языках. На самом деле большинство проблем, которые вы представляете выше, одинаково проблематичны в С++. EG, многие люди пишут инкапсулированные функции без объектов в С++ и используют их внутри классов (см. Расширенное использование статических функций С++ в классах, например, таких как функции MyAscii:: fromUtf8()).
И я уверен, что вы видели gazillion функции класса С++ со слишком большим количеством параметров. И если вы считаете, что только потому, что функция имеет только один параметр, лучше, рассмотрите случай, что внутренне он часто маскирует переданные в параметрах, используя кучу переменных-членов. Не говоря уже о "статических или скрытых" функциях (подсказка, помните, что ключевое слово private: ") так же велико, как проблема.
Итак, реальный ответ на ваш вопрос заключается не в том, что "C хуже по причинам, о которых вы заявляете", а скорее "вам нужно правильно его создать на C, как и на С++". Например, если у вас есть зависимые функции, поместите их в другой файл и верните количество возможных ответов, которые они могут предоставить, реализовав фальшивую версию этой функции при тестировании суперфункции. И это едва ли может быть изменено. Не выполняйте статические или скрытые функции, если вы хотите их протестировать.
Реальная проблема заключается в том, что вы, похоже, заявляете в своем вопросе, что вы пишите тесты для другой библиотеки, которую вы не пишете и не архитектор для правильной проверки. Тем не менее, есть тонна библиотек С++, которые демонстрируют те же самые симптомы, и если вам вручили один из них для тестирования, вы были бы столь же раздражены.
Решение всех проблем, подобных этому, всегда одно и то же: правильно напишите код и не используйте кому-то неправильно написанный код.
Ответ 3
При модульном тестировании C вы обычно включаете .c файл в тест, чтобы сначала проверить статические функции перед тестированием публичных.
Если у вас есть сложные функции, и вы хотите протестировать их код, тогда можно работать с макетными объектами. Взгляните на cmocka модульную систему тестирования, которая предлагает поддержку макетных объектов.