Я нашел решение, которое работает для меня, может быть, оно вам тоже поможет.
Использование MACROS само по себе может только помочь вам. Если вы хотите выполнить тест для функции, но затем заглушить ее различными способами, с помощью MACROS потребуется несколько раз перестроить код и выполнить каждое условие по отдельности. Это сложно автоматизировать - теперь вам нужно иметь пакетный скрипт, который будет определять различные символы, перестраивать код и агрегировать результаты.
Однако, если вы используете MACROS для определения указателя на функцию для каждой функции, которую вы хотите отключить, у вас есть работоспособное решение, при условии, что вы можете внести незначительные изменения в целевой код, который вы хотите протестировать.
Следующий пример находился под сильным влиянием следующего:
- http://eradman.com/posts/tdd-in-c.html
- http://locklessinc.com/articles/mocking/
- http://www.embedded.com/design/programming-languages-and-tools/4007177/2/Doing-C-code-unit-testing-on-a-shoestring-Part-1-The-basics-and-the-tools
MUT обозначает тестируемый модуль в этом примере.
Предположим, у вас есть четыре файла: mut.h, mut.c, test_mut.h, test_mut.c. Предположим также, что вы можете определить символ UNIT_TEST при его создании.
mut.h будет включать любые функции, которые являются общедоступными. Для этого примера их нет, поэтому давайте забудем об этом.
Итак, давайте начнем с версии mut.c
#include <cstdbool>
#include "mut.h"
static bool foo(int baz);
static bool bar(int baz);
static bool foo(int baz)
{
bool ret = false;
if(bar(baz))
{
//do something
ret = true;
}
else
{
ret = false;
}
return ret;
}
static bool bar(int baz)
{
//Black box mystery / Don't care
}
Давайте предположим, что мы уже протестировали блок. Работает нормально. Теперь мы хотим протестировать foo, но нам не хочется настраивать все, что нам нужно для того, чтобы бар работал правильно. Таким образом, мы должны заглушить бар.
Итак, давайте добавим новый заголовок test_mut.h. Среди прочего, вы должны иметь следующее в test_mut.h
#ifdef UNIT_TEST
...
//Convert every static definition into an extern declaration.
#define static extern
bool bar_mock_true (int baz);
bool bar_mock_false(int baz);
bool bar_real (int baz);
extern bool(*bar_ptr)(int baz);
#define bar bar_ptr
...
#endif
Итак, как вы можете видеть, мы определили новый указатель функции, который теперь может указывать на наши заглушки / насмешки или на нашу реальную функцию бара. Этот заголовок также будет включен в test_mut.c, так что функции-заглушки теперь могут быть определены внутри test_mut.c - их не нужно включать в mut.c, загромождая его.
Теперь давайте сделаем это полезным, нам нужно немного изменить mut.c
mut.c теперь должен будет включать "test_mut.h", стандартное объявление bar () должно быть отключено во время модульного тестирования, и нам нужно изменить определение функции на bar_real ()
...
#include "test_mut.h"
...
#ifdef UNIT_TEST
static bool bar(int baz);
#endif
...
#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
//Black box mystery / Don't care
}
Каждая функция, которую вам нужно заглушить, будет нуждаться в аналогичных #ifdefs и переименовании вокруг объявления и определения. Таким образом, ваш тестируемый код, к сожалению, будет немного загроможден.
Теперь test_mut.c может использовать ваш код следующим образом:
#include <cstdbool>
#include "test_mut.h"
...
UT_STATIC void test_foo(void)
{
int baz = 0;
extern bool foo(int baz);
//Test Case A
bar_ptr = bar_mock_true;
TEST_ASSERT(foo(baz), "Condition A");
//Test Case B
bar_ptr = bar_mock_false;
TEST_ASSERT(!foo(baz), "Condition B");
}
bool bar_mock_true(int baz)
{
return true;
}
bool bar_mock_false(int baz)
{
return false;
}
Вот несколько полных списков моих исходных файлов. Я построил облегченный тестовый жгут здесь, и он компилируется и запускается на встроенном компиляторе IAR Workbench (я не пробовал его ни на чем другом) и выдает следующий вывод
..
Тестовые прогоны: 2
mut.c
#include <cstdbool>
#include "mut.h"
#include "test_mut.h"
static bool foo(int baz);
#ifndef UNIT_TEST
static bool bar(int baz);
#endif
static bool foo(int baz)
{
bool ret = false;
if(bar(baz))
{
//do something
ret = true;
}
else
{
ret = false;
}
return ret;
}
#ifdef UNIT_TEST
static bool bar_real(int baz)
#else
static bool bar(int baz)
#endif
{
//Black box mystery / Don't care
}
test_mut.h
#ifdef UNIT_TEST
#ifndef _TEST_MUT_H
#define _TEST_MUT_H
//Handle to your test runner
void test_mut(void);
//Track the number of test cases that have executed
extern int tests_run;
//Convert every static definitions into extern declarations.
#define static extern
//An alternate definition of static for the test barness to use
#define UT_STATIC static
bool bar_mock_true (int baz);
bool bar_mock_false (int baz);
bool bar_real (int baz);
extern bool(*bar_ptr)(int baz);
#define bar bar_ptr
//Test Macros
#define TEST_FAIL(name) \
do \
{ \
printf("\nTest \"%s\" failed in %s() line %d\n", (name), __func__, __LINE__); \
} while(0)
#define TEST_ASSERT(test_true,test_name) \
do \
{ \
tests_run++; \
if(!(test_true)) \
{ \
TEST_FAIL(test_name); \
} \
else \
{ \
printf("."); \
} \
} while(0)
//... Insert any other macro instrumentation you may need...
#endif // _TEST_MUT_H
#endif // UNIT_TEST
test_mut.c
#ifdef UNIT_TEST
#include <cstdbool>
#include <cstdio>
#include "test_mut.h"
#include "mut.h"
UT_STATIC void test_foo(void);
int tests_run = 0;
inline UT_STATIC void test_report(void);
void test_mut(void) {
//call your setup function(s)
test_foo();
//call your teardown function(s)
test_report();
}
inline UT_STATIC void test_report(void)
{
printf("\nTests Run: %d\n", tests_run);
}
void main(void)
{
test_mut();
}
//Setup the function pointer for bar, by default it will point to the real
//bar function, and not a stub.
bool(*bar_ptr)(int baz) = bar_real;
UT_STATIC void test_foo(void)
{
int baz = 0;
extern bool foo(int baz);
//Test Case A
bar_ptr = bar_mock_true;
TEST_ASSERT(foo(baz), "Condition A");
//Test Case B
bar_ptr = bar_mock_false;
TEST_ASSERT(!foo(baz), "Condition B");
}
bool bar_mock_true(int baz)
{
return true;
}
bool bar_mock_false(int baz)
{
return false;
}
#endif