前言
CppUnit是一个开源的单元测试框架,支持Linux和Windows操作系统,在linux上可以直接进行源码编译,得到动态库和静态库,直接链接就可以正常使用,在Windows上可以使用VC直接进行编译,非常便于调试。CppUnit的源码框架被运用到了Java和Python等语言中,使用非常广泛,熟悉了一种语言下的CppUnit使用方法,其他语言测试框架也不在话下,本文以cppunit-1.12.1为例进行演示和说明。
一个例子
Linux下CppUnit源码编译和安装
- 解压源码文件到cppunit-1.12.1目录
- cd cppunit-1.12.1
- ./configure --prefix=安装路径(必须是绝对路径)
- make
- make install
编辑测试代码
一共三个文件main.cpp、simpleTest.h、simpleTest.c,目录下文件的组织结构如下所示:
三个文件的源码如下:
//main.cpp文件 #include "cppunit/TestResultCollector.h" #include "cppunit/TextOutputter.h" #include "cppunit/XmlOutputter.h" #include "cppunit/CompilerOutputter.h" #include "cppunit/TestResult.h" #include "cppunit/TestRunner.h" #include "cppunit/extensions/TestFactoryRegistry.h" #include <cstdlib> #include <ostream> int main() { CppUnit::TestResult r; CppUnit::TestResultCollector rc; r.addListener(&rc); // 准备好结果收集器 CppUnit::TestRunner runner; // 定义执行实体 runner.addTest(CppUnit::TestFactoryRegistry::getRegistry("alltest").makeTest()); runner.run(r); // 运行测试 //CppUnit::TextOutputter o(&rc, std::cout); //o.write(); // 将结果输出 //std::ofstream file; //file.open("./UnitTest.xml"); //CppUnit::XmlOutputter xo(&rc, file); //xo.write(); CppUnit::CompilerOutputter co(&rc, std::cout); co.write(); return rc.wasSuccessful() ? 0 : -1; }
//SimpleTest .h文件 #include "cppunit/extensions/HelperMacros.h" class SimpleTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(SimpleTest); CPPUNIT_TEST(test1); CPPUNIT_TEST(test2); CPPUNIT_TEST_SUITE_END(); public: void test1(); void test2(); };
//simpleTest.cpp文件 #include "simpleTest.h" #include <string> #include <iostream> #include "cppunit/TestCase.h" #include "cppunit/TestAssert.h" CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SimpleTest, "alltest"); void SimpleTest::test2() { CPPUNIT_ASSERT(3 == 3); } void SimpleTest::test1() { CPPUNIT_ASSERT(2 == 2); }
编译命令如下:
g++ main.cpp simpleTest.cpp -o test -I /home/chusiyong/cppunit/install/include -L /home/chusiyong/cppunit/install/lib -Wl,-Bstatic -lcppunit -Wl,-Bdynamic -ldl
运行可执行文件,结果如下:
OK (2)
表示所有用例都执行成功
源码分析
1.主要类的继承关系
- Test相关类
-
- Test类作为所有测试用例的基类,是一个抽象类,含有纯虚函数
- Test类主要的方法是run方法
- 采用Composition设计模式(类比文件和文件夹的设计方法)
- TestComposition类主要是实现类名的处理,并提供start和end的处理(配合TestListener使用)
- TestSuite类非常重要,里面可以包含多个Test类,使用Vector的方式保存
- TestFixture类主要提供setUp和tearDown方法,用于运行测试用例前和测试用例后执行
- TestLeaf类主要是实现Test类中的部分方法,在体现通用性的同时做安全措施,重写必要的virtual方法(实现Test类中模板方法中调用的函数),继承该类的子类只能是单个测试用例,不能包含子测试用例
- TestCase类是主要的测试类,每一个TestCase表示一个测试用例
- TestCaller类主要用来生成TestCase实例,将TestFixture类中的每一个测试方法,变成一个单独的TestCase实例(很重要),然后将TestCase实例加入到TestSuite中
//TestComposite.h class CPPUNIT_API TestComposite : public Test { public: TestComposite( const std::string &name = "" ); ~TestComposite(); void run( TestResult *result ); std::string getName() const; private: const std::string m_name; }; //TestComposite.cpp void TestComposite::run( TestResult *result ) { ... doRunChildTests( result ); ... } void TestComposite::doRunChildTests( TestResult *controller) //关键方法,调用每一个子用例的run方法 { int childCount = getChildTestCount(); for ( int index =0; index < childCount; ++index ) { if ( controller->shouldStop() ) break; getChildTestAt( index )->run( controller ); } } std::string TestComposite::getName() const //获取测试用例名称 { return m_name; }
//TestSuite.h class CPPUNIT_API TestSuite : public TestComposite { public: TestSuite( std::string name = "" ); ~TestSuite(); void addTest( Test *test ); //添加测试用例 virtual void deleteContents(); //删除测试用例 int getChildTestCount() const; //根据vector获取子用例个数 Test *doGetChildTestAt( int index ) const;//根据index获取子用例对象 private: CppUnitVector<Test *> m_tests; //保存子用例 }; //TestSuite.cpp void TestSuite::deleteContents() //删除所有测试用例 { int childCount = getChildTestCount(); for ( int index =0; index < childCount; ++index ) delete getChildTestAt( index ); m_tests.clear(); } void TestSuite::addTest( Test *test ) //添加测试用例 { m_tests.push_back( test ); } int TestSuite::getChildTestCount() const //获取子测试用例的个数 { return m_tests.size(); } Test *TestSuite::doGetChildTestAt( int index ) const//根据index获取子用例对象 { return m_tests[index]; }
//TestLeaf.h 没有实现run方法,不能生成实例对象 class CPPUNIT_API TestLeaf: public Test { public: int countTestCases() const; //Test类中的checkIsValidIndex方法调用 int getChildTestCount() const;//Test类中的checkIsValidIndex方法调用 Test *doGetChildTestAt( int index ) const;//Test类中的getChildTestAt方法调用 }; //TestLeaf.cpp int TestLeaf::countTestCases() const { return 1; } int TestLeaf::getChildTestCount() const { return 0; } Test *TestLeaf::doGetChildTestAt( int index ) const { checkIsValidIndex( index ); return NULL; // never called, checkIsValidIndex() always throw. }
//TestFixture.h class CPPUNIT_API TestFixture //接口 { public: virtual ~TestFixture() {}; virtual void setUp() {};//运行用例前调用 virtual void tearDown() {};//运行用例后调用 };
//TestCase.h class CPPUNIT_API TestCase : public TestLeaf, public TestFixture { public: TestCase( const std::string &name ); TestCase(); ~TestCase(); virtual void run(TestResult *result); //实现纯虚方法 std::string getName() const; //获取用例名称 virtual void runTest(); //子类实现 private: TestCase( const TestCase &other ); TestCase &operator=( const TestCase &other ); private: const std::string m_name; }; //TestCase.cpp //说明:运行测试用例的时候,是采用的保护性运行方式,保证一个用例执行失败后续的用例可以继续执行 //采用try{...}catch{...}的模式,失败就抛异常,然后记录,继续执行 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 ); }
-
- TestCaseMethodFunctor类:函数对象,作用就是封装方法,便于使用
class TestCaseMethodFunctor : public Functor { public: typedef void (TestCase::*Method)(); TestCaseMethodFunctor( TestCase *target, Method method ) : m_target( target ) , m_method( method ) { } bool operator()() const //重载()操作符 { (m_target->*m_method)(); //直接调用测试用例的地方 return true; } private: TestCase *m_target; Method m_method; };
template <class Fixture> class TestCaller : public TestCase { public: TestCaller( std::string name, TestMethod test ) : TestCase( name ), m_ownFixture( true ), m_fixture( new Fixture() ), m_test( test ) { } TestCaller(std::string name, TestMethod test, Fixture& fixture) : TestCase( name ), m_ownFixture( false ), m_fixture( &fixture ), m_test( test ) { } TestCaller(std::string name, TestMethod test, Fixture* fixture) : TestCase( name ), m_ownFixture( true ), m_fixture( fixture ), m_test( test ) { } ~TestCaller() { if (m_ownFixture) delete m_fixture; } void runTest() { (m_fixture->*m_test)(); //运行测试用例的地方 } void setUp() { m_fixture->setUp (); } void tearDown() { m_fixture->tearDown (); } std::string toString() const { return "TestCaller " + getName(); } private: TestCaller( const TestCaller &other ); TestCaller &operator =( const TestCaller &other ); private: bool m_ownFixture; Fixture *m_fixture; //new出来的测试对象,即TestCase typedef void (Fixture::*TestMethod)(); TestMethod m_test;//Testcase中的一个测试方法 };
- TestListener相关类
-
- TestListener类和TestResult类之间是采用观察者模式,TestResult类将测试用例的执行结果通知给TestListener类
- TestListener类将保持的结果,通过OutPutter类显示出来
- TestSuccessListener类主要作用是实现多线程安全
class CPPUNIT_API TestListener { public: virtual ~TestListener() {} virtual void addFailure( const TestFailure & /*failure*/ ) {} //主要的函数,当测试用例执行失败时,调用该接口将结果保持到观察者实例中 };
//TestSuccessListener.h class CPPUNIT_API TestSuccessListener : public TestListener, public SynchronizedObject { public: TestSuccessListener( SynchronizationObject *syncObject = 0 ); virtual ~TestSuccessListener(); virtual void reset(); void addFailure( const TestFailure &failure ); //添加失败信息 virtual bool wasSuccessful() const; //判断执行结果 private: bool m_success; }; //TestSuccessListener.cpp void TestSuccessListener::addFailure( const TestFailure &failure ) { ExclusiveZone zone( m_syncObject ); //多线程时的锁 m_success = false; } bool TestSuccessListener::wasSuccessful() const { ExclusiveZone zone( m_syncObject ); return m_success; } void TestSuccessListener::reset() { ExclusiveZone zone( m_syncObject ); m_success = true; }
//TestResultCollector.h class CPPUNIT_API TestResultCollector : public TestSuccessListener { public: TestResultCollector( SynchronizationObject *syncObject = 0 ); virtual ~TestResultCollector(); void addFailure( const TestFailure &failure ); virtual void reset(); virtual int testErrors() const; virtual int testFailures() const; virtual int testFailuresTotal() const; virtual const TestFailures& failures() const; protected: void freeFailures(); typedef CppUnitDeque<Test *> Tests; Tests m_tests; typedef CppUnitDeque<TestFailure *> TestFailures; TestFailures m_failures; int m_testErrors; }; //TestResultCollector.cpp void TestResultCollector::freeFailures() //释放所有错误信息 { TestFailures::iterator itFailure = m_failures.begin(); while ( itFailure != m_failures.end() ) delete *itFailure++; m_failures.clear(); } void TestResultCollector::reset() //将Listener的状态变成初始状态 { TestSuccessListener::reset(); ExclusiveZone zone( m_syncObject ); freeFailures(); m_testErrors = 0; m_tests.clear(); } void TestResultCollector::addFailure( const TestFailure &failure )//添加错误信息 { TestSuccessListener::addFailure( failure ); ExclusiveZone zone( m_syncObject ); if ( failure.isError() ) ++m_testErrors; m_failures.push_back( failure.clone() ); } int TestResultCollector::testFailuresTotal() const //返回错误信息的个数(包括error) { ExclusiveZone zone( m_syncObject ); return m_failures.size(); } int TestResultCollector::testFailures() const //返回失败用例的个数(不包括error) { ExclusiveZone zone( m_syncObject ); return m_failures.size() - m_testErrors; } //返回错误信息 const TestResultCollector::TestFailures & TestResultCollector::failures() const { ExclusiveZone zone( m_syncObject ); return m_failures; } //返回error的个数 int TestResultCollector::testErrors() const { ExclusiveZone zone( m_syncObject ); return m_testErrors; }
-
- TestFailure类:用于表示测试用例的执行结果,一个测试用例执行失败就会生成一个TestFailure类的实例
- TestFailure可以表示用例执行失败,也可以表示error,二者的区别是:测试用例执行失败时抛出的异常是已知的,如果执行用例时抛出未知异常,就是error
//TestFailure.cpp.h class CPPUNIT_API TestFailure { public: TestFailure( Test *failedTest, Exception *thrownException, bool isError ); virtual ~TestFailure (); virtual Test *failedTest() const; //返回失败用例对象 virtual Exception *thrownException() const; //返回抛出的对象 virtual SourceLine sourceLine() const; //获取抛出异常的代码行号 virtual bool isError() const; //判断是用例失败还是error virtual std::string failedTestName() const;//获取失败测试用例的名称 virtual TestFailure *clone() const;//克隆 protected: Test *m_failedTest; Exception *m_thrownException; bool m_isError; private: TestFailure( const TestFailure &other ); TestFailure &operator =( const TestFailure& other ); }; //TestFailure.cpp TestFailure::TestFailure( Test *failedTest, Exception *thrownException, bool isError ) : m_failedTest( failedTest ), //失败的用例 m_thrownException( thrownException ), //抛出的异常 m_isError( isError )//是用例失败还是未知异常 { }
- 多线程安全同步机制
-
-
SynchronizationObject类似一个基类,提供lock和unlock的接口,可以依据不同的平台进行继承实现互斥锁
-
ExclusiveZone类的作用是封装SynchronizationObject类,方便使用互斥锁,关键就是在构造函数中调用lock函数,析构函数中调用unlock函数,无需手动调用lock和unlock函数
class CPPUNIT_API SynchronizedObject { public: class SynchronizationObject //实现互斥锁的基类 { public: SynchronizationObject() {} virtual ~SynchronizationObject() {} virtual void lock() {} virtual void unlock() {} }; SynchronizedObject( SynchronizationObject *syncObject =0 ); virtual ~SynchronizedObject(); protected: class ExclusiveZone //封装SynchronizationObject类的使用方式 { SynchronizationObject *m_syncObject; public: ExclusiveZone( SynchronizationObject *syncObject ) : m_syncObject( syncObject ) { m_syncObject->lock(); //构造函数中调用lock函数 } ~ExclusiveZone() { m_syncObject->unlock();//析构函数中调用unlock函数 } }; virtual void setSynchronizationObject( SynchronizationObject *syncObject ); protected: SynchronizationObject *m_syncObject; private: SynchronizedObject( const SynchronizedObject © ); void operator =( const SynchronizedObject © ); };
- TestResult类,与TestListener类组成观察者模式,其中TestResult类是被观察者,TestListener类是观察者
-
-
-
TestResult类的runTest方法会调用每一个测试用例的run方法
-
TestResult类的关键代码如下:
-
//TestResult.h class CPPUNIT_API TestResult : protected SynchronizedObject { public: TestResult( SynchronizationObject *syncObject = 0 ); virtual ~TestResult(); virtual void addListener( TestListener *listener );//添加测试用例 virtual void removeListener( TestListener *listener );//移除测试用例 virtual void addFailure( Test *test, Exception *e );//添加失败信息 virtual void runTest( Test *test );//入口方法 protected: void addFailure( const TestFailure &failure ); //将失败的消息通知给所有的观察者 protected: typedef CppUnitDeque<TestListener *> TestListeners; TestListeners m_listeners;//保存所有的监听者 }; //TestResult.cpp void TestResult::addListener( TestListener *listener )//添加测试用例 { ExclusiveZone zone( m_syncObject ); m_listeners.push_back( listener ); } void TestResult::removeListener ( TestListener *listener )//移除测试用例 { ExclusiveZone zone( m_syncObject ); removeFromSequence( m_listeners, listener ); } void TestResult::runTest( Test *test )//入口方法 { startTestRun( test ); test->run( this ); endTestRun( test ); } void TestResult::addFailure( Test *test, Exception *e )//用例失败时被调用 { TestFailure failure( test, e, false ); addFailure( failure ); } void TestResult::addFailure( const TestFailure &failure )//将失败的消息通知给所有的观察者 { ExclusiveZone zone( m_syncObject ); for ( TestListeners::iterator it = m_listeners.begin(); it != m_listeners.end(); ++it ) (*it)->addFailure( failure ); }
- OutPutter相关类
-
- OutPutter是公共的基类,提供统一的接口
class CPPUNIT_API Outputter { public: virtual ~Outputter() {} virtual void write() =0; //关键方法 };
-
-
-
XmlOutPutter类:将执行结果按照xml模式打印出来,一般是保存到xml文件中(一般在自动化中使用)
-
CompilerOutPutter类:将执行的结果以编译器兼容的模式打印出来,便于调试,一般不怎么使用
-
总结:就是将TestResult类中的Failure信息以不同的格式输出
-
2. 创建测试用例的相关类
- 创建测试用例的相关类主要使用了工厂模式
- 创建的具体过程使用了宏进行简化
- 相关类如下
- ConcretTestFixtureFactory类:用于创建测试用例对象
class TestFixtureFactory { public: virtual TestFixture *makeFixture() =0; //用于创建具体测试用例的公共方法 virtual ~TestFixtureFactory() {} }; //使用工厂方法模式 template<class TestFixtureType> class ConcretTestFixtureFactory : public CPPUNIT_NS::TestFixtureFactory { TestFixture *makeFixture() { return new TestFixtureType(); //根据具象的类型创建实例 } };
-
- TestSuiteBuilderContextBase类:用于将测试用例对象添加到suite中
//TestSuiteBuilderContextBase.h class CPPUNIT_API TestSuiteBuilderContextBase { public: TestSuiteBuilderContextBase( TestSuite &suite, const TestNamer &namer, TestFixtureFactory &factory ); virtual ~TestSuiteBuilderContextBase(); void addTest( Test *test ); protected: TestFixture *makeTestFixture() const; //创建测试用例 TestSuite &m_suite; //用于保存测试用例的suite const TestNamer &m_namer; //保存suite的名称 TestFixtureFactory &m_factory; //创建测试用例的工厂 }; //TestSuiteBuilderContextBase.cpp TestSuiteBuilderContextBase::TestSuiteBuilderContextBase( TestSuite &suite, const TestNamer &namer, TestFixtureFactory &factory ) : m_suite( suite ) , m_namer( namer ) , m_factory( factory ) {//构造函数 } TestFixture *TestSuiteBuilderContextBase::makeTestFixture() const { return m_factory.makeFixture(); } void TestSuiteBuilderContextBase::addTest( Test *test ) //添加用例到suite { m_suite.addTest( test ); }
- TestFactory类
class CPPUNIT_API TestFactory { public: virtual ~TestFactory() {} virtual Test* makeTest() = 0; };
- TestSuiteFactory类:此处调用的TestCaseType::suite()返回的suite就是包含测试用例的suite
template<class TestCaseType> class TestSuiteFactory : public TestFactory { public: virtual Test *makeTest() { return TestCaseType::suite(); //关键方法,该方法的实现是宏定义 } };
-
- TestFactoryRegistry类:用于从TestFactoryRegistryList类中获取指定名称的TestFactoryRegistry实例
- TestFactoryRegistryList类:单例类,根据名称保存所有的TestFactoryRegistry实例
- AutoRegisterSuite类:封装TestSuiteFactory的注册方式
3.框架入口类
- TestRunner类是整个CppUnit的入口类,将TestSuite类、TestResult类以及TestListener类联合在一起,然后提供统一的入口方法,便于使用
- TestListener实例包含在TestResult实例里面
- 部分代码如下:
//TestRunner.h class CPPUNIT_API TestRunner { public: TestRunner( ); virtual ~TestRunner(); //将需要运行的测试用例添加进来 virtual void addTest( Test *test ); //运行指定的测试用例 virtual void run( TestResult &controller, const std::string &testPath = "" ); protected: //内部类,对suite进行了包装 class CPPUNIT_API WrappingSuite : public TestSuite { public: WrappingSuite( const std::string &name = "All Tests" ); int getChildTestCount() const; std::string getName() const; void run( TestResult *result ); protected: Test *doGetChildTestAt( int index ) const; bool hasOnlyOneTest() const; Test *getUniqueChildTest() const; }; protected: WrappingSuite *m_suite; private: TestRunner( const TestRunner © ); void operator =( const TestRunner © ); private: }; //TestRunner.cpp void TestRunner::addTest( Test *test ) { m_suite->addTest( test ); } void TestRunner::run( TestResult &controller, const std::string &testPath ) { TestPath path = m_suite->resolveTestPath( testPath ); Test *testToRun = path.getChildTest(); controller.runTest( testToRun ); }
4.重要宏的解析
- CPPUNIT_TEST_SUITE_NAMED_REGISTRATION
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SimpleTest, "alltest"); //展开后如下 static CPPUNIT_NS::AutoRegisterSuite<SimpleTest> autoRegisterRegistry__12("alltest");
- CPPUNIT_TEST_SUITE、CPPUNIT_TEST以及CPPUNIT_TEST_SUITE_END宏,这三个红必须配合使用,不能单独使用,用于声明需要运行的测试用例
private: //根据typeid(SimpleTest)为名称生成TestNamer类的实例,就是对名称的封装 static const CPPUNIT_NS::TestNamer &getTestNamer__() { static CPPUNIT_NS::TestNamer testNamer(typeid(SimpleTest)); return testNamer; } public: //将测试用例添加到suite中 static void addTestsToSuite( CPPUNIT_NS::TestSuiteBuilderContextBase &baseContext ) { CPPUNIT_NS::TestSuiteBuilderContext<SimpleTest> context(baseContext) context.addTest(( new CPPUNIT_NS::TestCaller<SimpleTest>(context.getTestNameFor( #testMethod), &SimpleTest::testMethod, context.makeFixture()))) } //对外接口,被TestSuiteFactory中的makeTest方法调用,返回一个完整的suite,等待被运行 static CPPUNIT_NS::TestSuite *suite() { const CPPUNIT_NS::TestNamer &namer = getTestNamer__(); std::auto_ptr<CPPUNIT_NS::TestSuite> suite(new CPPUNIT_NS::TestSuite(namer.getFixtureName())); CPPUNIT_NS::ConcretTestFixtureFactory<SimpleTest> factory; CPPUNIT_NS::TestSuiteBuilderContextBase context(*suite.get(), namer, factory ); SimpleTest::addTestsToSuite( context ); return suite.release(); }