我觉得,整个项目最辛苦的阶段是UT,为什么?因为UT的工作又细致又繁琐。工作量往往都比预期的要大。我用过三个UT工具。最方便的是北研的镜像测试工具(Mirror
Test Tools,MTT)是应用镜象测试技术理论,将脚本(python)化的变量控制与桩驱动应用于单元测试与集成测试,以达到高效率高质量的支撑白盒测试的目的,为公司使用C语言的产品提供通用的调测服务。接着是CUNIT,是应用在Linux平台下的一个轻型的C语言单元测试框架,比较适合深研,可以在真实环境下完成UT工作。最后是C++Test,是一款在windows环境下运行的单元测试工具,而我们在linux系统下开发,就需要它模拟
Linux环境。它号称可以生成80%的UT代码,其实都是需要我们自己编写,最大的缺点就是性能超级慢。最后,说说我做UT的体会,做UT就是需要学会如何打桩,其它都是细致活儿。
1. 如何对一个函数进行多次打桩
有时候我们会遇到这样的情况,被测函数是func_aaa,而这个函数又多次调用了fopen,并且func_aaa每次调用fopen时要求返回不同的值。我们可以这样编写桩函数,如下:
int g_stub_list_flag;//全局置位标志变量
int g_stub_clear_flag;//清除桩函数静态变量的全局变量
FILE * stub_fopen(char *filename ,const char *mode)
{ FILE *fp;
static int call_count= 0;
if(0 == g_stub_clear_flag)
{
call_count
= 0;
g_stub_clear_flag
g = 1;
}
call_count++;
if(g_stub_list_flag &
(1<< call_count-1))
{
return
NULL;
}
else
{
fp
= fopen(filename, mode);
return
fp;
}
} |
如果,我们需要对fopen函数进行打桩,并且第一次要求成功,第二、三次要求失败,驱动代码如下:
void UT_func_aaa_Case_01()
{ g_stub_clear_flag = 0;
g_stub_list_flag = 1|1<<2;
fp = fopen(filename,mode);
} |
2. 如何对变参函数进行打桩
例如 uint32_t sql_exec_insert(char * format,char* a,...)
在该函数中有可能又要调用真实函数。
2.1 用实际的参数直接填写到真实的函数
int32_t stub_sql_exec_insert (char* funcname,
char * format,char* a,...)
{ static int call_count=
0;
if(0 == g_stub_clear_flag)
{
call_count
= 0;
g_stub_clear_flag
g = 1;
}
call_count++;
if(g_stub_list_flag &
(1<< call_count-1))
{
return
1;//失败
}
else
{
if(0==strcmp(funcname,
"func_aaa"))
return sql_exec_insert (format, a,XX,YY,ZZ);
else
return sql_exec_insert (format, a,XX,YY);
}
} |
这时XX,YY,ZZ可以直接使用外面的用例驱动中定义的真实情况数值。
2.2 可变变参函数使用
int32_t stub_sql_exec_insert (char * format,char*
a,...)
{ static int call_count=
0; char arg_buf[1024];
va_list arg_ptr;
int32_t ret; if(0 == g_stub_clear_flag)
{
call_count = 0;
g_stub_clear_flag g = 1;
}
call_count++;
if(g_stub_list_flag &
(1<< call_count-1))
{
return
1;//失败
}
else
{
va_list
arg_ptr;
va_start(arg_ptr,
format);
ret
= sql_exec_insert (format, arg_ptr);
va_end(arg_ptr);
return
ret;
}
} |
2.3 汇编实现
原理:指定调用方式为调用者压参数,退参数,然后拷贝参数,调完后再退栈。
int32_t stub_sql_exec_insert (char * format,char*
a,...)
{ static int call_count=
0; int32_t __CPTR_result;
typedef uint32_t(*__CPTR_FuncPtr)(void);
if(0 == g_stub_clear_flag)
{
call_count = 0;
g_stub_clear_flag g = 1; }
call_count++;
if(g_stub_list_flag &
(1<< call_count-1))
{
return 1;//失败
}
else
{
__CPTR_FuncPtr
__CPTR_funcPtr = (__CPTR_FuncPtr)& sql_exec_insert;
__asm__(
"\t""pushl %esi""\n"
"\t""pushl %edi""\n"
"\t""pushl %ecx""\n"
"\t""subl %128,%esp "\n"
"\t""lea 8(%ebp),%esi "\n"
"\t""lea (%esp),%edi "\n"
"\t""movl %32,%ecx "\n"
"\t""cld"\n"
"\t""rep"\n"
"\t""movsl"\n"
};
__CPTR_result
= __CPTR_funcPtr();
__asm__(
"\t""addl %128,%esp "\n"
"\t""pop %ecx""\n"
"\t""pop %edi""\n"
"\t""pop %esi""\n"
};
return
__CPTR_result;
}
} |
不过,这是在x86机器上运行的汇编指令。 |