使用C:模拟嵌套函数调用的Cmocka单元测试



所以,这个玩具程序复制了我使用cmocka为现有代码开发单元测试时遇到的问题。问题是嵌套函数调用不模拟,这使得单元测试依赖于嵌套函数调用的正确执行。请注意,使用"mockable_static"定义是因为原始代码具有作为"内部函数调用"存在的静态函数,但出于单元测试的目的,这些函数对外部调用是开放的。(见stackoverflow的帖子,这个想法是从哪里来的(

事不宜迟,下面是代码:

函数h:

#ifndef FUNC_H_
#define FUNC_H_
#ifdef UNIT_TESTING
#define mockable_static
mockable_static char* bar();
#endif
char* foo();
#endif // FUNC_H_

函数c:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef UNIT_TESTING
#define mockable_static static
#else
#define mockable_static
#endif
mockable_static char* bar (){
printf("This is bar!n");
char *str = "This is the result of bar!";
return str;
}
char* foo(){
printf("This is foo, and it should return the results of bar()n");
char * res;
res = bar();
return res;
}

test.c:

#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>
#include "func.h"
static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);
char* __real_bar();
char* __wrap_bar(){
printf("This is a wrap and doesn't return bar!n");
return (char*)mock();
}
int main(void){
//bar test
const struct CMUnitTest bar_tests[] = {
cmocka_unit_test(test_bar),
cmocka_unit_test(test_wrap_bar)
};
const struct CMUnitTest foo_tests[] = {
cmocka_unit_test(test_foo)
};
//foo test w/ mocking bar
int status;
status = cmocka_run_group_tests(bar_tests,NULL,NULL);
status = cmocka_run_group_tests(foo_tests,NULL,NULL);
printf("Status = %dn",status);
return status;
}
static void test_bar(void **state){
char expected_res[] = "This is the result of bar!";
char * actual_res;
actual_res = __real_bar();
assert_string_equal(actual_res,expected_res);
}
static void test_wrap_bar(void **state){
char * this =  "I don't want bar!";
will_return(__wrap_bar,this);
char * res = bar();
assert_string_equal(res,this);
}
static void test_foo(void **state){
char * this =  "I don't want bar!";
will_return(__wrap_bar,this);
char * res = foo();
assert_string_equal(res,this);
}

gcc编译行:

gcc ./test.c ./func.c -DUNIT_TESTING -g -Wl,--wrap=bar -o test -lcmocka-static

测试执行结果:

[==========] Running 2 test(s).
[ RUN      ] test_bar
This is bar!
[       OK ] test_bar
[ RUN      ] test_wrap_bar
This is a wrap and doesn't return bar!
[       OK ] test_wrap_bar
[==========] 2 test(s) run.
[  PASSED  ] 2 test(s).
[==========] Running 1 test(s).
[ RUN      ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[  ERROR   ] --- "This is the result of bar!" != "I don't want bar!"
[   LINE   ] --- ./test.c:59: error: Failure!
[  FAILED  ] test_foo
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_foo
1 FAILED TEST(S)
Status = 1

正如您所看到的,bar((并没有被封装在foo((中,但在封装测试中,bar的封装方式与foo(((调用bar的方式完全相同。Bar使用__real_Bar((进行测试,该函数是cmocka测试库的一部分(虽然__real_Bar((有一个原型,但从未定义该函数,并根据cmocka文档返回预期结果。有人有在嵌套函数调用上使用单元测试的经验吗?我还没有发现任何用cmocka模拟嵌套函数调用的结果,但我的谷歌foo可能缺乏。如果在test_foo((结束时删除断言,则由于will_return队列中未使用的值,测试失败。

[==========] Running 2 test(s).
[ RUN      ] test_bar
This is bar!
[       OK ] test_bar
[ RUN      ] test_wrap_bar
This is a wrap and doesn't return bar!
[       OK ] test_wrap_bar
[==========] 2 test(s) run.
[  PASSED  ] 2 test(s).
[==========] Running 1 test(s).
[ RUN      ] test_foo
This is foo, and it should return the results of bar()
This is bar!
[  ERROR   ] --- %s() has remaining non-returned values.
./test.c:56: note: remaining item was declared here
[  FAILED  ] test_foo
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_foo
1 FAILED TEST(S)
Status = 1

好吧,有几种不同的方法可以解决这个问题。我正在发布解决方案,以便其他人可以看到。

解决方案#1:将嵌套函数调用分离到单独的.c文件中。IE-func.c包含foo((,(newfile(bar.c包含bar((。这允许GCC--wrap=bar在func.c中工作,因为它需要链接到另一个文件。

解决方案2:为测试bar和foo构建单独的测试。使用函数c中的以下行使条成为"弱">

__attribute__((weak))
mockable_static char* bar ().............(code follows)

在测试foo的文件中,使用一个模拟的bar,我们将bar((重新定义为定义的原始char*__wrap_bar((函数。使用__attribute__((弱((,这个重新定义的条将覆盖原始条,我们可以继续强制它在测试文件中给出我们想要的结果。

生成的test_foo.c文件如下所示:

#include <setjmp.h> /* needs to be before cmocka.h */
#include <stdio.h>
#include <string.h>
#include <cmocka.h>
#include "func.h"
#include "bar.h"
static void test_bar(void **state);
static void test_wrap_bar(void **state);
static void test_foo(void **state);

char* bar(){
printf("This is a wrap and doesn't return bar!n");
return (char*)mock();
}
int main(void){
//bar test
const struct CMUnitTest bar_tests[] = {
cmocka_unit_test(test_wrap_bar)
};
const struct CMUnitTest foo_tests[] = {
cmocka_unit_test(test_foo)
};
//foo test w/ mocking bar
int status;
status = cmocka_run_group_tests(bar_tests,NULL,NULL);
status += cmocka_run_group_tests(foo_tests,NULL,NULL);
printf("Status = %dn",status);
return status;
}
static void test_wrap_bar(void **state){
char * this =  "I don't want bar!";
will_return(bar,this);
char * res = bar();
assert_string_equal(res,this);
}
static void test_foo(void **state){
char * this =  "I don't want bar!";
will_return(bar,this);
char * res = foo();
assert_string_equal(res,this);
}

其中func.c文件为:

#include <stdio.h>
#include <string.h>
#ifndef UNIT_TEST
#define mockable_static static
#else
#define mockable_static __attribute__((weak))
#endif

mockable_static char* bar (){
printf("This is bar!n");
char *str = "This is the result of bar!";
//char *str = "This is the resfjkl;dsaj of bar!";
return str;
}
char* foo(){
printf("This is foo, and it should return the results of bar()n");
char * res;
res = bar();
return res;
}

将有一个单独的文件test_bar.c,它不会重新定义bar,并且可以在func.c中测试bar((。

你的第一个解决方案就是解决我自己的问题!发帖让其他人看到/评论/对我大喊大叫:(

同事们,谢谢你们的帮助!

最新更新