使用 C 的 Cmocka 单元测试:模拟嵌套函数调用
Cmocka unit testing with C: mocking nested function calls
所以,玩具程序重复了我在使用 cmocka 为现有代码开发单元测试时遇到的问题。问题是嵌套函数调用没有模拟,这使得单元测试依赖于嵌套函数调用是否正常执行。请注意,使用 "mockable_static" define 是因为原始代码具有以 "internal function calls" 形式存在的静态函数,但出于单元测试的目的,这些函数对外部调用开放。 ()
事不宜迟,代码如下:
func.h:
#ifndef FUNC_H_
#define FUNC_H_
#ifdef UNIT_TESTING
#define mockable_static
mockable_static char* bar();
#endif
char* foo();
#endif // FUNC_H_
func.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 = %d\n",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() 有原型,但函数从未定义,returns 根据 cmocka 文档的预期结果. 任何人有在嵌套函数调用上使用单元测试的经验吗?我还没有找到用 cmocka 模拟嵌套函数调用的任何结果,但我的 google-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 内工作,因为它需要 link 针对另一个文件。
解决方案 #2:为测试 bar 和 foo 构建单独的测试。在 func.c 中使用以下行来制作 bar "weak"
__attribute__((weak))
mockable_static char* bar ().............(code follows)
在测试 foo 的文件中,使用模拟的 bar,我们重新定义 bar() 以充当定义的原始 char* __wrap_bar() 函数。使用 __attribute__((weak)),这个重新定义的柱会覆盖原来的柱,我们可以继续强制它在测试文件中给出我们想要的结果。
生成的 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 = %d\n",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()
是的,第一个解决方案是解决我自己的问题!在我那里为其他人发帖到 see/comment/yell :)
同事们,感谢您的帮助!
所以,玩具程序重复了我在使用 cmocka 为现有代码开发单元测试时遇到的问题。问题是嵌套函数调用没有模拟,这使得单元测试依赖于嵌套函数调用是否正常执行。请注意,使用 "mockable_static" define 是因为原始代码具有以 "internal function calls" 形式存在的静态函数,但出于单元测试的目的,这些函数对外部调用开放。 (
事不宜迟,代码如下:
func.h:
#ifndef FUNC_H_
#define FUNC_H_
#ifdef UNIT_TESTING
#define mockable_static
mockable_static char* bar();
#endif
char* foo();
#endif // FUNC_H_
func.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 = %d\n",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() 有原型,但函数从未定义,returns 根据 cmocka 文档的预期结果. 任何人有在嵌套函数调用上使用单元测试的经验吗?我还没有找到用 cmocka 模拟嵌套函数调用的任何结果,但我的 google-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 内工作,因为它需要 link 针对另一个文件。
解决方案 #2:为测试 bar 和 foo 构建单独的测试。在 func.c 中使用以下行来制作 bar "weak"
__attribute__((weak))
mockable_static char* bar ().............(code follows)
在测试 foo 的文件中,使用模拟的 bar,我们重新定义 bar() 以充当定义的原始 char* __wrap_bar() 函数。使用 __attribute__((weak)),这个重新定义的柱会覆盖原来的柱,我们可以继续强制它在测试文件中给出我们想要的结果。
生成的 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 = %d\n",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()是的,第一个解决方案是解决我自己的问题!在我那里为其他人发帖到 see/comment/yell :)
同事们,感谢您的帮助!