CppUnit測試框架入門

工程師WWW發表於2015-08-27

測試驅動開發(TDD)是以測試作為開發過程的中心,它堅持,在編寫實際程式碼之前,先寫好基於產品程式碼的測試程式碼。開發過程的目標就是首先使測試能夠通過,然後再優化設計結構。測試驅動開發式是極限程式設計的重要組成部分。XUnit,一個基於測試驅動開發的測試框架,它為我們在開發過程中使用測試驅動開發提供了一個方便的工具,使我們得以快速的進行單元測試。XUnit的成員有很多,如JUnit,PythonUnit等。今天給大家介紹的CppUnit即是XUnit家族中的一員,它是一個專門面向C++的測試框架。

本文不對CppUnit原始碼做詳細的介紹,而只是對CppUnit的應用作一些介紹。在本文中,您將看到:

1、CppUnit原始碼的各個組成部分。

2、怎樣設定你的開發環境以能夠使用CppUnit。

3、怎樣為你的產品程式碼新增測試程式碼(實際上應該反過來,為測試程式碼新增產品程式碼。在TDD中,先有測試程式碼後有產品程式碼),並通過CppUnit來進行測試。

本文敘述背景為:CppUnit1.9.0, Visual C++ 6.0, Windows2000。文中敘述有誤之處,敬請批評指正。

一、CppUnit原始碼組成

CppUnit測試框架的原始碼可以到 http://sourceforge.net/projects/cppunit/ 上下載。下載解壓後,你將看到如下資料夾:

圖一

主要的資料夾有:

doc: CppUnit的說明文件。另外,程式碼的根目錄,還有三個說明文件,分別是INSTALL,INSTALL-unix,INSTALL-WIN32.txt。

examples: CpppUnit提供的例子,也是對CppUnit自身的測試,通過它可以學習如何使用CppUnit測試框架進行開發。

include: CppUnit標頭檔案。

src: CppUnit原始碼目錄。

二、初識CppUnit測試環境

解壓原始碼包後,您一定急著想看看CppUnit到底是個什麼樣?Ok,下面我們就來揭開CppUnit的神祕面紗:

1、進入example資料夾,用VC開啟examples.dsw。我們先來看看CppUnit自帶的測試例子。這些例子都是針對CppUnit自身的單元測試集,一方面這是CppUnit作者開發CppUnit框架過程中寫的測試用例,另一方面,我們可以通過這些例子來學習如何在我們自己的工程中新增測試用例。

2、將CppUnitTestApp工程設為Active Project(Win32 Debug),編譯後執行,則可以看到CppUnit的基於GUI方式進行單元測試TestRunner的介面。點選“Run”,將會看到如圖二所示介面:

圖二

這是一個針對CppUnit的單元測試結果,它表明剛才我們做了11個測試,全部通過。

點選“Browse”,我們還可以選擇想要進行的單元測試,如圖三:

圖三

CppUnit將所有的單元測試按照樹的結構來表示。在CppUnit中,最小的測試單元,稱為TestMethod測試方法,而多個相關的測試方法又可以組成一個TestCase測試用例。多個測試用例又組成TestSuite測試包。測試包互相巢狀在一起,就形成了上面我們看到的樹結構。我們可以選擇其中任意的樹節點來進行單元測試。

3、將CppUnitTestMain工程設定為Active Project(Win32 Debug),編譯並執行,我們來看看另一個單元測試的環境,如圖四:

圖四

這是一個基於文字方式的單元測試環境。CppUnit提供了幾種測試環境,一種基於文字,一種基於GUI,即圖三。

4、將HostApp工程設定為Active Project(Win32 Debug),編譯執行。如圖五:

圖五

這亦是一個對CppUnit自身進行的測試,只不過它向我們演示的是各種失敗的測試。在基於GUI的測試環境中,若測試不成功,進度條顯示紅色,反之則為綠色。從測試結果我們可以看到失敗的單元測試名稱,引起測試不能通過的原因,以及測試失敗的語句所在的檔案及所在行數。

三、CppUnit開發環境設定

認識了CppUnit的測試環境,想必你已經是在磨拳擦掌,準備在你的開發過程中感受一下測試驅動開發的感覺了。不過,在使用CppUnit前,還需要設定一下你的開發環境。

1、CppUnit的lib和dll

CppUnit為我們提供了兩套框架庫,一個為靜態的lib,一個為動態的dll。

cppunit project:靜態lib

cppunit_dll project:動態dll和lib

在開發中我們可以根據實際情況作出選擇。進入src資料夾,開啟CppUnitLibraries.dsw。分別編譯這兩個project,輸出位置均為lib資料夾。

另外一個需要關注的project是TestRunner,它輸出一個dll,提供了一個基於GUI 方式的測試環境,即前面我們提到的兩種測試環境之一。我們也需要編譯這個project,輸出位置亦為lib資料夾。

為了方便開發,我們把這些編譯出來的lib和dll(包括Debug版和Release版) copy 到我們自己建立的一個資料夾中(當然你也可以不這麼做),例如F:\cppunit1.9.0\lib\,同時我們也把CppUnit原始碼中include資料夾copy到我們自己的include資料夾下。然後在VC的tools/options/directories/include files和library files中設定include路徑和lib路徑。最後別忘了在你的project中link正確的lib。

2、在你的VC project中開啟RTTI開關。

具體位置Project Settings/C++/C++ Language。

3、為TestRunner.dll設定環境變數

TestRunner.dll為我們提供了基於GUI的測試環境。為了讓我們的測試程式能正確的呼叫它,TestRunner.dll必須位於你的測試程式的路徑下。但最簡單的方法是在作業系統的環境變數Path中添TestRunner.dll的路徑,這樣是最省事的。

四、你的第一個TDD example

一切準備就緒,現在我們可以來看看怎樣新增測試程式碼了。前面我們提到過,CppUnit最小的測試單位是TestCase,多個相關TestCase組成一個TestSuite。要新增測試程式碼最簡單的方法就是利用CppUnit為我們提供的幾個巨集來進行(當然還有其他的手工加入方法,但均是殊途同歸,大家可以查閱CppUnit標頭檔案中的演示程式碼)。這幾個巨集是:

1.CPPUNIT_TEST_SUITE() 開始建立一個TestSuite
2.CPPUNIT_TEST() 新增TestCase
3.CPPUNIT_TEST_SUITE_END() 結束建立TestSuite
4.CPPUNIT_TEST_SUITE_NAMED_REGISTRATION() 新增一個TestSuite到一個指定的TestFactoryRegistry工廠

感興趣的朋友可以在HelperMacros.h看看這幾個巨集的宣告,本文在此不做詳述。

1、一個實現兩個整數相加的類

假定我們要實現一個類,類名暫且取做CPlus,它的功能主要是實現兩個數相加(多簡單的一個類啊,這也要測試嗎?不要緊,我們只是瞭解怎樣加入測試程式碼來測試它就行了,所以越簡單越好)。 假定這個類要實現的相加的方法是:

1.int Add(int nNum1, int nNum2);

Ok,那我們先來寫測試這個方法的程式碼吧。TDD 可是先寫測試程式碼,後寫產品程式碼(CPlus)的哦!先寫的測試程式碼往往是不能執行或編譯的,我們的目標是在寫好測試程式碼後寫產品程式碼,使之編譯通過,然後再進行重構。這就是Kent Beck說的“red/green/refactor”( 還記得基於GUI的測試環境的狀態條嗎?)。所以,上面的類名和方法應該還只是在你的心裡,還只是你的idea而已。

2、在VC中為測試程式碼建立一個 Project

通常,測試程式碼和被測試物件是處於不同的Project中的。這樣就不會讓你的產品程式碼被測試程式碼所“汙染 ”。

在本例中,我們將建立一個基於GUI 方式的測試環境。在VC中,我們建立一個基於對話方塊的Project。別忘了link正確的lib,本例中我們使用靜態的CppUnit lib。由於我們希望這個Project執行後顯示的是圖2這樣的介面,所以我們需要在App的 Instance()中遮蔽掉原有的對話方塊,代之以CppUnit的GUI。

01.CppUnit::MfcUi::TestRunner runner;
02.runner.addTest(PlusTest::suite()); //新增測試
03.runner.run(); //show UI
04./*
05.CCPlusTestDlg dlg;
06.m_pMainWnd = &dlg;
07.int nResponse = dlg.DoModal();
08.if (nResponse == IDOK)
09.{
10.// TODO: Place code here to handle when the dialog is
11.// dismissed with OK
12.}
13.else if (nResponse == IDCANCEL)
14.{
15.// TODO: Place code here to handle when the dialog is
16.// dismissed with Cancel
17.}
18.*/

前面我們提到過,TestRunner輸出圖2這樣的對話方塊,這也是前面我們為什麼要為TestRunner.dll的路徑設定環境變數的原因。 
注意:PlusTest::suite()返回一個指向CppUnit::Test的指標.這個指標就是整個測試的起點。CppUnit::TestFactoryRegistry::getRegistry()根據TestSuite的名字返回TestFactoryRegistry工廠,然後呼叫工廠裡的makeTest()對TestSuite進行組裝,這是個遞迴呼叫,將建立起一個樹狀的測試結構。

01.namespace PlusTest
02.{
03.CppUnit::Test* suite()
04.{
05.CppUnit::TestFactoryRegistry ®istry =
06.CppUnit::TestFactoryRegistry::getRegistry(plusSuiteName());
07.return registry.makeTest();
08.}
09.}

另外別忘加標頭檔案:

1.#include "CPlusTestSuite.h"
2.#include < cppunit/ui/mfc/TestRunner.h>
3.#include < cppunit/extensions/TestFactoryRegistry.h>

3、在Project中加入一個類,取名CPlusTestCase

CPlusTestCase從CppUnit::TestCase繼承,程式碼如下:

01.class CPlusTestCase : public CppUnit::TestCase
02.{
03.CPPUNIT_TEST_SUITE(CPlusTestCase);
04.CPPUNIT_TEST(testAdd);
05.CPPUNIT_TEST_SUITE_END();
06.public:
07.CPlusTestCase();
08.virtual ~CPlusTestCase();
09.void testAdd(); //測試方法
10.};

看到這幾個巨集了嗎?它們可是在這大顯身手了一把。

1.CPPUNIT_TEST_SUITE(CPlusTestCase);
2.CPPUNIT_TEST( testAdd );
3.CPPUNIT_TEST_SUITE_END();

通過這幾個巨集,我們就把CPlusTestCase和testAdd註冊到了測試列表當中。

另外,我們需要在Cpp檔案中加入另外一個巨集:

1.CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(CPlusTestCase,PlusTest::plusSuiteName() );

它將CPlusTestCase這個TestSuite註冊到一個指定的TestFactory工廠中,這個TestSuite用 PlusTest::plusSuiteName()函式返回的名字來標識(前面介紹的suite()函式中就是通過這個名字來獲取這個工廠的)。plusSuiteName()是PlusTest這個namespace下的一個函式,它返回我們為這個TestSuite建立的名字(本例我們取名為“plus”)。其實我們也可以不用這麼做,直接在巨集裡寫入“plus“即可。但是這樣可以防止硬編碼帶來的麻煩。

在測試類中,我們新增了一個測試方法:

1.void testAdd();

它測試的物件是前面提到的CPlus類的方法:

1.int Add(int nNum1, int nNum2);

我們來看看它的實現:

1.void CPlusTestCase::testAdd()
2.{
3.CPlus plus;
4.int nResult = plus.Add(10, 20); //執行Add操作
5.CPPUNIT_ASSERT_EQUAL(30, nResult); //檢查結果是否等於30
6.}

CPPUNIT_ASSERT_EQUAL是一個判斷結果的巨集。CppUnit中類似的其它巨集請查閱TestAssert.h,本文在此不做詳述 。

另外,我們還可以覆寫基類的 setUp()、tearDown()兩個函式。這兩個函式實際上是一個模板方法,在測試執行之前會呼叫setUp()以進行一些初始化的工作,測試結束之後又會呼叫tearDown()來做一些“善後工作” ,比如資源的回收等等。當然,你也可以不覆寫這兩個函式,因為它們在基類裡定義成了空方法,而不是純虛擬函式。另外,Cpp中要加入標頭檔案:

1.#include "plusSuite.h"

4、根據測試程式碼編寫產品程式碼

編寫完上面的測試程式碼後,進行編譯。編譯肯定通不過,編譯器會告訴我們CPlus類沒有宣告,因為我們還沒有實現CPlus類呢!現在的工作就是馬上實現CPlus類,讓編譯通過。現在你應該嗅到一點“測試驅動“的味道了吧?

在VC中建立一個MFC Extension Dll的Project,在這個Project 中加入類CPlus,它的宣告如下:

1.class AFX_EXT_CLASS CPlus
2.{
3.public:
4.CPlus();
5.virtual ~CPlus(); public:
6.int Add(int nNum1, int nNum2);
7.};

僅有一個方法,就是我們的測試程式碼要測試的那個方法。來看看它的實現:

1.int CPlus::Add(int nNum1, int nNum2)
2.{
3.return nNum1+nNum2;
4.}

非常簡單,不是嗎?現在讓前面那個包含測試程式碼的Project dependent這個Project,include 相關標頭檔案 ,Rebuild All,你會發現編譯已通過。你體會到了測試程式碼驅動產品程式碼了嗎?當然我們的這個例子還很簡單 ,沒有重構這一步驟。

執行我們的測試程式,你就會看到如圖六的介面:

圖六

單擊”Browse”, 如圖七:

圖七

這下你應該對前面我們說的TestSuite的名字理解更深了吧。plus是一個測試包TestSuite,它的下面包含一個測試用例,這個測試用例下面又包含一個測試方法。

至此,我們對CppUnit測試框架的應用作了一個詳細的介紹,希望能對你在進行TDD過程中有所幫助。

參考資料:

CppUnit原始碼及說明文件

Website:http://tdd.nease.net

Email:cpluser@hotmail.com

Blog:http://blog.csdn.net/cpluser/

相關文章