本人原创,转载请注明出处。
目录
以前一直从事底层驱动和应用开发,这几年中一直从事应用开发,采用C++去实现,最近由于工作原因,需要用C语言进行产品开发,想找一个c语言的单元测试框架,网上找了一个embUnit,这个库不依赖于标准库,方便进行移植,可以在嵌入式和PC上运行。在看代码的过程中,发现这个库的C语言编写,采用了类似C++面向对象的编程,面向对象C++上还算比较熟悉,但C实现还需要补补。由此便有了两个目标。
1)学习该开源库的c语言面向对象编程以及其编程习惯
2)学习embUnit,做为后面工作c语言单元测试框架
2.c语言面向对象编程
下面只是自己对简单的c语言面向对象编程做一个简单的总结记录。有不对之处,请提出讨论。
2.1c语言继承实现
我们知道c语言只能用结构体来实现C++语言类的概念。那怎么样才能继承呢。总结一句话。当一个类的结构体开始格式数据与另一个类结构体格式相同时。那这两个类可以作为父子类。
2.1.1子类结构体包含父类结构体
例如:
typedef void(*aFunc)();
typedef struct __Father{
aFunc sFuc;
char* sName;
}Father,*pFather;
typedef struct __ChildS{
Father sFather;
char *sFav;
}ChildS,*pChildS;
ChildS结构体开始格式是Father sFather结构体,那ChildS就可以作为Father子类。
测试一下
static void _helloADo()
{
printf("helloADo....\r\n");
}
static const ChildS ChildSA={
{
_helloADo,
"FatherA"
},
"ChildA"
};
pFather pf=(pFather)&ChildSA;
pf->sFuc();
printf("name=%s\r\n",pf->sName);
运行结果如下:
helloADo....
name=FatherA
说明ChildS,可以作为Father子类
2.1.2子类结构体包含父类结构中的指针
参考embUnit采用了另一种方法实现。我们看其中用的最多的Test.
TestImplement是测试抽象实现函数,有点类似于接口概念,纯虚函数。Test是一个类包含TestImplement指针,这时Test作为父类。子类有两个一个是TestCaller和TestCase.下面抽出部分结构体定义。
struct __Test {
TestImplement* isa;
};
struct __TestCaller {
TestImplement* isa;
char *name;
void(*setUp)(void);
void(*tearDown)(void);
int numberOfFixtuers;
TestFixture *fixtuers;
};
struct __TestCase {
TestImplement* isa;
char *name;
void(*setUp)(void);
void(*tearDown)(void);
void(*runTest)(void);
};
还是记住可以继承的原则。当一个类的结构体开始格式数据与另一个类结构体格式相同时。那这两个类可以作为父子类。这里需要好好理解一下。
所以你看void TestRunner_runTest(Test* test),运行测试都是一个Test对象。这时我们就知道 它可以是TestCaller和TestCase。embUnit顶层测试开始是TestCaller.这些是我们自己由一个个测试用例组成的。
2.1.3结构体和指针有何不同
因为c语言这块面向对象编程才刚开始,c好久不用不太熟悉了。只是谈谈我的个人感觉,这个和c语言特性有关。
指针的好处是方便初始化,可以先把IMP的对象初始化好。再填充。而结构体中结构体初始化填充不是很方便。有兴趣的自己可以测试一下。
2.2其他参考
2.2.1宏定义
embUnit大量用宏定义来生成一个对象。如
#define new_TestFixture(name,test)\
{\
name,\
test,\
}
生成一个TestFixture
其他可以自己研究
2.2.2标准库实现StdImpl
可以参考stdImpl,实现了标准库字符串的操作,其中整形转字符串函数,算法比较简洁可以参考下。
3embUnit学习
简单用UML 做了几个类的分析。但毕竟是C语言的,只能大概示意下。
3.1总体说明
三步调用,Start,RunTest,end.
TestRunner_start();
TestRunner_runTest(assertTest_tests());
TestRunner_end();
主要是准备Test对象,
3.2类图
3.3TestRunner调用过程
这里不过多解释,根据代码分析一下就可以
3.4TestCaller调用过程
TestCaller运行中,生成一个TestCase,然后根据我们应用做好的TestFixture,作为runTest函数,然后在依次启动TestCase Run函数依次运行我们的测试用例
3.5TestResult分析
注意我assertTest,只加了一个testASSERT_EQUAL_STRING,打印结果如下
***********************************************************
************** Unit Test Start ***************
***********************************************************
OK (1 tests)
***********************************************************
************** Unit Test End ***************
***********************************************************
问题:
运行assertTest时,明明它有失败的为什么不打印出来呢?
带着问题我们来分析。
new_TestFixture("testASSERT_EQUAL_STRING",testASSERT_EQUAL_STRING),
是调用testASSERT_EQUAL_STRING该函数
static void testASSERT_EQUAL_STRING(void)
{
//接口函数的实现都用TestCaseImplement
//#define new_TestCase(name,setUp,tearDown,runTest)\
// {\
// (TestImplement*)&TestCaseImplement,\
// name,\
// setUp,\
// tearDown,\
// runTest,\
// }
TestCase tcase = new_TestCase("assert_equal_string",NULL,NULL,assert_equal_string_runTest);
verify(&tcase);
}
它会生成TestCase,就是一个Test对象,再看verify
static void verify(TestCaseRef test)
{
TestResult result = new_TestResult(NULL);
test->isa->run(test,&result);
if (result.failureCount == 0) {
TEST_FAIL("fail");
}
}
生成一个TestResult,注意它没有Listner接口。这时再分析TestCase的Run函数
它的result有POP和PUSH的过程,此处自己体会。由于Listner接口。所以
TEST_ASSERT_EQUAL_STRING("123","456"); 此时用的TestResult没有addfail接口。自然没有失败打印。
那如何才能有呢。
两种方法:
- 直接用TestRunner中的TestResult,
- 自己定义TestReult,增加Listener接口。
- 很简单,在添加时如下
new_TestFixture("testASSERT_EQUAL_STRING",assert_equal_string_runTest), 直接添加该运行函数。注意结果如下。
***********************************************************
************** Unit Test Start ***************
***********************************************************
AssertTest.testASSERT_EQUAL_STRING (..\emb\Tests\assertTest.c 95) exp "123" was "456"
run 1 failures 1
***********************************************************
************** Unit Test End ***************
***********************************************************
失败结果打印出来了。
第二种,可以自行添加代码。我加了也可以实现。
3.6TestUI分析
调用方式如下。
先设置Outputer对象可以是Text和XML,格式。这里又是面向对象的应用。
TextUIRunner_setOutputter(TextOutputter_outputter());
TextUIRunner_start();
TextUIRunner_runTest(assertTest_tests());
TextUIRunner_runTest(stdImplTest_tests());
TextUIRunner_runTest(TestCaseTest_tests());
TextUIRunner_runTest(TestCallerTest_tests());
TextUIRunner_runTest(TestResultTest_tests());
TextUIRunner_runTest(RepeatedTestTest_tests());
TextUIRunner_end();
使用方式差不多 ,不做具体分析,