我觉得,整个项目最辛苦的阶段是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机器上运行的汇编指令。