单元测试:CMocka单元测试框架(支持mock功能)

CMocka概述

官网: https://cmocka.org/
源码: https://gitlab.com/cmocka/cmocka
CMocka API: CMocka API

CMocka 是一款支持 mock 对象、面向C语言的单元测试框架,CMocka 往往是编译成库的形式,供C单元测试程序链接调用。其前身是谷歌开发的Cmockery,由于后者缺少维护,因此 CMocka 继承了 Cmockery 并继续维护。

CMocka 框架的特性:

  • 支持模拟对象,可设置模拟函数的期望返回值,期望输出参数,可检查模拟函数的输入参数、函数调用顺序。
  • 支持Test fixtures(包括setup和teardown).
  • 不依赖第三方库,只需要一个C库即可.
  • 支持众多平台(Linux, BSD, Solaris, Windows和嵌入式平台)和编译器(GCC, LLVM, MSVC, MinGW等).
  • 提供对异常信号(SIGSEGV, SIGILL, …)的处理。
  • 非fork()执行.
  • 提供基本的内存检测,包括内存泄露,内存溢出检测.
  • 提供丰富的断言宏.
  • 支持多种格式输出 (STDOUT, SUBUNIT, TAP, XML).
  • 开源.

CMocka编译安装

CMocka 往往是以库的形式提供给测试程序链接调用的,为此我们需要先将 CMocka 源码编译成库。

从官网下载 CMocka 源码(系统中centos7),解压,然后:

# cd cmocka
# mkdir build && cd build
# cmake -DCMAKE_INSTALL_PREFIX=/usr/lib ..
# make
# make install

Linux 动态库的默认搜索路径是 /lib 和 /usr/lib

编写CMocka测试程序

通常用法

源码 cmocka/example/simple_test.c 很好的展示了 CMocka 的通常用法,代码如下所示:

#include 
#include 
#include 
#include 
// 在 #include  之前,必须先 #include 上面四个头文件,这是官网 The CMocka API 中明确要求的,在 cmocka.h 头文件开头部分也有注明。
#include 

/* null_test_success 函数是测试用例: 什么也没有做,而且成功返回了*/
static void null_test_success(void **state) {
    (void) state; /* unused */
}

int main(void) {
      // CMUnitTest 结构体是测试用例集(可以包含多个测试用例),每个测试用例可以设定可选的 startup 和 teardown 函数,用于负责执行测试前的初始化和测试后的销毁工作
    const struct CMUnitTest tests[] = {
    //  用了 cmocka_unit_test 宏来填充 CMUnitTest 结构体中的测试用例( startup 和 teardown 为 NULL)
        cmocka_unit_test(null_test_success),
    };

// cmocka_run_group_tests 函数用于启动测试并展示测试结果,可以为测试集指定全局的 startup 和 teardown(示例中都是NULL)。
    return cmocka_run_group_tests(tests, NULL, NULL);
}

我们来执行它

# cd cmocka/example
# gcc -o simple_test simple_test.c -lcmocka

# ./simple_test 
[==========] Running 1 test(s).
[ RUN      ] null_test_success
[       OK ] null_test_success
[==========] 1 test(s) run.
[  PASSED  ] 1 test(s).

测试函数

CMocka 的测试用例是一个C函数,其函数签名为:

CMocka 的测试用例是一个C函数,其函数签名为:

其中void **state指针是指向 CMUnitTest结构体中的void *initial_state 变量的地址,在初始化 CMUnitTest结构体时会设置initial_state 变量初始值(如 cmocka_unit_test 宏会设置 initial_state 为 NULL,还有其他初始化的宏,会在后续中介绍),state指针也会传递给测试用例对应的 setupteardown函数。

int setup(void **state)
int teardown(void **state)

执行测试用例的过程中,会按先后顺序依次调用setup、test_func 和 teardown 三个函数,它们都可以访问(读或写) CMUnitTest结构体中的initial_state 变量。

源码cmocka/tests/test_basics.c 中的示例很好的演示了 state 指针的使用,代码如下(有删减):

static int setup(void **state) {
    int *answer = malloc(sizeof(int));
    assert_non_null(answer);
    *answer = 42;
    *state = answer;
    return 0;
}

static int teardown(void **state) {
    free(*state);
    return 0;
}

static void int_test_success(void **state) {
    int *answer = *state;
    assert_int_equal(*answer, 42);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test_setup_teardown(int_test_success, setup, teardown),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}

除此之外,源码 cmocka/tests/test_fixtures.c 还演示了有关state 指针的更多用法

初始化CMUnitTest结构体

CMUnitTest 结构体变量用于存放测试用例集,该结构体定义如下:

struct CMUnitTest {
    const char *name;                /* 测试用例名 */
    CMUnitTestFunction test_func;    /* 测试用例函数指针 */
    CMFixtureFunction setup_func;    /* 测试用例对应的setup函数指针 */
    CMFixtureFunction teardown_func; /* 测试用例对应的teardown函数指针 */
    void *initial_state;             /* 测试用例私有数据指针 */
};

CMocka 提供了足够丰富的宏用于初始化 CMUnitTest 结构体,官网API手册中的 Running Tests 模块列出了所有的这些宏定义:

#define 	cmocka_unit_test(f)   { #f, f, NULL, NULL, NULL }
#define 	cmocka_unit_test_setup(f, setup)   { #f, f, setup, NULL, NULL }
#define 	cmocka_unit_test_teardown(f, teardown)   { #f, f, NULL, teardown, NULL }
#define 	cmocka_unit_test_setup_teardown(f, setup, teardown)   { #f, f, setup, teardown, NULL }
#define 	cmocka_unit_test_prestate(f, state)   { #f, f, NULL, NULL, state }
#define 	cmocka_unit_test_prestate_setup_teardown(f, setup, teardown, state)   { #f, f, setup, teardown, state }

其中 cmocka_unit_test 宏最为简单,只设置测试用例函数,其他为NULL。cmocka_unit_test_prestate_setup_teardown 宏最丰富,可设置测试用例的所有信息。

执行测试

CMocka 即支持cmocka_run_group_tests函数执行所有测试用例集,也支持run_test函数执行单个测试用例。

static void null_test_success(void **state) {
    (void) state;
}

int main(void) {
    return run_test(null_test_success);
}

不管是哪种方式执行测试,当执行测试用例遇到错误时,会立刻中断并退出当前测试用例,测试程序将继续执行下一测试用例。

CMocka API

官网API手册 The CMocka API 中有这么一张图,它清晰的描述了 CMocka API 由哪些子模块构成。

单元测试:CMocka单元测试框架(支持mock功能)_第1张图片

断言

CMocka 提供了一组用于测试逻辑条件的断言,其使用方法和标准C中的 assert 差不多。比如要测试 add 函数(两个整形数之和),可以:

static void test_add(void **state) {
    (void) state;

    assert_int_equal(add(1, 2), 3);
}

官网API手册中的 Assert Macros 模块中列出了 CMocka 框架支持的所有断言

模拟函数

CMocka 框架带有模拟函数(Mock Functions)的功能,可以为模拟函数设置期望返回值,设置期望输出参数,检查输入参数,检查调用顺序。

模拟函数返回值

other

有一些特殊的函数,用于在不执行逻辑测试的情况下,向框架注册通过或失败。这对于测试控制流或其他不需要逻辑测试的情况都非常有用:

  • void fail(void)

立刻中断当前测试用例的执行,将其标记为失败,并继续执行下一个测试用例会(如果有的话)。

  • *void fail_msg(const char *msg,…)

跟fail()一样,只是多了打印日志信息。

  • void skip(void)

立刻中断当前测试用例的执行,将其标记为跳过,并继续执行下一个测试用例会(如果有的话)。

源码 cmocka/tests/test_skip.c演示了 skip() 的用法,可自行查阅,fail() 用法类似。

你可能感兴趣的:(C++)