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

2009-09-03 作者:wzhwho 来源:CSDNBlog

 

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


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织