单元测试中如何对可变参函数打桩

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

 

 

 

你可能感兴趣的:(sql,linux,list,单元测试,insert,单元测试工具)