C语言单元测试框架--EmbedUnit
1、简介
Embedded Unit是个纯标准c构建的单元测试框架,主要用在嵌入式c的单体测试上,其主要特点是不依赖于任何C的标准库,所有的对象都是静态分配。
最早这个项目托管在SourceForge上(https://sourceforge.net/projects/embunit ),目前在GitHub也有多个拷贝。
2、框架剖析
2.1 断言
#define TEST_ASSERT_NULL(pointer)\
TEST_ASSERT_MESSAGE(pointer == NULL,#pointer " was not null.") #define TEST_ASSERT_NOT_NULL(pointer)\
TEST_ASSERT_MESSAGE(pointer != NULL,#pointer " was null.") #define TEST_ASSERT_MESSAGE(condition, message)\
if (condition) {} else {TEST_FAIL(message);} #define TEST_ASSERT(condition)\
if (condition) {} else {TEST_FAIL(#condition);} #define TEST_FAIL(message)\
if () {} else {addFailure(message,__LINE__,__FILE__);return;
TEST_ASSERT_NULL依赖TEST_ASSERT_MESSAGE,TEST_ASSERT_MESSAGE依赖TEST_FAIL,TEST_FAIL依赖addFailure。
所以一般的错误断言,会使用addFailure来完成错误处理,其原型如下。
void addFailure(const char *msg, long line, const char *file)
{
TestResult_addFailure(result_, (Test*)self_, (char*)msg, line, (char*)file);
} void TestResult_addFailure(TestResult* self,Test* test,const char* msg,int line,const char* file)
{
self->failureCount++;
if (self->listener) {
TestListner_addFailure(self->listener, test, msg, line, file);
}
}
在TestResult_addFailure中对错误case的总数进行计数,而错误消息由TestListner_addFailure负责。
static void TestRunner_addFailure(TestListner* self,Test* test,char* msg,int line,char* file)
{
stdimpl_print("\n");
stdimpl_print(Test_name(root_));
stdimpl_print(".");
stdimpl_print(Test_name(test));
{
char buf[];
stdimpl_print(" (");
stdimpl_print(file);
stdimpl_print(" ");
stdimpl_itoa(line, buf, );
stdimpl_print(buf);
stdimpl_print(") ");
}
stdimpl_print(msg);
stdimpl_print("\n");
}
2.2 测试case管理
EmbedUnit在测试的管理方面,主要使用了2个编程技术,一是结构体数组、二是函数指针。EmbedUnit可以说是C语言模块化开发的教材,在宏定义、函数指针、结构体对象方面的应用十分精妙。
TestRef CounterTest_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture("testInit",testInit),
new_TestFixture("testSetValue",testSetValue),
new_TestFixture("testInc",testInc),
new_TestFixture("testDec",testDec),
new_TestFixture("testClr",testClr),
};
EMB_UNIT_TESTCALLER(CounterTest,"CounterTest",setUp,tearDown,fixtures); return (TestRef)&CounterTest;
}
EMB_UNIT_TESTFIXTURES(fixtures)很奇怪的C语言写法,但是如果展开后就很明了恍然大悟。
#define EMB_UNIT_TESTFIXTURES(fixtures) \
static const TestFixture fixtures[] = #define new_TestFixture(name,test)\
{\
name,\
test,\
}
fixtures就是一个数组而已,static const TestFixture fixtures[]。new_TestFixture就是一个大括号。
然后是关键的一句EMB_UNIT_TESTCALLER,这个函数把上面的数组fixtures[]加入到测试case组,组名叫做CounterTest。 而测试case的个数由sizeof(fixtures)/sizeof(fixtures[0])来直接计算出来。
#define EMB_UNIT_TESTCALLER(caller,name,sup,tdw,fixtures) \
static const TestCaller caller = new_TestCaller(name,sup,tdw,sizeof(fixtures)/sizeof(fixtures[]),(TestFixture*)fixtures)
继续深入,new_TestCaller是一个宏定义,展开后扩展为一个TestCaller类型的结构体。
#define new_TestCaller(name,sup,tdw,numberOfFixtuers,fixtuers)\
{\
(TestImplement*)&TestCallerImplement,\
name,\
sup,\
tdw,\
numberOfFixtuers,\
fixtuers,\
}
其结构体定义为:
typedef struct __TestCaller TestCaller;
typedef struct __TestCaller* TestCallerRef;/*downward compatible*/ struct __TestCaller {
TestImplement* isa;
char *name;
void(*setUp)(void);
void(*tearDown)(void);
int numberOfFixtuers;
TestFixture *fixtuers;
};
上面的写法非常精妙,值得在项目中学习,第一用宏定义展开结构体很好的包装了细节。第二结构体类型的使用,不直接用结构体定义名称__TestCaller,而进行转换用typedef重新定义为TestCaller,在很大的程度上起到接口隔离的效果。
到目前为止,已经构成了一个完整的测试组,包括setUp,tearDown,fixtuers,测试环境准备、现场清理、待测函数三个因素已经具备。CounterTest类型为TestCaller,被返回传递给测试执行函数。
2.3测试的执行
测试的执行得从测试组开始说起,测试组保证了测试例程以及其运行相关的结构数据。 测试的执行从TestRunner_runTest(CounterTest_tests())开始。
void TestRunner_runTest(Test* test)
{
root_ = test;
Test_run(test, &result_);
}
对Test_run进行追踪。
#define Test_run(s,r) ((Test*)s)->isa->run(s,r)
struct __Test {
TestImplement* isa;
};
测试组的执行时从Test_run开始的,参数是Test* test和TestResult result_,与其说TestImplement* isa被转成(Test*)类型,不如说取出了TestCaller结构体的第一个元素,然后调用了run函数指针。
typedef struct __TestImplement TestImplement;
typedef struct __TestImplement* TestImplementRef;/*downward compatible*/ typedef char*(*TestNameFunction)(void*);
typedef void(*TestRunFunction)(void*,TestResult*);
typedef int(*TestCountTestCasesFunction)(void*); struct __TestImplement {
TestNameFunction name;
TestRunFunction run;
TestCountTestCasesFunction countTestCases;
};
这是一路漫长的C面向对象写法,虽然看起来结构整齐,但是逻辑上绕了很多弯。分析如下。
1)isa->run的来源
TestCaller中的isa来源于定义测试组时候的结构体展开。 TestCallerImplement是一个全局的变量。 在TestCaller 内部,TestCallerImplement是一个全局的变量是其第一个元素,类型为(TestImplement*),也叫做Test类型。
extern const TestImplement TestCallerImplement; #define new_TestCaller(name,sup,tdw,numberOfFixtuers,fixtuers)\
{\
(TestImplement*)&TestCallerImplement,\
name,\
sup,\
tdw,\
numberOfFixtuers,\
fixtuers,\
} struct __Test {
TestImplement* isa;
};
2)函数的调用
struct __TestImplement {
TestNameFunction name;
TestRunFunction run;
TestCountTestCasesFunction countTestCases;
};
const TestImplement TestCallerImplement = {
(TestNameFunction) TestCaller_name,
(TestRunFunction) TestCaller_run,
(TestCountTestCasesFunction)TestCaller_countTestCases,
};
所以isa->run就是调用TestCaller_run函数。
typedef void(*TestRunFunction)(void*,TestResult*); void TestCaller_run(TestCaller* self,TestResult* result)
{
TestCase cs = new_TestCase(,,,);
int i;
cs.setUp= self->setUp;
cs.tearDown = self->tearDown;
for (i=; i<self->numberOfFixtuers; i++) {
cs.name = self->fixtuers[i].name;
cs.runTest = self->fixtuers[i].test;
/*run test*/
Test_run(&cs,result);
}
}
更具isa->run(s,r),可以知道,s就是TestCaller 类型的CounterTest变量,只不过在函数调用时候被截取了第一个元素,转换成了(TestImplement *)类型。
r就是static TestResult result_,用来记录测试结果。
struct __TestResult {
unsigned short runCount;
unsigned short failureCount;
TestListner* listener;
};
到目前为止,所有的测试都从Test_run(test, &result_)跳转到测执行函数。
3)函数的执行
在TestCaller_run中,Test_run负责执行具体的函数体。
for (i=; i<self->numberOfFixtuers; i++) {
cs.name = self->fixtuers[i].name;
cs.runTest = self->fixtuers[i].test;
/*run test*/
Test_run(&cs,result);
}
cs.runTest = self->fixtuers[i].test负责找到具体的case,Test_run负责执行测试,将其展开。
#define Test_run(s,r) ((Test*)s)->isa->run(s,r)
此处的s是指测试case cs,源于TestCase cs = new_TestCase(0,0,0,0)。
typedef struct __TestCase TestCase;
typedef struct __TestCase* TestCaseRef;/*compatible embUnit1.0*/ struct __TestCase {
TestImplement* isa;
char *name;
void(*setUp)(void);
void(*tearDown)(void);
void(*runTest)(void);
};
而此处的((Test*)s)->isa->run(s,r),其中run函数指向谁呢?玄机在TestCase cs = new_TestCase(0,0,0,0); new_TestCase 的第一个元素就是TestCaseImplement。
struct __TestCase {
TestImplement* isa;
char *name;
void(*setUp)(void);
void(*tearDown)(void);
void(*runTest)(void);
};
extern const TestImplement TestCaseImplement;
#define new_TestCase(name,setUp,tearDown,runTest)\
{\
(TestImplement*)&TestCaseImplement,\
name,\
setUp,\
tearDown,\
runTest,\
}
这个原型为:
struct __TestImplement {
TestNameFunction name;
TestRunFunction run;
TestCountTestCasesFunction countTestCases;
};
const TestImplement TestCaseImplement = {
(TestNameFunction) TestCase_name,
(TestRunFunction) TestCase_run,
(TestCountTestCasesFunction)TestCase_countTestCases,
};
测试函数执行,就是TestRunFunction run所指的TestCase_run函数。前面已经由cs.runTest = self->fixtuers[i].test这一句找到函数的应用,然后self->runTest()就是执行该测试函数。
由于不依靠任何c标准库,所以没有longjmp这样的长跳转,那么测试出错如何进行返回呢?诀窍就在addFailure函数的时机、以及下面几个PUSH和POP上,共同完成局部变量和全局的result之间的信息传递。
void TestCase_run(TestCase* self,TestResult* result)
{
TestResult_startTest(result, (Test*)self);
if (self->setUp) {
self->setUp();
}
if (self->runTest) {
TestResult* wr =result_; /*push*/
TestCase* ws = self_; /*push*/
result_ = result;
self_ = self;
self->runTest();
result_ = wr; /*pop*/
self_ = ws; /*pop*/
}
if (self->tearDown) {
self->tearDown();
}
TestResult_endTest(result, (Test*)self);
}
3、测试实例
下面演示了一个EmbedUnit的测试工程,包含三个方面:
1. 写测试例子
比如static void testInit(void)。
2. 构成测试组
比如TestRef CounterTest_tests(void)。返回(TestRef)&CounterTest变量。
3. 调用框架执行全部测试
main函数里面流程的就是测试框架的执行流程。
TestRef CounterTest_tests(void);
TestRef PersonTest_tests(void); int main (int argc, const char* argv[])
{
TestRunner_start();
TestRunner_runTest(CounterTest_tests());
TestRunner_runTest(PersonTest_tests());
TestRunner_end();
getchar();
return ;
} TestRef CounterTest_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture("testInit",testInit),
new_TestFixture("testSetValue",testSetValue),
};
EMB_UNIT_TESTCALLER(CounterTest,"CounterTest",setUp,tearDown,fixtures); return (TestRef)&CounterTest;
} static void testInit(void)
{
TEST_ASSERT_EQUAL_INT(, Counter_value(counterRef));
} static void testSetValue(void)
{
Counter_setValue(counterRef,);
TEST_ASSERT_EQUAL_INT(, Counter_value(counterRef)); Counter_setValue(counterRef,-);
TEST_ASSERT_EQUAL_INT(-, Counter_value(counterRef));
}
C语言单元测试框架--EmbedUnit的更多相关文章
- c语言单元测试框架--CuTest
1.简介 CuTest是一款微小的C语言单元测试框,是我迄今为止见到的最简洁的测试框架之一,只有2个文件,CuTest.c和CuTest.h,全部代码加起来不到一千行.麻雀虽小,五脏俱全,测试的构建. ...
- [置顶] C语言单元测试框架
unitest.h /****************************************************************************** * * * This ...
- 编C语言单元测试框架CUnit方法库
/********************************************************************* * Author : Samson * Date ...
- 编译C语言单元测试框架CUnit库的方法
引用: http://blog.csdn.net/yygydjkthh/article/details/46357421 个人备忘使用 /******************************* ...
- Java - Junit单元测试框架
简介 Junit : http://junit.org/ JUnit是一个开放源代码的Java语言单元测试框架,用于编写和运行可重复的测试. 多数Java的开发环境都已经集成了JUnit作为单元测试的 ...
- Java单元测试框架 JUnit
Java单元测试框架 JUnit JUnit是一个Java语言的单元测试框架.它由Kent Beck和Erich Gamma建立,逐渐成为源于KentBeck的sUnit的xUnit家族中为最成功的一 ...
- C语言单元测试
转自http://blog.csdn.net/colin719/article/details/1420583 对于敏捷开发来说,单元测试必不可少,对于Java开发来说,JUnit非常好,对于C++开 ...
- javascript单元测试框架mochajs详解
关于单元测试的想法 对于一些比较重要的项目,每次更新代码之后总是要自己测好久,担心一旦上线出了问题影响的服务太多,此时就希望能有一个比较规范的测试流程.在github上看到牛逼的javascript开 ...
- JavaScript单元测试框架JsUnit基本介绍和使用
JavaScript单元测试框架JsUnit基本介绍和使用 XUnit framework XUnit是一套标准化的独立于语言的概念和结构集合,用于编写和运行单元测试(Unit tests). 每一个 ...
随机推荐
- 2017 ACM区域赛(西安) 参赛流水账
day 0: 周五, 鸽了概统课,早上和紫金港的几位小伙伴一起打车去萧山机场,从咸阳机场到西北工业大学坐了五十多个站的公交车,感觉身体被掏空.晚上在宾馆本来打算补之前训练的一个题,想想还是先花个十来分 ...
- poj 1322 Chocolate (概率dp)
///有c种不同颜色的巧克力.一个个的取.当发现有同样的颜色的就吃掉.去了n个后.到最后还剩m个的概率 ///dp[i][j]表示取了i个还剩j个的概率 ///当m+n为奇时,概率为0 # inclu ...
- linux配置网关
linux配置网关 输入账号root 再输入安装过程中设置的密码,登录到系统 vi /etc/sysconfig/network-scripts/ifcfg-eth0 #编辑配置文件,添加修改以下内容 ...
- mysql死锁-非主键索引更新引起的死锁
背景:最近线上经常抛出mysql的一个Deadlock,细细查来,长了知识! 分析:错误日志如下: 21:02:02.563 ERROR dao.CommonDao [pool-15-t ...
- Angular入门(三) 引入boostrap4
1.cnpm install ngx-bootstrap bootstrap --save ※可能缺少jquery cnpm i jquery 2. 打开 angular-cli.json (项目 ...
- Java 语言基础(一)
大多数编程语言都包括以下基本内容: 关键字 标识符 注释 常量和变量 运算符 语句 函数 数组 学习语言最重要的两点: 该语言基础的表现形式是什么 这些东西什么时候使用 关键字 在程序语言中有特殊含义 ...
- 如何实现redis集群?
由于Redis出众的性能,其在众多的移动互联网企业中得到广泛的应用.Redis在3.0版本前只支持单实例模式,虽然现在的服务器内存可以到100GB.200GB的规模,但是单实例模式限制了Redis没法 ...
- KMP算法最浅显理解——一看就明确
说明 KMP算法看懂了认为特别简单,思路非常easy,看不懂之前.查各种资料,看的稀里糊涂.即使网上最简单的解释,依旧看的稀里糊涂. 我花了半天时间,争取用最短的篇幅大致搞明确这玩意究竟是啥. 这里不 ...
- 信息安全意识教育日历——By 安全牛
安全牛:企业即使投入再好的信息安全技术和产品,也难以解决内部威胁以及社会工程等攻击手段,无法做到全面有效地保护企业信息资产.而通过开展员工的信息安全意识培训教育工作,不仅能降低企业风险.满足合规要求, ...
- thinkphp使用阿里云OSS最新SDK,文件部署
这文章是建立在你已经注册号阿里云的OSS,和创建好Bucket前提下: 其实阿里云的帮助与文档写的很详细,这里只说一下源码方式 1.phpsdk下载地址(摘自阿里云OSS的帮助与文档)(也有我自己下载 ...