CppUnit 的核心部分可分成三个主要部分,可以认为这三部分组成 MVC 模式。
1 Model
以 Test 类为基类,向下派生出 TestComposite, TestSuite, TestLeaf, TestCase 等。
其中 Test , TestComposite , TestLeaf 又构成一个 Composite 模式,由 TestCase 可以创建单个测试用例,而用 TestSuite 可以打包一组测试用例。 ( 个人认为 Test 类改为 TestComponent 可能更合适 ) 。
在 test 类中,其主要虚函数为 virtual void run( TestResult *result ) =0; Controller 调用这个函数运行测试用例。
而在TestCase 中,添加了新的虚函数:virtual void runTest();run 函数实现如下:
void TestCase::run( TestResult *result )
{
result->startTest(this);
if ( result->protect( TestCaseMethodFunctor( this, &TestCase::setUp ), this, "setUp() failed" ) )
{
result->protect( TestCaseMethodFunctor( this, &TestCase::runTest ), this );
}
result->protect( TestCaseMethodFunctor( this, &TestCase::tearDown ), this, "tearDown() failed" );
result->endTest( this );
}
可以看到,它在开始一个测试用例时,会回调 Controller 的 StartTest 接口,而 Controller 再回调给 Listener ,最后由 Listener 进行一些操作 ( 比如将该 test 类插入自己的列表,或在界面上显示信息等 ) 。
接着,调用 setUp 函数进行测试前的准备工作,比如准备资源,读取文件等。
setUp 和 tearDown 接口由 TestFixture 定义。 TestCase 类从 TestFixture 和 TestLeaf 多继承下来。
然后,调用 runTest ,运行测试。
测试结束后,调用 tearDown ,释放资源,调用 endTest ,通知 Controller 。
所以,如果我们从 TestCase 派生类写测试用例,应该改写 runTest 接口,而不应该动 run 接口。
TestSuite 的 run 接口实现如下:
调用 Controller 的 startSuite 进行通知;
对每一个子测试用例,调用run 接口
调用 Controller 的 endSuite
2 Controller
主要就 TestResult 一个类( 个人认为应该改为TestController) 。
另外还有一些辅助的类,例如:
1) TestFailure ,Exception ,Message ,SourceLine ,Asserter 等一系列类支持用户在测试代码中加入断言,并且当测试用例失败时记录失败信息,代码位置等
2) SynchronizedObject 及其内嵌的SynchronizationObject ,ExclusiveZone 支持多线程同步
3) Protector ,ProtectorChain ,DefaultProtector ,Functor ,ProtectFunctor 等类,使用了类似Decorator 的模式,在运行时组合Protector ,用于在测试用例失败时捕获异常,并根据异常类型生成相关失败/ 错误信息通知Controller 。
最后是 TestResult 类,它使用了Observer 模式,由多个Listener 注册监听Model 的运行状态。当测试用例开始,结束,有错误发生时,TestResult 调用每一个Listener 的startSuite ,startTest ,addFailure 等等接口回调通知Listener 。
3 View ,也就是 Listener
TestListener 是所有监听者的基类,定义了所有对测试过程和结果回调的接口。包括:
startTestRun/endTestRun :一个大的测试项目开始/ 结束
startSuite/endSuite :一个测试组合(TestSuite) 开始/ 结束
startTest/endTest :一个测试项目(TestCase) 开始/ 结束
addFailure :测试失败,有错误发生
4 TestRunner
TestRunner 相当于Fa ç ade 模式,在最高层提供接口使得底层的模型更易使用。
它主要有两个接口:
virtual void addTest( Test *test );
virtual void run( TestResult &controller, const std::string &testPath = "" );
CppUnit 预先为我们定义好了两个TestRunner ,基于控制台的TextTestRunner 和基于对话框的MfcTestRunner 。
---------------------------------------------------------------------------------------------------------------------
基于对CppUnit 框架的理解,我们很容易开发出单元测试项目。
例如,我们要测试CMyParser 类中的Parse(const CString &text) 接口,首先创建一个文件,存储我们的测试参数:
CMyParser_Parse_testcase.txt:
Line1
Line2
…
然后从TestCase 派生出单元测试类
class CMyParserTestCase : public CppUnit::TestCase
{
public:
static void LoadTestCaseFromFile (const CString fileName, CStringList &caselist);
public:
CMyParserTestCase(const CString &row);
virtual ~CMyParserTestCase(void);
void runTest();
private:
CString m_row;
};
我们提供了一个静态函数LoadTestCaseFromFile 用于读取文件,载入测试参数。
实现如下:
CMyParserTestCase::CMyParserTestCase(const CString &row) : m_row(row){}
CMyParserTestCase::~CMyParserTestCase(void){}
void CMyParserTestCase::runTest()
{
CMyParser myParser;
CPPUNIT_ASSERT(myParser.Parse(m_row));
}
void CMyParserTestCase::LoadTestCaseFromFile (const CString fileName, CStringList &caselist)
{
caselist.RemoveAll();
CStdioFile file (fileName, CFile::modeRead);
CString line;
while (file.ReadString (line))
{
caselist.AddTail (line);
}
}
runTest 中先创建CMyParser 实例,然后通过CPPUNIT_ASSERT 断言对Parse 接口测试。
我们的测试用例类实现了,接下来是主函数:
void main()
{
TextUi::TestRunner runner;
TestSuite *pSuite = new TestSuite;
{// 加入测试子类
CStringList caselist;
CMyParserTestCase::LoadTestCaseFromFile ("CMyParser_Parse_testcase.txt", caselist);
POSITION pos = caselist.GetHeadPosition();
while(pos != NULL)
{
CString oneCase = caselist.GetNext (pos);
CMyParserTestCase *pCase = new CMyParserTestCase (oneCase);
pSuite->addTest (pCase);
}
}
runner.addTest (pSuite);
runner.run("", true, true, false);
}
整个过程是先创建基于控制台的TestRunner 实例,然后创建一个TestSuite ,往里面加入一系列的测试用例。最后调用TestRunner 的addTest 和run 接口。有了CppUnit 的框架,一切就这么简单。