概述
介绍
cmocka作为开源的单元测试工具,基于google发布的cmockery工具,目前被libssh、csync等软件所采用。作为源代码单元测试工具,其功能强大,且方便使用。本文结合单元测试中的一些规范,对于cmokca的使用进行分析和说明。
cmocka基本使用说明
cmocka基于google的单元测试工具cmockery,其主要功能有:
- 内存操作检查
- 模拟函数返回值
- 函数入参检查
- 不同类型的验证宏定义
cmocka作为独立的工具,编译安装之后通过lib库方式进行调用。为了便于使用,在测试过程中,也可以直接将cmocka中的cmocka.c作为源文件,然后在单元测试程序中包含cmocka.h。
1. 内存操作检查
cmocka通过宏定义,将malloc、calloc、free重定向到自己实现的内存操作函数_test_malloc、_test_calloc、_test_free。
记录了内存操作的大小,底层仍然调用了glibc的malloc、calloc、free函数。
2. 模拟函数返回值
在测试模块中,可以通过will_return(function, type)设置模拟函数function的返回值type,然后在模拟函数中,通过mock_type(type)函数得到相应type的值。Cmocka通过维护数据堆栈进行上述操作,即:
mock_type(type)和will_return(function, type)是成对出现的。
通过设定模拟函数中的返回值,避免了对于模拟函数的多次实现。
3. 函数入参检查
程序模块unit.c中的函数func_t运行过程中调用了func_call,func_t根据输入和运行逻辑的不同,在调用func_call是传入的参数不同,可以通过设置func_call对于其参数进行检查,验证func_t在调用func_call之前执行是否正常。
通过check_expected(param)和expect_value(param, value)检查。
4. assert验证函数
cmocka根据不同的数据类型,提供了bool,int,memory addr等不同的验证比较函数。可以针对不同的验证类型,调用不同的函数进行验证。
测试函数定义和执行
cmocka中定义了UnitTest结构体,存放函数名称,函数入口和函数类型。run_tests函数直接执行UnitTest结构体数组,进行单元测试。
cmocka具体使用方法
1. 内存操作测试
对于如下的代码alloc.c:
#include #include #include #include "alloc.h"voidmemory(){ MEM_OBJ_T *obj = malloc(sizeof(MEM_OBJ_T)); obj->blockSize = 10; obj->szBuf = malloc(obj->blockSize); free(obj->szBuf); free(obj);}void leak_memory(void){ MEM_OBJ_T* obj = malloc(sizeof(MEM_OBJ_T)); obj->blockSize = 10; obj->szBuf = malloc(obj->blockSize); free(obj);}
可以在编译时,将其中的malloc、calloc、free重定义为cmocka中的内存操作函数:
#ifdef UNIT_TESTING/* Redirect calloc and free to test_calloc() and test_free() so cmocka can * check for memory leaks. */#ifdef malloc#undef malloc#endif /* calloc */#define malloc(size) _test_malloc(size, __FILE__, __LINE__)#ifdef calloc#undef calloc#endif /* calloc */#define calloc(num, size) _test_calloc(num, size, __FILE__, __LINE__)#ifdef free#undef free#endif /* free */#define free(ptr) _test_free(ptr, __FILE__, __LINE__)extern void* _test_calloc(const size_t number_of_elements, const size_t size, const char* file, const int line);extern void* _test_malloc(const size_t size, const char* file, const int line);extern void _test_free(void* const ptr, const char* file, const int line);
通过编译之后,使用nm检查其中的符号:
U _test_free U _test_malloc000000c0 T leak_memory00000060 T mem_overflow00000000 T mem_underflow000000f0 T memory
已将glibc中的内存操作函数替换。然后在单元测试代码中,调用上述函数即可进行测试:
static voidtest_leak_memory(void **state){ leak_memory();}
2. 静态函数测试方法
static函数作用范围限定在当前文件模块内,通过引入单元测试头文件,将static关键字重定向为空,即可在外部进行调用。并在测试用例函数中通过extern关键字,定义对应的static函数。
3. 模拟函数调用测试方法
在逻辑模块的实现过程中,通常函数A会调用函数B,并根据函数B的返回值进行不同逻辑处理。对于如下check.c模块:
boolCheckIdxPrice(int index, double check){ double price ; price = GetIdxPrice(index); if (check == price) { LogInfo("success"); return true; } else { LogInfo("failed"); return false; }}boolCheckIdxRes(int index, double check){ IDX_RES_T *res = NULL; res = GetIdxRes(index); if (check == res->price) { LogInfo("success"); return true; } else { LogInfo("failed"); return false; }}
函数CheckIdxPrice调用了GetIdxPrice,CheckIdxRes调用了GetIdxRes函数,而GetIdxPrice和GetIdxRes均在res.c模块中定义,头文件如下:
#ifndef __RES_H#define __RES_Htypedef struct TAG_IDX_RES{ int index; double price;} IDX_RES_T;doubleGetIdxPrice(int index);IDX_RES_T*GetIdxRes(int index);#endif //__RES_H
在单元测试模块test_check.c中,对GetIdxPrice和GetIdxRes进行模拟实现,返回不同的值:
//模拟GetIdxPricedoubleGetIdxPrice(int index){ int ret = 0; ret = mock_type(double); return ret;}//模拟GetIdxResIDX_RES_T*GetIdxRes(int index){ IDX_RES_T *ptRes = NULL; ptRes = mock_ptr_type(IDX_RES_T*); return ptRes;} 测试时再根据不同的返回值进行不同的处理:
//测试价格正常static void test_CheckIdxPriceOk(void **state){ expect_value(GetIdxPrice, index, 10); //设置GetRes返回的值 will_return(GetIdxPrice, 1000.0); assert_true(CheckIdxPrice(10, 1000.0));}
直接通过设置返回值,不用多次实现GetIdxRes函数。
4. 函数参数值测试方法
在逻辑实现的模块中,通常函数A会调用函数B,函数A内部处理之后,传递给B函数参数param,需要验证传递的参数param是否正确。对于如下模块:
//模拟GetIdxPricedoubleGetIdxPrice(int index){ check_expected(index); int ret = 0; ret = mock_type(double); return ret;}
在执行GetIdxPrice时,对于index参数进行检查。在相应的在测试函数中,通过expect_value(GetIdxPrice, index, 10)设置函数GetIdxPrice的参数index应该返回10,如下所示:
//测试价格正常static void test_CheckIdxPriceOk(void **state){ expect_value(GetIdxPrice, index, 10); //设置GetRes返回的值 will_return(GetIdxPrice, 1000.0); assert_true(CheckIdxPrice(10, 1000.0));}
如果CheckIdxPrice执行过程中,调用GetIdxPrice时传入的参数index不等于10,则测试失败,说明CheckIdxPrice函数在调用GetIdxPrice之前的执行逻辑有误。
5. 系统函数调用测试方法
在程序实现过程中,通常会调用open、read、write、connect等系统函数,而在调用这些系统函数时,会根据其中的返回值、错误编号errno进行不同的处理。需要对于这些系统调用进行模拟。可以参照5.1中的方式,在编译的时将系统调用重定向到自己的实现模块中。
为了尽可能减少对于原有代码的修改,通过链接期垫片(link seams)进行系统调用的模拟。在测试模块中实现read、write等系统调用,在动态连接的时候,编译器优先选择自己实现的read函数。
对于如下的socket实现:
intreadLenFromSrv(int fd){ char buf[1024]; int ret; int len; //从socket中读取int类型长度,设定为4byte ret = read(fd, buf, sizeof(buf)); if (ret != sizeof(len)) { return -1; } else { memcpy(&len, buf, sizeof(len)); } return len;}
readLenFromSrv从服务端读取int类型的长度。
将系统调用函数通过单独模块syscall.c进行实现,其中read系统调用可以通过如下方式进行实现:
//mock readint mock_read_errno = -1;bool mock_read = true;typedef int (*sys_read_f)(int fd, void *buf, size_t count);//read返回int长度intread(int fd, void *buf, size_t count){ sys_read_f sys_read = dlsym(RTLD_NEXT, "read"); if (mock_read) { int len = 10; memcpy(buf, &len, sizeof(int)); return sizeof(int); } else { return sys_read(fd, buf, count); }}
其中,sys_read_f为函数指针,通过dlsym函数根据”read”符号查找动态连接库中的函数地址,即:sys_read指向了真正的系统调用read。注:在编译时需要增加-ldl选项。
如果设置mock_read为true,则为模拟read调用,对于buf填充测试数据以及设置对应的返回值,如果实际需要进行系统调用,则将mock_read设置为false,真正调用系统read。设置read系统调用为模拟函数,则读取的长度为值为10,单元测试函数为:
static voidtest_readLenFromSrvOk(void **state){ int fd = 10; int len = 10; assert_int_equal(readLenFromSrv(fd), len);}
如果需要模拟read错误的情况,则read模拟函数可以实现为:
//read 返回错误intread(int fd, void *buf, size_t count){ sys_read_f sys_read = dlsym(RTLD_NEXT, "read"); if (mock_read) { errno = mock_read_errno; return errno == 0 ? 0 : -1; } else { return sys_read(fd, buf, count); }}
其中,read返回-1,mock_read_errno设置相应的错误信息。在网络代码实现中,可以通过mock_read_errno设置EAGAIN等模拟读取异常情况。
测试模块文件结构、命名规范
根据cmocka测试示例,以及在工作中的应用,考虑如下的规范:
- 程序模块与对应的单元测试模块分开,单元测试模块可以进行单独编译;
- 对于模块unit.c的测试过程,不包含在unit.c文件本身中。重新创建对应的测试文件unit_test.c文件;
- 对于unit.c中的函数func_t,如果其没有调用其他模块中的函数,在unit_test.c中,创建对应的测试函数test_func_t,调用func_t并传输参数,检查func_t返回值是否为期望值。
- 对于unit.c中的函数func_t,如果调用了other.c模块中的函数func_call,则在unit_test.c中,实现函数func_call,在测试函数test_func_t中设置func_call的不同返回值,进行相应的测试。
总结
单元测试是软件开发过程中非常重要的一环,只有“基础”牢固了,系统这座“大厦”才能稳定运行。
最后
以上就是合适老师为你收集整理的c++ 单元测试_嵌入式自动化单元测试(2)-Cmocka的全部内容,希望文章能够帮你解决c++ 单元测试_嵌入式自动化单元测试(2)-Cmocka所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复