我正在寻找一种方法来存根位于同一 C 文件中的辅助方法。有没有办法在不修改源文件的情况下做到这一点?我正在考虑使用#define
方法替换方法b
,b_stub
但我认为这最终会重命名方法b
这是一个示例用例:
#include "file.h"
a(){
b();
}
b(){
}
我正在尝试创建一个测试框架,但我希望用户只需要包含一个包含框架和存根定义的文件。
谢谢。
我正在寻找一种方法来存根位于同一 C 文件中的辅助方法。有没有办法在不修改源文件的情况下做到这一点?我正在考虑使用#define
方法替换方法b
,b_stub
但我认为这最终会重命名方法b
这是一个示例用例:
#include "file.h"
a(){
b();
}
b(){
}
我正在尝试创建一个测试框架,但我希望用户只需要包含一个包含框架和存根定义的文件。
谢谢。
我不确定我是否完全理解你的问题。
如果你想调用一个不同的例程,b
那么你可以在编译时这样做:
a() {
#ifdef USE_STUB
b_stub();
#else
b();
#endif
}
或者,如果您总是想调用b
但想要b
表现不同,您可以在编译时执行以下操作:
a() {
b():
}
b() {
#ifdef USE_STUB
printf( "I am in the stub version of b()\n" );
#else
printf( "I am in the real version of b()\n" );
#endif
}
或者您可以在运行时执行类似的操作(为简单起见,此处使用全局变量显示):
a() {
extern int _use_stub;
if( _use_stub ) {
b_stub();
} else {
b();
}
}
或者
a() {
b();
}
b() {
extern int _use_stub;
if( _use_stub ) {
printf( "This is the stub code\n" );
} else {
printf( "This is the real code\n" );
}
}
使用编译时示例,您可以通过更改头文件或 Makefile 定义来回切换。使用运行时示例,您可以使用命令行选项、环境变量、用户首选项窗格或其他任何内容来回切换。
我找到了一个适合我的解决方案,也许它也会帮助你。
单独使用 MACROS 只能让你走这么远。如果您想对一个函数执行测试,然后使用 MACROS 以各种不同的方式将其存根,则需要您多次重建代码并单独运行每个条件。这很难自动化 - 现在您必须有一个批处理脚本来定义不同的符号并重建代码并汇总结果。
但是,如果您使用 MACROS 为要存根的每个函数定义一个函数指针,那么假设您可以对要测试的目标代码进行一些小的修改,那么您就有一个可行的解决方案。
以下示例深受以下因素的影响:
在此示例中,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
变种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
如果这是在框架代码而不是最终用户代码中,那么你可以做这样的事情
#ifndef UNIT_TEST_FUNC_B
b()
{
}
#endif
现在,当您想在 B 上运行单元测试时,您定义 UNIT_TEST_FUNC_B 并将存根代码包含在单独的模块中
或者如果您想将测试代码保留在同一个模块中,您可以这样做
#ifndef UNIT_TEST_FUNC_B
b()
{
}
#else
b()
{
// test code goes here
}
#endif
我为定义使用了一个唯一的名称,因此您可以为不同的测试提取不同的函数。
您必须修改源代码,但不多。
尝试这个
#define b() stub_b()
a(){
b();
}
(b)()
{
}
现在对方法 b() 的调用将被 stub_b() 替换,并且 b() 定义将保持不变。:)