Написание модульных тестов для кода C - программирование
Подтвердить что ты не робот

Написание модульных тестов для кода 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( ... );
}
4b9b3361

Ответ 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 модульную систему тестирования, которая предлагает поддержку макетных объектов.