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

时间:2020-02-25 18:08:48

标签: c unit-testing cmocka

因此,玩具程序复制了我使用cmocka为现有代码开发单元测试时遇到的问题。问题是嵌套函数调用不会模拟,这使得单元测试依赖于嵌套函数调用的正确执行。请注意,使用“ mockable_static”定义是因为原始代码具有作为“内部函数调用”存在的静态函数,但是出于单元测试的目的,这些函数对外部调用开放。 (See stackoverflow post where this idea came from

事不宜迟,这是代码:

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()进行测试,而__real_bar()是cmocka测试库的一部分(而__real_bar()具有原型,该函数从未定义,并且不会根据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 个答案:

答案 0 :(得分:1)

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

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

解决方案2:为测试bar和foo构建单独的测试。在func.c中使用以下行将条形设置为“弱”

joined_dfs <- purrr::map_df(list_species, bold::bold_seqspec)

在正在测试foo的文件中,带有模拟的bar,我们重新定义bar()以充当原始char * __wrap_bar()函数的定义。使用__attribute __((weak)),此重新定义的条形将覆盖原始条形,我们可以继续强制其根据需要在测试文件中提供结果。

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

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

func.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);
}

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

是的,第一个解决方案是解决我自己的问题的方法!发布给其他人查看/评论/对我大喊:)

同事们,感谢您的帮助!