Тестирование функции со статической глобальной переменной в Ceedling

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

//Pseudo code for file_under_test.c
static int global_var;

int func_under_test(){ 

   switch(global_var){
    case x:
      return some_value;
    case y:
      return some_other_value;
    .
    .
    .
    .
    default:
      return something; 
   }

}

4 ответа

Это очень распространенная проблема в модульном тестировании кода C, и наиболее распространенное решение, о котором я знаю, - исключить ключевое слово static при тестировании. Это требует некоторого планирования, и это трудно сделать в устаревшем коде, но любая статика, которую я планирую тестировать, заменяется какой-либо другой строкой. Обычно STATIC или еще лучше TESTABLE_STATIC.

Помните, что ceedling и, вероятно, большинство фреймворков модульного тестирования устанавливают макрос времени компиляции TEST, поэтому ваш код будет

//Pseudo code for file_under_test.c
#ifdef TEST
#define TESTABLE_STATIC 
#else
#define TESTABLE_STATIC static
#endif


TESTABLE_STATIC int global_var;

int func_under_test(){ 

   switch(global_var){
    case x:
      return some_value;
    case y:
      return some_other_value;
    .
    .
    .
    .
    default:
      return something; 
   }

}

Затем в вашем тестовом файле вы просто рассматриваете переменную как глобальную

// your ceedling test 
#include <your_functions.h>

extern int global_var;
void test_function_under_test(void)
{
    // your test code setting global_var as needed
    global_var = some_test_value;
    TEST_ASSERT_EQUAL(expected_val, func_under_test());
}

Я обычно скрываю TESTABLE_STATIC в файле заголовка проекта или если у вас есть файл datatypes.h, поэтому он обычно доступен везде в моем проекте.

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

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

файл wrapped_for_test.h

#include <file_under_test.h>
void set_for_test(int value);

файл wrapped_for_test.c

#include <file_under_test.c>

void set_for_test(int value)
{
  global_var = value;
}

Этот вопрос на самом деле не имеет ничего общего с Ceedling (или Unity, CMock и т. Д.), Но я скорее думаю, что это пример очень конкретной интерпретации слова "unit". Краткая версия моего ответа заключается в том, что пример функции, которую вы здесь написали, на самом деле не представляет собой автономную "единицу", поэтому я бы сказал, что она на самом деле не является "модульной-тестируемой".

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

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

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

* Это если только ваш пример не имеет побочного эффекта изменения самой статической переменной. В этом случае должна быть как минимум функция, которая сбрасывает "глобальное состояние", иначе ваши тесты не будут изолированы друг от друга (т.е. их сложно сделать независимыми от порядка). Лучшим решением было бы явно показать зависимость вашего состояния с помощью аргументовfunc_under_test, нравиться func_under_test(struct some_opaque_type *state) и добавить struct some_opaque_type *init_for_func_under_test() функция.

TL;DR: если ваша функция не является чистой функцией (например, она полагается на скрытое состояние и / или сама имеет побочные эффекты) или если у вас нет соответствующих "швов" (например, заглушек или шпионов), тогда также включайте функции который может изменить скрытое состояние или проверить побочные эффекты в вашем определении тестируемого модуля.

Вы можете создать вспомогательную функцию теста в file_under_test.c это настроит global_var перед звонком func_under_test(). При необходимости этот помощник тестовой функции может быть скомпилирован только для целей тестирования (с использованием определенного #ifdef), чтобы он не поставлялся с остальной частью кода, если ваш код создается, например, для продукта.

file_under_test.h:

void set_for_test(int value);

file_under_test.c

#ifdef TESTS
void set_for_test(int value)
{
  global_var = value;
}
#endif

test_file.c:

#include <assert.h>
#include "file_under_test.h"

// some other tests
set_for_test(3);
assert (func_under_test() == something);
//...
Другие вопросы по тегам