測試驅動開發 (TDD) 以測試為開發過程核心,堅持在撰寫實際程式碼前,先寫好基於產品程式碼的測試程式碼。開發目標是先讓測試通過,再優化設計結構。TDD 是極限編程 (XP) 的重要組成。xUnit 是基於 TDD 的測試框架家族,為開發過程中使用 TDD 提供方便工具,使快速單元測試成為可能。xUnit 成員眾多,如 JUnit、PythonUnit 等。本文介紹的 CppUnit 是 xUnit 家族中專門面向 C++ 的測試框架。
本文不詳細介紹 CppUnit 原始碼,僅介紹其應用。您將看到:CppUnit 原始碼組成、如何設定開發環境使用 CppUnit、如何為產品程式碼加入測試程式碼(實際上 TDD 中先有測試程式碼後有產品程式碼)並透過 CppUnit 測試。敘述背景:CppUnit 1.12.0、Visual C++ 6.0、Windows XP。
從 SourceForge 下載 CppUnit 原始碼套件(開源產品,目前最高版本 1.12.0)。下載解壓縮至本地硬碟(如 E:\cppunit-1.12.0,作者移除版本號改為 E:\cppunit)。解壓後主要資料夾:
doc:說明文件examples:範例程式,也是對 CppUnit 自身的測試include:標頭檔src:原始碼目錄config:設定檔contrib:他人貢獻的外圍程式碼lib:編譯好的程式庫在 src/ 目錄下,用 VC 開啟 CppUnitLibraries.dsw 工作空間。執行 Build/Batch Build 編譯。成功時程式庫會複製到 lib 目錄。中途可能有些專案編譯失敗,一般不必理會,重點關注 cppunit 和 TestRunner 兩個專案的 Debug 與 Release 版本。
編譯通過後,lib/ 目錄下會產生若干 .lib 和 .dll 檔案,皆以 cppunit 開頭。cppunitd 表示 Debug 版,cppunit 表示 Release 版。CppUnit 提供兩套框架程式庫:靜態 .lib 與動態 .dll。cppunit 專案:靜態 lib;cppunit_dll 專案:動態 dll 和 lib。開發中可根據實際情況選擇。
另一個需關注的專案是 TestRunner,輸出一個 dll,提供基於 GUI 方式的測試環境。CppUnit 可選擇主控台與 GUI 兩種呈現方案。


本文選擇 GUI 方式,故也需編譯此專案,輸出位置同為 lib 資料夾。
以 VC6 為例,選擇 Tools/Options/Directories,在 Include files 和 Library files 中分別加入 %CppUnitPath%\include 和 %CppUnitPath%\lib。本文分別填 E:\CPPUNIT\INCLUDE 和 E:\CPPUNIT\LIB。

使用前有必要認識 CppUnit 主要類別:
Test:所有測試物件的基類。CppUnit 採用樹狀結構組織管理測試物件(類似目錄樹),採用 Composite 設計模式。Test 兩個直接子類 TestLeaf 和 TestComposite 分別代表「測試樹」中的葉節點與非葉節點。TestComposite 主要負責組織管理,如目錄樹中的資料夾;TestLeaf 才是最終具備執行能力的測試物件,如目錄樹中的檔案。最重要介面:virtual void run(TestResult *result) = 0;,用於執行測試物件並將結果提交給 result。TestFixture:維護一組測試案例的上下文環境。實際應用中常開發一組測試案例測試某類別介面,這些案例可能具相同初始化與清理程式碼。CppUnit 引入 TestFixture 實現此機制,提供 setUp() 和 tearDown() 兩個介面分別處理測試環境初始化與清理。TestCase:測試案例,單元測試的執行物件。從 Test 和 TestFixture 多重繼承而來,透過將 Test::run 制定成 Template Method 將兩個父類別操作融合。runTest() 是 TestCase 定義的介面,使用者需從 TestCase 衍生子類別並實作 runTest 開發所需測試案例。TestSuite:測試套件,依樹狀結構管理測試案例。TestComposite 一個實作,採用 vector 管理子測試物件,形成遞迴樹狀結構。TestFactory:測試工廠,輔助類別,透過巨集定義讓測試案例組織管理自動化。TestRunner:執行測試案例。管理待執行測試物件供使用者呼叫。介面:addTest(Test *test)、run(TestResult &controller, const std::string &testPath = "")。透過 addTest 加入的測試物件必須是透過 new 動態建立,使用者不可刪除,因 TestRunner 將自行管理測試物件生命週期。
配置完成後即可正式使用。TDD 一般先寫測試程式碼再寫產品程式碼,但作者認為先寫產品程式碼框架再寫測試程式碼,再慢慢補充產品程式碼使通過測試的方法較好。本文決定先寫測試程式碼。
CppUnit 可選擇主控台與 UI 兩種方案,本例選擇 UI 方式,建立基於對話方塊的專案,假設名為 UnitTest。測試程式碼與被測試物件(產品程式碼)通常處於不同專案,避免產品程式碼被測試程式碼「汙染」。
cppunitd.lib testrunnerd.lib,Release 加入 cppunit.lib TestRunner.lib/lib 目錄下的 testrunnerd.dll 和 TestRunner.dll 分別複製到 UnitTest 專案 Debug 和 Release 輸出目錄





CppUnit 以 TestCase 為最小測試單位,多個 TestCase 組成 TestSuite。在 UnitTest 專案中新建類別 CPlusTestCase,繼承自 CppUnit::TestCase。新增方法 testAdd() 撰寫測試程式碼。
#include <cppunit/TestCase.h>
#include <CppUnit/extensions/HelperMacros.h>
class CPlusTestCase : public CppUnit::TestCase
{
CPPUNIT_TEST_SUITE(CPlusTestCase);
CPPUNIT_TEST(testAdd);
CPPUNIT_TEST_SUITE_END();
public:
CPlusTestCase();
virtual ~CPlusTestCase();
void testAdd();
static std::string GetSuiteName();
static CppUnit::Test* GetSuite();
};
使用三個巨集:CPPUNIT_TEST_SUITE() 開始建立 TestSuite,CPPUNIT_TEST() 加入 TestCase,CPPUNIT_TEST_SUITE_END() 結束建立。透過巨集將 CPlusTestCase 和 testAdd 註冊到測試清單。
接著註冊測試 Suite,使用 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION() 註冊到指定 TestFactoryRegistry 工廠:
// PlusTestCase.h
class CPlusTestCase : public CppUnit::TestCase
{
// ... 巨集宣告 ...
static std::string GetSuiteName();
static CppUnit::Test* GetSuite();
};
// PlusTestCase.cpp
std::string CPlusTestCase::GetSuiteName()
{
return "CPlus";
}
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CPlusTestCase, CPlusTestCase::GetSuiteName());
CppUnit::Test* CPlusTestCase::GetSuite()
{
CppUnit::TestFactoryRegistry& reg = CppUnit::TestFactoryRegistry::getRegistry(CPlusTestCase::GetSuiteName());
return reg.makeTest();
}
在 CUnitTestApp::InitInstance() 中屏蔽原有對話方塊,改用 CppUnit GUI:
CppUnit::MfcUi::TestRunner runner;
runner.addTest(CPlusTestCase::GetSuite()); // 加入測試
runner.run(); // 顯示 UI
記得包含標頭檔:#include <cppunit/ui/mfc/TestRunner.h> 和 #include "PlusTestCase.h"。
測試類別中添加測試方法 testAdd(),測試對象為構想中的 CPlus::Add(int, int):
#include <cppunit/TestAssert.h>
void CPlusTestCase::testAdd()
{
CPlus plus;
int nResult = plus.Add(10, 20); // 執行 Add 操作
CPPUNIT_ASSERT_EQUAL(30, nResult); // 檢查結果是否等於 30
}
CPPUNIT_ASSERT_EQUAL 是判斷結果的巨集。也可覆寫 setUp()、tearDown() 進行初始化與清理工作。
編譯測試程式碼時會失敗(CPlus 類別未宣告),這就是「測試驅動」的味道——現在實作 CPlus 類別讓編譯通過。在 VC 中建立 MFC Extension DLL 專案,加入 CPlus 類別:
// Plus.h
class AFX_EXT_CLASS CPlus
{
public:
CPlus();
virtual ~CPlus();
int Add(int nNum1, int nNum2);
};
// Plus.cpp
int CPlus::Add(int nNum1, int nNum2)
{
return nNum1 + nNum2; // 可寫錯誤語句觀察失敗結果
}
讓測試專案 dependent 產品專案,包含相關標頭檔,Rebuild All,編譯通過。
執行測試程式,點擊 Browse,選擇 CPlusTestCase::testAdd 後點擊 Run,即可看到測試結果。若修改 CPlus::Add 回傳錯誤值(如 return 2;),重新編譯執行,GUI 會顯示單元測試失敗,並顯示錯誤位置與原因,良好控制 Bug。


文末提供完整程式清單(PlusTestCase.h、PlusTestCase.cpp、UnitTest.cpp 等)及參考資料:Cpluser《CppUnit 測試框架入門》、Freefalcon《CppUnit 快速入門》、《使用 CppUnit 進行單元測試》。