Как проверить статическую функцию

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

Ответов (12)

Решение

У меня есть тестовая подвеска. В тяжелых случаях - например, при попытке протестировать статическую функцию, я использую:

#include "code_under_test.c"
...test framework...

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

Если вы находитесь в среде Unix вы можете включить в тест файл дополнительный заголовок yourheader_static.h с декларациями ваших статических функций и перевести OBJ файл code_under_test.o через objdump --globalize-symbols=syms_name_file глобализовать локальные символы. Они будут видны как нестатические функции.

Это можно сделать двумя способами.

  1. Включите исходный файл c в исходный файл модульного тестирования, чтобы статический метод теперь находился в области действия исходного файла модульного тестирования и мог быть вызван.

  2. Сделайте трюк:

#define static

в заголовке исходного файла модульного тестирования.

Он преобразует ключевое слово static в "ничего" или, я могу сказать, удаляет его static из исходного кода c.

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

static=

Инструмент преобразует все static ключевые слова в "ничего"

Но я должен сказать, что использование этих способов приводит к тому, что static метод (или переменная) теряет свое конкретное значение.

#define static

Это очень плохая идея. Если у вас есть переменная, объявленная локальной для функции, это меняет поведение функции. Пример:

static int func(int data)
{
   static int count = 0;

   count += data;
   return count;
}

Вы можете вызвать функцию из модульного теста, поскольку функция func () будет экспортирована, однако базовая функциональность кода будет изменена.

--курт

Если вы используете Ceedling и пытаетесь использовать метод #include «code_under_test.c», тестовая сборка завершится неудачно, потому что она автоматически попытается создать «code_under_test.c» один раз при #included, а также потому, что это цель теста .

Мне удалось обойти это путем небольшой модификации кода code_under_test.c и нескольких других изменений. Оберните весь файл code_under_test.c этой проверкой:

#if defined(BUILD)
...
#endif // defined(BUILD)

Добавьте это в свой тестовый жгут:

#define BUILD
#include "code_under_test.c"

Добавьте BUILD определение в свой Makefile или файл конфигурации проекта:

# Makefile example
..
CFLAGS += -DBUILD
..

Теперь ваш файл будет собран из вашей среды и при включении из вашей тестовой среды. Ceedling теперь не сможет построить файл во второй раз (убедитесь, что ваш файл project.yml НЕ определяет BUILD).

Все предложенные выше ответы (за исключением нескольких) предполагают условную компиляцию, которая требует изменения исходного кода. Таким образом, это не должно быть проблемой, это просто добавит беспорядка (только для тестирования). Скорее можно сделать что-то вроде этого.

Скажите, что ваша функция для тестирования

static int foo(int);

Вы создаете другой файл заголовка с именем testing_headers.h, который будет иметь содержимое -

static int foo(int);
int foo_wrapper(int a) {
    return foo(a);
}

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

Для clang и gcc флаг есть --include . Для компилятора Microsoft C это так /FI .

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

Если вам не нужна нестатическая оболочка, вы также можете создать нестатический глобальный указатель на функцию, инициализированный значением foo.

Затем вы можете вызвать функцию, используя этот глобальный указатель на функцию.

Просто чтобы добавить к принятому ответу Джонатана Леффлера и подробно рассказать о других упоминаниях функции-оболочки:

  1. Создайте тестовый исходный файл, как в test_wrapper_foo.c, где foo.c - это оригинал.
  2. В test_wrapper_foo.c:
#include "foo.c"   

// Prototype
int test_wrapper_foo();

// wrapper function
int test_wrapper_foo() {
    // static function to test
    return foo();
}

Предполагая, что статическая функция foo в foo.c имеет прототип: int foo (void);

  1. создайте test_wrapper_foo.c через свой make-файл вместо foo.c (обратите внимание, что это не нарушит никаких зависимостей от функций в foo.c другими внешними функциями)

  2. В сценарии модульного тестирования вызовите test_wrapper_foo () вместо foo ().

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

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

static int foo ()
{
   return 3;
}

#ifdef UNIT_TEST
int test_foo ()
{
  if (foo () == 3)
    return 0;

  return 1;
}
#endif

Нет - вы не можете напрямую протестировать статическую функцию, не изменив хотя бы немного источник (это определение static в C - то, что он не может быть вызван из функции в другом файле).

Вы можете создать отдельную функцию в тестовом файле, которая просто вызывает статическую функцию?

Например:

//Your fn to test
static int foo(int bar)
{
  int retVal;
  //do something
  return retVal;
}

//Wrapper fn
int test_foo(int bar)
{
  return foo(bar);
}

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

Для модульных тестов у нас фактически есть тестовый код в самом исходном файле, и мы условно компилируем его при тестировании. Это дает модульным тестам полный доступ ко всем функциям и переменным уровня файла (статическим или другим).

Сами модульные тесты не статичны - это позволяет нам вызывать модульные тесты из одной супертестовой программы, которая тестирует все модули компиляции.

Когда мы отправляем код, мы условно компилируем модульные тесты, но на самом деле в этом нет необходимости (если вы хотите быть уверены, что отправляете точно такой же код, который вы тестировали).

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

Не могли бы вы дать дополнительную информацию о том, почему вы не можете вызвать функцию?

Он недоступен, потому что он является частным для файла .c? Если это так, лучше всего использовать условную компиляцию, которая позволяет получить доступ к функции, чтобы другие единицы компиляции могли получить к ней доступ. Например

SomeHeaderSomewher.h

#if UNIT_TEST
#define unit_static 
#else
#define unit_static static
#endif

Foo.h

#if UNIT_TEST
void some_method
#endif

Foo.cpp

unit_static void some_method() ...

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

Выходные данные (возвращаемые значения / побочные эффекты) публичной функции следует использовать для проверки эффекта статики.

Это означает, что вам нужны соответствующие заглушки, чтобы «уловить» эти побочные эффекты. (например, если функция вызывает файловый ввод-вывод, вам необходимо предоставить заглушки, чтобы переопределить эти файловые функции ввода-вывода lib). Лучший способ сделать это - сделать каждый набор тестов отдельным проектом / исполняемым файлом и избегать ссылок на какие-либо внешние функции библиотеки. Вы можете издеваться даже над функциями C, но это того не стоит.

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