异想天开

What's the true meaning of light, Could you tell me why

Ctest单元测试

日期:2015-07-12 19:43:07
  
最后更新日期:2015-07-15 10:20:54
单元测试很重要,一个模块应该确保有详细的单元的测试。网上查找了c语言的单元测试框架,找到一个ctest框架,该框架主要是简单,只有一个头文件,包含头文件即可。
浏览了项目代码,主要是如下机制:
1.实现原理 [code lang="cpp"]
typedef void (*SetupFunc)(void*);
typedef void (*TearDownFunc)(void*);

struct ctest {
const char* ssname; // suite name
const char* ttname; // test name
void (*run)();
int skip;

void* data;
SetupFunc setup;
TearDownFunc teardown;

unsigned int magic;
};
[/code]
利用上诉结构体表示一次测试实例,这里有一点需要注意的是run函数指针,参数列表里面没有加void,那么在c语言里面表示可以准许带参数的。宏CTEST展开就是定义一个struct ctest结构体,同时利用gcc编译器的扩展属性,将这些结构体全部放到一个section里面。
[code lang="cpp"]
#define __CTEST_MAGIC (0xdeadbeef)
#ifdef __APPLE__
#define __Test_Section __attribute__ ((unused,section ("__DATA, .ctest")))
#else
#define __Test_Section __attribute__ ((unused,section (".ctest")))
#endif
[/code]
这样就可以解释为ctest_main里面这部分代码:
[code lang="cpp"]
struct ctest* ctest_begin = &__TNAME(suite, test);
struct ctest* ctest_end = &__TNAME(suite, test);
// find begin and end of section by comparing magics
while (1) {
struct ctest* t = ctest_begin-1;
if (t->magic != __CTEST_MAGIC) break;
ctest_begin--;
}
while (1) {
struct ctest* t = ctest_end+1;
if (t->magic != __CTEST_MAGIC) break;
ctest_end++;
}
ctest_end++; // end after last one
[/code]
框架里面会定义一个:
[code lang="cpp"]
static CTEST(suite, test) { }
[/code]
故利用__TNAME(suite, test)可以找到这个默认的结构体名字,从而找到这些相邻的struct ctest结构体的上下边界。一个个挨个调用里面的run方法。每次调用前会setjmp,若断言失败,则断言里面会调用longjmp,根据setjmp返回值,主例程就获知断言失败了。若发生了信号,怎么办?比如段错误,ctest会记录下信号的值,然后调用系统默认的信号处理方式,一般结束程序。
[code lang="cpp"]
#ifdef CTEST_SEGFAULT
#include <signal.h>
static void sighandler(int signum)
{
char msg[128];
sprintf(msg, "[SIGNAL %d: %s]", signum, sys_siglist[signum]);
color_print(ANSI_BRED, msg);
fflush(stdout);

/* "Unregister" the signal handler and send the signal back to the process
* so it can terminate as expected */
signal(signum, SIG_DFL);
kill(getpid(), signum);
}
#endif
[/code]

2.应用例子 一般在测试代码文件里面会有一系列的CTEST,CTEST宏下面的中括号括起来的代码就是该run方法。
比如测试反转链表和头插法链表的功能代码:
main.c
第一步:main函数里面直接调用ctest_main
[code lang="cpp"]
#define CTEST_MAIN
// uncomment line below to get nicer logging on segfaults
#define CTEST_SEGFAULT
#include "ctest.h"

int main(int argc, const char *argv[])
{
return ctest_main(argc, argv);
}
[/code]

第二步:测试文件里面包含头文件ctest.h,加一系列的测试集合
mytests.c
[code lang="cpp"]
#include "node.h"
#include "ctest.h"

#ifndef NULL
#define NULL (void*)0
#endif

CTEST(tree,add_node1)
{
struct node *root=NULL;
add_node(&root,1);
ASSERT_FALSE((root == NULL));
ASSERT_TRUE((root->data == 1));
ASSERT_TRUE((root->next == NULL));
}

CTEST(tree,add_node2)
{
int a[] = {1,2,3,4,5};
int i;
const int len = (int)sizeof(a)/sizeof(a[0]);
struct node *root=NULL,*p=NULL;
//暂且不考虑内存释放
root = NULL;
for (i=0; i<len; ++i){
add_node(&root,a[i]);
}
//头插法
p = root;
for (i=len-1; i>=0; --i){
ASSERT_TRUE((p->data == a[i]));
p = p->next;
}
ASSERT_TRUE((p == NULL));
}

CTEST(tree,inverse1){
int a[] = {1,2,3,4,5};
const int len = (int)sizeof(a)/sizeof(a[0]);
int i;
struct node *root = NULL,*p;
for (i=0; i<len; ++i){
add_node(&root,a[i]);
}

root = reverse(root);
ASSERT_FALSE((root == NULL));
p = root;
for (i=0; i<len; ++i){
ASSERT_TRUE((p->data == a[i]));
p = p->next;
}
ASSERT_TRUE((p == NULL));

}

CTEST(tree,inverse2){
int a[] = {1};
const int len = (int)sizeof(a)/sizeof(a[0]);
int i;
struct node *root = NULL,*p;
for (i=0; i<len; ++i){
add_node(&root,a[i]);
}
ASSERT_FALSE((root == NULL));
root = reverse(root);
ASSERT_FALSE((root == NULL));
p = root;
for (i=0; i<len; ++i){
ASSERT_TRUE((p->data == a[i]));
p = p->next;
}
ASSERT_TRUE((p == NULL));
}
[/code]

node.h文件:
[code lang="cpp"]
#ifndef __NODE_H__
#define __NODE_H__
struct node{
char data;
struct node *next;
};

struct node * reverse(struct node *tree);
void add_node(struct node **root,char v);

#endif
[/code]