CppUnit快速入門

來源:互聯網
上載者:User

From:http://blog.csdn.net/freefalcon/archive/2006/05/25/753819.aspx

簡介

測試是軟體開發過程中極其重要的一環,詳盡周密的測試能夠減少軟體BUG,提高軟體品質。測試包括單元測試、系統測試等。其中單元測試是指標對軟體功能單元所作的測試,這裡的功能單元可以是一個類的屬性或者方法,測試的目的是看這些基本單元是否工作正常。由於單元測試的內容很基礎,因此可以看作是測試工作的第一環,該項工作一般由開發人員自行完成。如果條件允許,單元測試代碼的開發應與程式碼的開發同步進行。

雖然不同程式的單元測試代碼不盡相同,但測試代碼的架構卻非常相似,於是便出現了一些單元測試類庫,CppUnit便是其中之一。

CppUnit是XUnit中的一員,XUnit是一個大家族,還包括JUnit和PythonUnit等。CppUnit簡單實用,學習和使用起來都很方便,網上已有一些文章對其作介紹,但本文更著重於講解其中的基本概念和使用方法,以協助初次接觸CppUnit的人員快速入門。

安裝

目前,CppUnit的最新版本是1.10.2,你可以從下面地址擷取:

http://sourceforge.net/projects/cppunit

解壓後,你可以看到CppUnit包含如下目錄:

config:  設定檔    contrib: contribution,其他人貢獻的外圍代碼    doc:     文檔,需要通過doxygen工具產生,也可以直接從sourceforge網站上下載打包好的文檔    examples:範例程式碼    include: 標頭檔    lib:     存放編譯好的庫    src:     源檔案,以及編譯庫的工程等

然後開啟src目錄下的CppUnitLibraries工程,執行build/batch build,編譯成功的話,產生的庫檔案將被拷貝到lib目錄下。

你也可以根據需要選擇所需的項目進行編譯,其中項目cppunit為靜態庫,cppunit_dll為動態庫,產生的庫檔案為:

cppunit.lib:     靜態庫release版    cppunitd.lib:    靜態庫debug版    cppunit_dll.lib: 動態庫release版    cppunitd_dll.lib:動態庫debug版

要使用CppUnit,還得設定好標頭檔和庫檔案路徑,以VC6為例,選擇Tools/Options/Directories,在Include files和Library files中分別添加%CppUnitPath%\include和%CppUnitPath%\lib,其中%CppUnitPath%表示CppUnit所在路徑。

做好準備工作後,我們就可以編寫自己的單元測試代碼了。需說明的是,CppUnit所用的動態運行期庫均為多線程動態庫,因此你的單元測試程式也得使用相應設定,否則會發生衝突。

概念

在使用之前,我們有必要認識一下CppUnit中的主要類,當然你也可以先看後面的例子,遇到問題再回過頭來看這一節。

CppUnit核心內容主要包括六個方面,

1. 測試對象(Test,TestFixture,...):用於開發測試案例,以及對測試案例進行組織管理。

2. 測試結果(TestResult):處理測試案例執行結果。TestResult與下面的TestListener採用的是觀察者模式(Observer Pattern)。

3. 測試結果監聽者(TestListener):TestListener作為TestResult的觀察者,擔任實際的結果處理角色。

4. 結果輸出(Outputter):將結果進行輸出,可以制定不同的輸出格式。

5. 對象工廠(TestFactory):用於建立測試對象,對測試案例進行自動化管理。

6. 測試執行體(TestRunner):用於運行一個測試。

以上各模組的主要類繼承結構如下:

         Test              TestFixture      TestResult          TestListener             _______|_________            |                                    |                  |               |            |                           TestSuccessListener    TestComposite   TestLeaf         |                                    |                  |               |____________|                           TestResultCollector              TestSuit                  |                           TestCase                                                   |                      TestCaller<Fixture>                                              Outputter                                    TestFactory                    TestRunner        ____________________|_________________                            |        |                   |                |                   TestFactoryRegistry    CompilerOutputter  TextOutputter    XmlOutputter                      |                                                             TestSuiteFactory<TestCaseType>

接下來再對其中一些關鍵類作以介紹。

Test:所有測試對象的基類。

CppUnit採用樹形結構來組織管理測試對象(類似於分類樹),因此這裡採用了組合設計模式(Composite Pattern),Test的兩個直接子類TestLeaf和TestComposite分別表示“測試樹”中的分葉節點和非分葉節點,其中TestComposite主要起組織管理的作用,就像分類樹中的檔案夾,而TestLeaf才是最終具有執行能力的測試對象,就像分類樹中的檔案。

Test最重要的一個公用介面為:

virtual void run(TestResult *result) = 0;

其作用為執行測試對象,將結果提交給result。

在實際應用中,我們一般不會直接使用Test、TestComposite以及TestLeaf,除非我們要重新定製某些機制。

TestFixture:用於維護一組測試案例的上下文環境。

在實際應用中,我們經常會開發一組測試案例來對某個類的介面加以測試,而這些測試案例很可能具有相同的初始化和清理代碼。為此,CppUnit引入TestFixture來實現這一機制。

TestFixture具有以下兩個介面,分別用於處理測試環境的初始化與清理工作:

virtual void setUp();
virtual void tearDown();

TestCase:測試案例,從名字上就可以看出來,它便是單元測試的執行對象。

TestCase從Test和TestFixture多繼承而來,通過把Test::run制定成模板函數(Template Method)而將兩個父類的操作融合在一起,run函數的偽定義如下:

// 虛擬碼
void TestCase::run(TestResult* result)
{
    result->startTest(this); // 通知result測試開始
if( result->protect(this, &TestCase::setUp) ) // 調用setUp,初始化環境
        result->protect(this, &TestCase::runTest); // 執行runTest,即真正的測試代碼
    result->protect(this, &TestCase::tearDown); // 調用tearDown,清理環境
    result->endTest(this); // 通知result測試結束
}

這裡要提到的是函數runTest,它是TestCase定義的一個介面,原型如下:

virtual void runTest();

使用者需從TestCase派生出子類並實現runTest以開發自己所需的測試案例。

另外還要提到的就是TestResult的protect方法,其作用是對執行函數(實際上是函數對象)的錯誤資訊(包括斷言和異常等)進行捕獲,從而實現對測試結果的統計。

TestSuit:測試包,按照樹形結構管理測試案例

TestSuit是TestComposite的一個實現,它採用vector來管理子測試對象(Test),從而形成遞迴的樹形結構。

TestCaller:TestCase適配器(Adapter),它將成員函數轉換成測試案例

雖然我們可以從TestCase派生自己的測試類別,但從TestCase類的定義可以看出,它只能支援一個測試案例,這對於測試代碼的組織和維護很不方便,尤其是那些有共同上下文環境的一組測試。為此,CppUnit提供了TestCaller以解決這個問題。

TestCaller是一個模板類,它以實現了TestFixture介面的類為模板參數,將目標類中某個符合runTest原型的測試方法適配成TestCase的子類。

在實際應用中,我們大多採用TestFixture和TestCaller相組合的方式,具體例子參見後文。

TestResult和TestListener:處理測試資訊和結果

前面已經提到,TestResult和TestListener採用了觀察者模式,TestResult維護一個註冊表,用於管理向其登記過的TestListener,當TestResult收到測試對象(Test)的測試資訊時,再一一分發給它所管轄的TestListener。這一設計有助於實現對同一測試的多種處理方式。

TestFactory:測試工廠

這是一個輔助類,通過藉助一系列宏定義讓測試案例的組織管理變得自動化。參見後面的例子。

TestRunner:用於執行測試案例

TestRunner將待執行的測試對象管理起來,然後供使用者調用。其介面為:

virtual void addTest( Test *test ); virtual void run( TestResult &controller, const std::string &testPath = "" );

這也是一個輔助類,需注意的是,通過addTest添加到TestRunner中的測試對象必須是通過new動態建立的,使用者不能刪除這個對象,因為TestRunner將自行管理測試對象的生命期。

使用

先讓我們看看一個簡單的例子:

#include <cppunit/TestCase.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
// 定義測試案例
class SimpleTest : public CppUnit::TestCase
{
public:
void runTest() // 重載測試方法
    {
int i = 1;
        CPPUNIT_ASSERT_EQUAL(0, i);
    }
};
int main(int argc, char* argv[])
{
    CppUnit::TestResult r;
    CppUnit::TestResultCollector rc;
    r.addListener(&rc); // 準備好結果收集器
    SimpleTest t;
    t.run(&r); // 運行測試案例
    CppUnit::TextOutputter o(&rc, std::cout);
    o.write(); // 將結果輸出
return 0;
}

編譯後運行,輸出結果為:

!!!FAILURES!!!
Test Results:
Run: 1 Failures: 1 Errors: 0
1) test: (F) line: 18 E:\CppUnitExamples\SimpleTest.cpp
equality assertion failed
- Expected: 1
- Actual : 0

上面的例子很簡單,需說明的是CPPUNIT_ASSERT_EQUAL宏。CppUnit定義了一組宏用於檢測錯誤,CPPUNIT_ASSERT_EQUAL是其中之一,當宣告失敗時,CppUnit便會將錯誤資訊報告給TestResult。這些宏定義的說明如下:

CPPUNIT_ASSERT(condition):判斷condition的值是否為真,如果為假則建置錯誤資訊。

CPPUNIT_ASSERT_MESSAGE(message, condition):與CPPUNIT_ASSERT類似,但結果為假時報告messsage資訊。

CPPUNIT_FAIL(message):直接報告messsage錯誤資訊。

CPPUNIT_ASSERT_EQUAL(expected, actual):判斷expected和actual的值是否相等,如果不等輸出錯誤資訊。

CPPUNIT_ASSERT_EQUAL_MESSAGE(message, expected, actual):與CPPUNIT_ASSERT_EQUAL類似,但宣告失敗時輸出message資訊。

CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta):判斷expected與actual的偏差是否小於delta,用於浮點數比較。

CPPUNIT_ASSERT_THROW(expression, ExceptionType):判斷執行運算式expression後是否拋出ExceptionType異常。

CPPUNIT_ASSERT_NO_THROW(expression):斷言執行運算式expression後無異常拋出。

接下來再看看TestFixture和TestCaller的組合使用:

#include <cppunit/TestCase.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestRunner.h>
// 定義測試類別
class StringTest : public CppUnit::TestFixture
{
public:
void setUp() // 初始化
    {
        m_str1 = "Hello, world";
        m_str2 = "Hi, cppunit";
    }
void tearDown() // 清理
    {
    }
void testSwap() // 測試方法1
    {
std::string str1 = m_str1;
std::string str2 = m_str2;
        m_str1.swap(m_str2);
        CPPUNIT_ASSERT(m_str1 == str2);
        CPPUNIT_ASSERT(m_str2 == str1);
    }
void testFind() // 測試方法2
    {
int pos1 = m_str1.find(',');
int pos2 = m_str2.rfind(',');
        CPPUNIT_ASSERT_EQUAL(5, pos1);
        CPPUNIT_ASSERT_EQUAL(2, pos2);
    }
protected:
std::string     m_str1;
std::string     m_str2;
};
int main(int argc, char* argv[])
{
    CppUnit::TestResult r;
    CppUnit::TestResultCollector rc;
    r.addListener(&rc); // 準備好結果收集器
    CppUnit::TestRunner runner; // 定義執行實體
    runner.addTest(new CppUnit::TestCaller<StringTest>("testSwap", &StringTest::testSwap)); // 構建測試案例1
    runner.addTest(new CppUnit::TestCaller<StringTest>("testFind", &StringTest::testFind)); // 構建測試案例2
    runner.run(r); // 運行測試
    CppUnit::TextOutputter o(&rc, std::cout);
    o.write(); // 將結果輸出
return rc.wasSuccessful() ? 0 : -1;
}

編譯後運行結果為:

OK (2 tests)

上面的代碼從功能上講沒有什麼問題,但編寫起來太繁瑣了,為此,我們可以藉助CppUnit定義的一套輔助宏,將測試案例的定義和註冊變得自動化。上面的代碼改造後如下:

#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
// 定義測試類別
class StringTest : public CppUnit::TestFixture
{
    CPPUNIT_TEST_SUITE(StringTest);  // 定義測試包
    CPPUNIT_TEST(testSwap);  // 添加測試案例1
    CPPUNIT_TEST(testFind);  // 添加測試案例2
    CPPUNIT_TEST_SUITE_END();  // 結束測試包定義
public:
void setUp() // 初始化
    {
        m_str1 = "Hello, world";
        m_str2 = "Hi, cppunit";
    }
void tearDown() // 清理
    {
    }
void testSwap() // 測試方法1
    {
std::string str1 = m_str1;
std::string str2 = m_str2;
        m_str1.swap(m_str2);
        CPPUNIT_ASSERT(m_str1 == str2);
        CPPUNIT_ASSERT(m_str2 == str1);
    }
void testFind() // 測試方法2
    {
int pos1 = m_str1.find(',');
int pos2 = m_str2.rfind(',');
        CPPUNIT_ASSERT_EQUAL(5, pos1);
        CPPUNIT_ASSERT_EQUAL(2, pos2);
    }
protected:
std::string     m_str1;
std::string     m_str2;
};
CPPUNIT_TEST_SUITE_REGISTRATION(StringTest); // 自動註冊測試包
int main(int argc, char* argv[])
{
    CppUnit::TestResult r;
    CppUnit::TestResultCollector rc;
    r.addListener(&rc); // 準備好結果收集器
    CppUnit::TestRunner runner; // 定義執行實體
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
    runner.run(r); // 運行測試
    CppUnit::TextOutputter o(&rc, std::cout);
    o.write(); // 將結果輸出
return rc.wasSuccessful() ? 0 : -1;
}

CppUnit的簡單介紹就到此,相信你已經瞭解了其中的基本概念,也能夠開發單元測試代碼了。

其它

CppUnit還包括其它一些輔助模組,比如基於MFC的圖形化測試介面,下面這篇文章對此有所介紹:

CppUnit測試架構入門

CppUnit使用了很多設計模式,整體構架還算清晰合理,源碼也比較簡單易懂,這對於學習設計模式是一個不錯的選擇。網上已有這樣的一些資料:

CppUnit源碼解讀 CppUnit代碼簡介 - 第一部分,核心類

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.