DevLabs 发表于 2015-1-19 18:12:46

C单元测试框架之Cmockery


Cmockery 是google开源的用于C单元测试的一个轻量级的框架. 它只需要测试程序与标准C库链接,此外, Cmockery尽量避免使用编译器中比较新的一些特性, 以保持对一些旧编译器的兼容性.

Cmockery一共有三个文件, Cmockery.c, Cmockery.h, config.h. 它们最终将和待测模块被编译成一个可独立运行的程序. 在Windows下的话makefile可以无视, 只要将上面三个文件加入到工程再写好对应的测试代码然后编译即可.
在嵌入式环境下我没有做测试, 但应该不会有问题.

在example文件夹里Google给出了一些例子可供参考, 目录结构可以参考这里

1.断言.
cmockery支持如下断言, 看名知意, 无需多言.

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);



2.mock()模拟函数.

此函数用于这样的场景: 模块所依赖的外部接口无法使用或者某些接口暂时还未实现时可用它来模拟该接口, 它的返回值由函数 will_return() 指定.

以之前写的表达式计算器为例.

在计算器源代码中有一个原型为 int get_input(char *buf) 的函数, 用来获取用户输入, 当要进行自动化的单元测试时肯定要采取某种方式模拟自动输入, 而这个输入要可以指定. 此时mock()就可以派上用场了.

在get_input()这个函数中, 用于获取输入的接口是getch(), 为了能自动模拟用户输入, 需要将此接口替换掉. 在这里使用一个宏将 getch() 替换为 test_getch(), 然后在单元测试模块中实现 test_getch(), 以实现自动输入的目地.

test_getch()的实现:


// 此函数将替换getch(), 用于模拟用户输入
int test_getch(void)
{
    // 此返回值使用will_return()函数指定, 强制转换为所需要的类型
    return (int)mock();
}



其中mock()的返回值是由will_return()指定的, mock()的默认返回值为 void * 类型, 将其转换为合适的目标类型即可.


get_input()的单元测试函数:

// 测试函数
void test_get_input(void **state)
{
    // 模拟用户输入的字符串
    char *user_input = "10   +9 - 8(\b* (4 - 3)2\b/2   \r";
    // 用户输入被处理之后得到的字符串
    char *input = "10+9-8*(4-3)/2";
    // 存储输入的缓存区
    char input_buf;

    size_t i;
    for (i = 0; i < strlen(user_input); i++)
    {
      // 当调用test_getch()时将依次返回此处写入的字符
      // test_getch()在get_input()函数中被调用
      will_return(test_getch, *(user_input + i));
    }

    // 测试, get_input()返回的是输入的有效字符数
    assert_int_equal(get_input(input_buf), strlen(input));
    // 测试, 得到的输入值是否符合期望
    assert_string_equal(input_buf, input);

    return;
}


其中 user_input 为模拟用户输入的字符流, input是经get_input()处理后应该得到的字符串.
test_getch()在get_input()中被调用, 在其被调用之前, 先使用will_return()将模拟输入的值存入队列, 当调用 test_getch()时将依次返回此处用will_return()写入队列的值.


3. expect_*()

ecpect_*()系列宏和check_expected()配合使用, 用于参数检查.

假设被测模块中有函数 foo() 如下:

int foo(int i)
{
    bar(i);

    return i;
}

其中bar()是被测模块外实现的函数.

在进行单元测试时, 为了测试能正常进行, 需要在单元测试模块中实现一个test_bar()的替代函数:

void test_bar(int i)
{
    // 在这里进行参数检查
    check_expected(i);

    // other code

    return i * i;
}

对foo()进行单元测试的代码:

void test_foo(void **state)
{
    // 检查test_bar()中传入的参数i的值是否为10.
    // test_bar()中的check_expected()函数将执行检查
    expect_value(test_bar, i, 10);

    assert_int_equal(foo(10), 100);

    return;
}


4. 用于单元测试的标准输出和错误输出函数

void print_message(const char* const format, ...);
void print_error(const char* const format, ...);
void vprint_message(const char* const format, va_list args);
void vprint_error(const char* const format, va_list args);

5. 测试的运行

所有测试函数的原型均为 void test_func(void **state);

unit_tests(func) 宏用来将测试函数添加入数组, 然后使用run_tests()来依次测试数组中的测试函数.

unit_test_setup_teardown(test, setup, teardown) 功能同上, 不过多了初始化与卸载函数. 其中setup是在测试之前进行初始化的函数, teardown是测试完成之后进行清场的函数. 这两个函数均可为NULL.


此外 cmockery 还可以测试动态内存分配, 找出内存泄漏等问题.

相关阅读:

http://coolshell.cn/articles/8209.html]“单元测试要做多细?”

Cmockery下载:


DevLabs 发表于 2015-1-19 19:55:58

没人感兴趣啊.

flotox 发表于 2015-1-19 20:30:13

不明白你说的是什么 Σ( ° △ °|||)︴

chxaitz 发表于 2015-1-19 21:09:59

没太看明白,期待LZ的详细教程~^_^

机器人天空 发表于 2015-1-19 22:02:52

没看懂,哈哈

ijlc1314 发表于 2015-1-19 22:08:57

顶楼主,只是平时都是赶进度,暂时用不上

卡斯发动机 发表于 2015-1-19 23:24:34

白盒测试吧,最近也在搞,只是没仔细看过,看样子是单函数测试?

DevLabs 发表于 2015-1-20 10:04:23

卡斯发动机 发表于 2015-1-19 23:24
白盒测试吧,最近也在搞,只是没仔细看过,看样子是单函数测试?

单元测试, 测试的最小单位就是函数.

DevLabs 发表于 2015-1-20 10:05:05

flotox 发表于 2015-1-19 20:30
不明白你说的是什么 Σ( ° △ °|||)︴

单元测试, 大部分人应该很少写......

dswkl11 发表于 2015-1-20 10:41:53

收藏一下先,虽然不懂单元测试。。。

xf331785508 发表于 2015-1-20 11:15:31

看过STM32库的人应该明白这东东,还是很不错的,至少能去除一些低极BUG。

DevLabs 发表于 2015-1-20 12:33:51

xf331785508 发表于 2015-1-20 11:15
看过STM32库的人应该明白这东东,还是很不错的,至少能去除一些低极BUG。

STM32库里那不算是单元测试, 如果我没记错的话只是使用了assert_param()进行了参数检查.

albert_w 发表于 2015-1-20 12:37:54

很好的帖子

wazhiyi 发表于 2015-1-20 13:59:44

这个需要教程

michael.yang 发表于 2015-1-20 17:47:05

搞java的时候,做过一些单元测试之类的事,但C语言的话,就没那么多心思来这样弄,特别又是应用在嵌入式上面的代码。
太折腾了,用断言就很好了。

xukai871105 发表于 2015-1-21 10:00:08

单元测试,我觉得这个东西在嵌入式开发中挺重要的,应该推进这个事情。

DevLabs 发表于 2015-1-21 11:47:58

xukai871105 发表于 2015-1-21 10:00
单元测试,我觉得这个东西在嵌入式开发中挺重要的,应该推进这个事情。

国内很多公司都不在意这个, 不过大多数工程师软件要写, 硬件要焊, 搞不好还有客串写个说明书什么的, 也没多少时间写吧.
但是了解这些还是必要的.

holts2 发表于 2015-1-21 12:07:34

DevLabs 发表于 2015-1-21 11:47
国内很多公司都不在意这个, 不过大多数工程师软件要写, 硬件要焊, 搞不好还有客串写个说明书什么的, 也没 ...

主要是要赶进度,公司又不会配专职的测试

xukai871105 发表于 2015-1-21 12:08:33

DevLabs 发表于 2015-1-21 11:47
国内很多公司都不在意这个, 不过大多数工程师软件要写, 硬件要焊, 搞不好还有客串写个说明书什么的, 也没 ...

您的博客真不错,果断收藏了。

C语言单元测试框架在我们现在的公司也一直在做,领导也算重视。
做这件事情还是需要人力支持,人力够那就好好干,人力不够那也没有办法了。
坚持一天两天基本没什么效果,坚持几年效果就很明显了。

myxiaonia 发表于 2015-1-21 12:20:08

xukai871105 发表于 2015-1-21 12:08
您的博客真不错,果断收藏了。

C语言单元测试框架在我们现在的公司也一直在做,领导也算重视。


你的也不错呀,我一起收藏了

twitter 发表于 2015-1-21 14:32:25

连有各种配套软件的版本控制都没多少搞c的在用,单元测试这个就更遥远了。

zhuser 发表于 2015-7-24 13:41:11

mark.收藏之。

zhangxun0712 发表于 2015-7-24 13:53:06

了解,学习了。   

dswkl11 发表于 2015-7-24 13:59:36

我还不太懂单元测试。。。悲剧

zyqcome 发表于 2016-3-24 14:33:36

谢谢楼主,刚刚在vs2012中通过测试代码

lzly0302 发表于 2017-5-18 14:46:39

楼主现在有在嵌入式里试过么

lzly0302 发表于 2017-5-18 14:47:18

xukai871105 发表于 2015-1-21 12:08
您的博客真不错,果断收藏了。

C语言单元测试框架在我们现在的公司也一直在做,领导也算重视。


现在有出效果了么,坚持了几年,是用在嵌入式领域么
页: [1]
查看完整版本: C单元测试框架之Cmockery