1、简介

CuTest是一款微小的C语言单元测试框,是我迄今为止见到的最简洁的测试框架之一,只有2个文件,CuTest.c和CuTest.h,全部代码加起来不到一千行。麻雀虽小,五脏俱全,测试的构建、测试的管理、测试语句,都全部包含在内。

2、CuTest剖析

2.1 断言

一个测试case是否通过落到代码实处,就是对测试值与期待值之间进行比较,这就要用到断言。

#define CuAssertStrEquals(tc,ex,ac)           CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertStrEquals_Msg(tc,ms,ex,ac) CuAssertStrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertIntEquals(tc,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertIntEquals_Msg(tc,ms,ex,ac) CuAssertIntEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
#define CuAssertDblEquals(tc,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac),(dl))
#define CuAssertDblEquals_Msg(tc,ms,ex,ac,dl) CuAssertDblEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac),(dl))
#define CuAssertPtrEquals(tc,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,NULL,(ex),(ac))
#define CuAssertPtrEquals_Msg(tc,ms,ex,ac) CuAssertPtrEquals_LineMsg((tc),__FILE__,__LINE__,(ms),(ex),(ac))
......
......

以数字测试为例CuAssertIntEquals,其实现为:

void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line, const char* message,
int expected, int actual)
{
char buf[STRING_MAX];
if (expected == actual) return;
sprintf(buf, "expected <%d> but was <%d>", expected, actual);
CuFail_Line(tc, file, line, message, buf);
}

如果测试成功,则会安静的进行下一步,由return返回此函数。
大部分的测试框架的哲学和linux哲学很像,小即是美,少就是好,没有异常下不会打扰用户。
而万一出现错误,则会保存错误信息,还有文件路径/文件名/函数名、及行号。

sprintf(buf, "expected <%d> but was <%d>", expected, actual);
CuFail_Line(tc, file, line, message, buf);

继续深入,上面函数实现了:拼接错误消息到string,然后传递给CuFailInternal函数。很容易从CuFailInternal函数名发现,这个函数才是真正的错误返回的核心。
1)把函数名和行号,追加到用户错误消息的字符串后面。由CuStringInsert语句实现。
2)错误标志,tc->failed置位。
3)完整的错误消息引用赋值给测试的消息指针。
4)返回,长跳转。

void CuFail_Line(CuTest* tc, const char* file, int line, const char* message2, const char* message)
{
CuString string; CuStringInit(&string);
if (message2 != NULL)
{
CuStringAppend(&string, message2);
CuStringAppend(&string, ": ");
}
CuStringAppend(&string, message);
CuFailInternal(tc, file, line, &string);
} static void CuFailInternal(CuTest* tc, const char* file, int line, CuString* string)
{
char buf[HUGE_STRING_LEN]; sprintf(buf, "%s:%d: ", file, line);
CuStringInsert(string, buf, ); tc->failed = ;
tc->message = string->buffer;
if (tc->jumpBuf != ) longjmp(*(tc->jumpBuf), );
}

到这里,一个错误的测试就会从longjmp返回。

2.2 测试的组织

无论设计多么精妙的测试,都需要一个一个的逻辑测试函数,这就是测试case。比如下面的测试case。
待测函数原型:

int AddInt(int a, int b);

测试用例:

void test_add(CuTest* tc)
{
CuAssert(tc, "\r\ntest not pass", == AddInt(,);
} CuSuite* TestAdd(void)
{
CuSuite* suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_add); return suite;
}

如果有许多测试,则要用到测试组的管理。也就是测试case的管理,CuTest中叫做suite。

CuSuite* CuGetSuite(void)
{
CuSuite* suite = CuSuiteNew(); SUITE_ADD_TEST(suite, TestCuStringAppendFormat);
SUITE_ADD_TEST(suite, TestCuStrCopy);
SUITE_ADD_TEST(suite, TestFail);
SUITE_ADD_TEST(suite, TestAssertStrEquals);
SUITE_ADD_TEST(suite, TestAssertStrEquals_NULL); return suite;
}

一般而言suite是一类测试的集合,其实就是调用了CuSuiteAdd函数。

#define SUITE_ADD_TEST(SUITE,TEST)    CuSuiteAdd(SUITE, CuTestNew(#TEST, TEST))

用宏展开,#TEST等价于TEST内容转换为字符串,CuTestNew(#TEST, TEST)是宏的一种妙用。此函数作用是把case加入到testSuite的具体链表中去。

void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase)
{
assert(testSuite->count < MAX_TEST_CASES);
testSuite->list[testSuite->count] = testCase;
testSuite->count++;
}

上面是一类测试,用suite函数SUITE_ADD_TEST来实现多个测试函数的归类管理。那么有多个的函数的测试时候,是如何规划呢,需要suite上再添加suite了。最后对上层接口提供一个总的suite的引用即可。

    CuSuite* suite = CuSuiteNew();

    CuSuiteAddSuite(suite, CuGetSuite());
CuSuiteAddSuite(suite, CuStringGetSuite());
CuSuiteAddSuite(suite, TestAdd());

2.3 测试的运行

测试case构成了测试组--suite,然后多个测试组可以合并为一个测试组。测试组的执行就是遍历数组,执行内部的每一个测试case。

void CuSuiteRun(CuSuite* testSuite)
{
int i;
for (i = ; i < testSuite->count ; ++i)
{
CuTest* testCase = testSuite->list[i];
CuTestRun(testCase);
if (testCase->failed) { testSuite->failCount += ; }
}
}

测试的执行靠CuTestRun来完成,依旧是打下跳转断点--setjmp(buf),然后运行测试case,如果测试case无错误,则安静的退出,否则记录出错信息,然后longjmp返回到if (setjmp(buf) == 0)一行,在CuSuiteRun中,会对错误case的个数进行计数,以便全部case运行完毕后,输出总结信息用。

void CuTestRun(CuTest* tc)
{
jmp_buf buf;
tc->jumpBuf = &buf;
if (setjmp(buf) == )
{
tc->ran = ;
(tc->function)(tc);
}
tc->jumpBuf = ;
}

上面的函数,测试函数的调用很隐晦,是(tc->function)(tc)语句完成的。测试case的原型为:

typedef void (*TestFunction)(CuTest *);

struct CuTest
{
char* name;
TestFunction function;
int failed;
int ran;
const char* message;
jmp_buf *jumpBuf;
};

所以function就指向具体的测试case。
具体的实现为:第一步创建测试case,即CuTest* tc。CuTestNew传入的参数function就是具体测试case函数的引用指针。

CuTest* CuTestNew(const char* name, TestFunction function)
{
CuTest* tc = CU_ALLOC(CuTest);
CuTestInit(tc, name, function);
return tc;
}

第二步,测试case初始化,将funciton引用指针赋值给CuTest* t->function。所以(tc->function)(tc)语句就相当于直接调用测试case函数本体。

void CuTestInit(CuTest* t, const char* name, TestFunction function)
{
t->name = CuStrCopy(name);
t->failed = ;
t->ran = ;
t->message = NULL;
t->function = function;
t->jumpBuf = NULL;
}

3、CuTest实例

下面是一个简单的实例,包含了测试case,测试组,测试执行。
1)测试case

void test_add(CuTest* tc)
{
CuAssert(tc, "\r\ntest not pass", == + );
}

2)测试组suite

CuSuite* TestAdd(void)
{
CuSuite* suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_add); return suite;
}

3)测试项目结构组织

void main()
{
RunAllTests();
getchar();
} void RunAllTests(void)
{
CuString *output = CuStringNew();
CuSuite* suite = CuSuiteNew(); CuSuiteAddSuite(suite, TestAdd()); CuSuiteRun(suite);
CuSuiteSummary(suite, output);
CuSuiteDetails(suite, output);
printf("%s\n", output->buffer);
}

c语言单元测试框架--CuTest的更多相关文章

  1. C语言单元测试框架--EmbedUnit

    1.简介 Embedded Unit是个纯标准c构建的单元测试框架,主要用在嵌入式c的单体测试上,其主要特点是不依赖于任何C的标准库,所有的对象都是静态分配. 最早这个项目托管在SourceForge ...

  2. [置顶] C语言单元测试框架

    unitest.h /****************************************************************************** * * * This ...

  3. 编C语言单元测试框架CUnit方法库

    /*********************************************************************  * Author  : Samson  * Date   ...

  4. 编译C语言单元测试框架CUnit库的方法

    引用: http://blog.csdn.net/yygydjkthh/article/details/46357421 个人备忘使用 /******************************* ...

  5. C语言单元测试

    转自http://blog.csdn.net/colin719/article/details/1420583 对于敏捷开发来说,单元测试必不可少,对于Java开发来说,JUnit非常好,对于C++开 ...

  6. Java - Junit单元测试框架

    简介 Junit : http://junit.org/ JUnit是一个开放源代码的Java语言单元测试框架,用于编写和运行可重复的测试. 多数Java的开发环境都已经集成了JUnit作为单元测试的 ...

  7. Java单元测试框架 JUnit

    Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...

  8. javascript单元测试框架mochajs详解

    关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...

  9. JavaScript单元测试框架JsUnit基本介绍和使用

    JavaScript单元测试框架JsUnit基本介绍和使用 XUnit framework XUnit是一套标准化的独立于语言的概念和结构集合,用于编写和运行单元测试(Unit tests). 每一个 ...

随机推荐

  1. 【翻译自mos文章】使用asm来部署 超大数据库(10TB到PB 范围)--针对oracle 10G

    使用asm来部署 超大数据库(10TB到PB 范围) 參考原文: Deployment of very large databases (10TB to PB range) with Automati ...

  2. hdu 2167(状压dp)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2167 思路:经典的状压dp题,前后,上下,对角8个位置不能取,状态压缩枚举即可所有情况,递推关系是为d ...

  3. AWS系列-磁盘扩容

    1 磁盘扩容 1.1 卷介绍 aws磁盘扩容有两个方式 1.购买新的磁盘,挂载到相应的目录 2.原来磁盘做快照,购买新的磁盘,选择恢复快照到硬盘上,这样相当于,从一块硬盘上50G升级到100G 说到a ...

  4. 使用phpnow本地搭建Discuz!如何实现伪静态

    用phpnow本地搭建Discuz!如何实现伪静态 因为phpnow本身就支持伪静态,那只要看下相关的设置是否正确,写个.htaccess的文件就可以了. 一.确认httpd.conf的设置 在xxx ...

  5. linux串口编程参数配置详解

    1.linux串口编程需要的头文件 #include <stdio.h>         //标准输入输出定义 #include <stdlib.h>        //标准函 ...

  6. CF679C(Bear and Square Grid) 经典好题

    题目链接:传送门 题目大意:给你一个n*n包含".","X"的图,你有一次机会选择一个k*k的子矩阵,将子矩阵全部变为".",问当操作过后, ...

  7. AngularJS 讲解,一 数据绑定

    AngularJS 完全使用javascript 编写的客户端技术.通过原生的Model-View-Controller(MVC,模型视图控制器)功能增强了HTML.这个选择可以快捷和愉悦地构建出 ...

  8. Entity Framework查询生成大量的子查询,如何避免?求救

    最近使用Entity Framework做一个中型的项目,一张表含有千万条数据,并没有使用很复杂的查询,只是程序上使用了DTO进行帅选数据,且使用了分页,效果很不理想.经过跟踪sql,我发现很多简单的 ...

  9. vbs 修改Administrator帐号密码

    Dim WshShell, oExec Set wshShell = CreateObject("WScript.Shell") Set objFSO = CreateObject ...

  10. Java 常用工具类之 String 类

    String 类的特点: 字符串对象一旦被初始化就不会被改变. //以下代码的区别: String s = "abc"; // 在常量池中创建一个字符串对象, 池中没有就建立, 池 ...