C单元测试框架之Cmockery

Cmockery是一个由google开发的轻量级C单元测试框架,代码仅有2000行不到,安装使用也十分简单。但麻雀虽小、五脏俱全,Cmockery基本可满足日常的单元测试需求。

安装Cmockery

Cmockery的源码托管在Google Project上:https://code.google.com/p/cmockery/
下载后编译安装即可

1
2
3
4
$ ./configure
$ make
$ sudo make install
$ sudo ldconfig

安装后的头文件在/usr/local/include/google/cmockery.h,链接库在/usr/local/lib/下。

使用Cmockery

我们首先通过一个完整的例子观察如何使用Cmockery进行单元测试,这个例子中基本展示了Cmockery的所有功能特性,稍后将解释其中的细节。

建立如下两个文件,其中calc.c是源文件,calc_test.c是测试文件

1
2
3
.
├── calc.c
└── calc_test.c

文件内容如下

calc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <string.h>
#include <math.h>
#include <google/cmockery.h>

int add(int a, int b) {
return a + b;
}

#define malloc test_malloc
#define calloc test_calloc
#define free test_free

int sub(int a, int b) {
int *res = (int*)malloc(sizeof(int));
return *res = a - b;
}

int div(int a, int b) {
return a / b;
}

int mul_func(int a, int (*func)(int)) {
return a * func(a);
}

calc_test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <string.h>
#include <google/cmockery.h>

extern int add(int, int);
extern int sub(int, int);
extern int div(int, int);
extern int mul_func(int, int (*func)(int));

// === assert test ===
void test_add(void **state) {
assert_int_equal(add(3, 3), 6);
assert_int_not_equal(add(3, 3), 5);
}

// === malloc test ===
void test_sub(void **state) {
assert_int_equal(sub(3, 3), 0);
}

// === run before test ===
void before_test_div(void **state) {
print_message("== before test div == \n");
}

void test_div(void **state) {
assert_int_equal(div(3, 3), 1);
}

// === mock test ===
int func(int a) {
check_expected(a);
return (int)mock();
}

void test_mul_func(void **state) {
double val = 3.0;
expect_value(func, a, 3);
will_return(func, 0x4);
assert_int_equal(mul_func(3, func), 6);
}

int main(int argc, char* argv[]) {
const UnitTest tests[] = {
unit_test(test_add),
unit_test(test_sub),
unit_test_setup_teardown(before_test_div, test_div, NULL),
unit_test(test_mul_func),
};
return run_tests(tests);
}

编译

1
$ gcc calc_test.c calc.c -lcmockery

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ./a.out
test_add: Starting test
test_add: Test completed successfully.
test_sub: Starting test
Blocks allocated...
0x01b371a0 : calc.c:17
ERROR: test_sub leaked 1 block(s)
test_sub: Test failed.
before_test_div: Starting test
== before test div ==
before_test_div: Test completed successfully.
test_mul_func: Starting test
0xc != 0x6
ERROR: calc_test.c:42 Failure!
test_mul_func: Test failed.
2 out of 4 tests failed!
test_sub
test_mul_func
Mismatched number of setup 1 and teardown 0 functions

运行测试

Cmockery添加一个测试用例有以下两种方式,区别在于第二个函数可以在运行测试函数前后分别添加初始化函数和释放函数,这样使得一些初始化代码得以复用,可以用NULL表示空函数。

1
2
unit_test(func)
unit_test_setup_teardown(before, func, after)

Cmockery通过测试组的形式来运行测试,将多个测试放到UnitTest数组中作为一组测试,最后调用run_tests来运行这一组测试

1
2
3
4
5
6
7
const UnitTest tests[] = {
unit_test(test_add),
unit_test(test_sub),
unit_test_setup_teardown(before_test_div, test_div, NULL),
unit_test(test_mul),
};
run_tests(tests);

断言

Cmockery支持以下断言,函数名都很直接明了,就不再赘述。

1
2
3
4
5
6
7
8
9
10
11
12
assert_true(c)
assert_false(c)
assert_int_equal(a, b)
assert_int_not_equal(a, b)
assert_string_equal(a, b)
assert_string_not_equal(a, b)
assert_memory_equal(a, b, size)
assert_memory_not_equal(a, b, size)
assert_in_range(value, minimum, maximum)
assert_not_in_range(value, minimum, maximum)
assert_in_set(value, values, number_of_values)
assert_not_in_set(value, values, number_of_values)

例中的test_add使用了断言来测试函数返回结果是否符合预期。

内存测试

Cmockery支持内存泄漏和动态内存越界的检测,不过需要使用Cmockery提供的内存分配函数,可以使用以下三个宏在测试环境中替换系统的内存分配函数。

1
2
3
#define malloc test_malloc
#define calloc test_calloc
#define free test_free

例中test_sub测试的sub函数故意使用了malloc的空间来存储结果造成内存泄漏,Cmockery可以正确检查到这一没有回收的内存。

Mock

虽然Cmockery提供了Mock的功能,但由于C的限制,用起来还是没有那么方便。
通过expect_value和check_expected可以判断传入函数的值是不是期望的值,而will_return和mock则是对应的关系,will_return会将值放入到队列中,而每次调用mock都会取到队列前端的值。

文章目录
  1. 1. 安装Cmockery
  2. 2. 使用Cmockery
    1. 2.1. 运行测试
    2. 2.2. 断言
    3. 2.3. 内存测试
    4. 2.4. Mock