我找到了一个适合我的解决方案,也许它也会对你有所帮助。
单独使用 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
}
假设我们已经对 bar 进行了单元测试。它工作正常。现在我们想测试 foo 但我们不想设置我们需要的所有东西以使 bar 正确执行。所以我们需要把bar去掉。
所以让我们包含一个新的头文件 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
所以,如您所见,我们已经定义了一个新的函数指针,它现在可以指向我们的存根/模拟或我们的真实 bar 函数。此标头也将包含在 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 嵌入式工作台编译器上编译和运行(我没有在其他任何东西上尝试过)并产生以下输出
..
测试运行: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