Embedded Unit是个纯标准c构建的单元测试框架,主要用在嵌入式c的单体测试上,其主要特点是不依赖于任何C的标准库,所有的对象都是静态分配。
最早这个项目托管在SourceForge上(https://sourceforge.net/projects/embunit ),目前在GitHub也有多个拷贝。
#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 (0) {} 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[16]; stdimpl_print(" ("); stdimpl_print(file); stdimpl_print(" "); stdimpl_itoa(line, buf, 10); stdimpl_print(buf); stdimpl_print(") "); } stdimpl_print(msg); stdimpl_print("\n"); }
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[0]),(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,被返回传递给测试执行函数。
测试的执行得从测试组开始说起,测试组保证了测试例程以及其运行相关的结构数据。 测试的执行从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(0,0,0,0); int i; cs.setUp= self->setUp; cs.tearDown = self->tearDown; for (i=0; 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=0; 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); }
下面演示了一个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 0; } 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(1, Counter_value(counterRef)); } static void testSetValue(void) { Counter_setValue(counterRef,1); TEST_ASSERT_EQUAL_INT(1, Counter_value(counterRef)); Counter_setValue(counterRef,-1); TEST_ASSERT_EQUAL_INT(-1, Counter_value(counterRef)); }
原文:https://www.cnblogs.com/pingwen/p/9222024.html