例子如下:
int additionPropertiesTest()
{
//conmutative: a + b = b + a
if ( addition(1, 2) != addition(2, 1) )
{
return (FALSE);
}
//asociative: a + (b + c) = (a + b) + c
if ( addition(1, addition(2, 3)) != addition(addition(2, 1), 3 ) )
{
return (FALSE);
}
//neutral element: a + NEUTRAL = a
if ( addition(10, 0) != 10 )
{
return (FALSE);
}
//inverse element: a + INVERSE = NEUTRAL
if ( addition(10, -10) != 0 )
{
return (FALSE);
}
return (TRUE);
}
上面的例子測試了多個數據相加順序不同的情況。
上述的兩個Test Case組成了一個Test Suite,Test Suite是指用來測試同一被測單元的一組Test Case。
在開發(fā)被測模塊時必須同時編寫這些Test Case和Test Suite的代碼,被測模塊變更時,要同時變更(有時需要增加)相應的Test Case和Test Suite。
舉例來說,當求和模塊升級為可以對小數求和的模塊,必須變更Test Case和Test Suite,加入諸如addDecimalNumbersTest之類的Test Case。
極限編程建議程序員在編寫目標模塊之前開發(fā)出所有單元測試中要用到的Test Case。其主要理由是:一旦程序員處于開發(fā)過程之中,那么他進入了一個持續(xù)改進的階段,必須同時考慮單元模塊功能、需要公布的接口、需要給方法傳遞的參數、外部訪問、內部行為等等。在編寫目標單元之前通過開發(fā)Test Case,可以對需要考慮的這些因素有更好的了解,這樣編寫目標模塊與其他方法相比速度會更快,代碼的質量也會更好。
每當開發(fā)團隊需要發(fā)布新版本的時候,都要進行徹底的單元測試。所有的單元必須通過單元測試,這樣可以發(fā)布成功的版本。如果有1個或以上的單元沒有通過所有的測試,Bug出現了。遇到這種情況需要在進行測試,如果需要的話還需要增加新的Test Case,檢查可以使Bug再現的所有情況。如果新的Test Case可以使Bug重現,可以修正這個Bug,然后再進行測試,如果模塊通過了測試,可以認為Bug已經修正,可以發(fā)布新的無Bug版本了。
為每一個發(fā)現的Bug添加新的Test Case是很有必要的,因為Bug會反復出現,當其重復出現時需要有效的測試來檢測Bug。這樣的話,Test Bettery會逐漸膨脹直至覆蓋所有的歷史Bug和潛在的錯誤。
測試工具:
有兩個小伙子,一個叫Kent Beck,另一個叫Eric Gamma,他們寫了一系列的Java類,希望可以把測試做的盡可能自動化,并稱之為JUnit,JUnit使整個單元測試界產生的很大的震動。其他的開發(fā)者們把JUnit的代碼移植到其他語言上,構建了一大系列稱為xUnit框架的產品。其總包括C/C++的CUnit和CPPUnit,Delphi的DUnit,Visual Basic的VBUnit,.NET平臺上的NUnit,等等。
所有這些框架都采用同樣的規(guī)則,對語言的依賴性很小,熟悉其中一個框架能夠熟練應用其他框架。
下面要講的是如何通過使用CPPUnit來編寫測試代碼并提高單元的質量。
CPPUnit采用面向對象的編程方法,中間會遇到諸如封裝、繼承、多態(tài)這些概念。另外,CPPUnit采用C++ SEH(Structured Exception Handling),所以還會遇到異常的概念,以及throw, try, finally, catch這些指令。
CPPUnit
每一個Test Case都需要在TestCase類的派生類中定義。TestCase類中包含了許多基本的功能,比如運行測試、在Test Suite中注冊Test Case等。
比如在需要寫一個在磁盤上存儲數據的小模塊的時候,模塊(定義為DiskData類)主要實現兩個功能:讀取數據和裝載數據。例程如下:
typedef struct _DATA
{
int number;
char string[256];
}DATA, *LPDATA;
class DiskData
{
public:
DiskData();
~DiskData();
LPDATA getData();
void setData(LPDATA value);
bool load(char *filename);
bool store(char *filename);
private:
DATA m_data;
};
此時,首先要做的事情不是弄明白上面的代碼是如何變出來的,而是要確定上面所定義的類是否完成了設計的全部功能——正確地讀取和存儲數據。
為此,需要設計一個新的Test Suite,其中包含兩個Test Case:一個讀取數據、一個存儲數據。
使用CPPUnit
新版本的CPPUnit可以在http://cppunit.sourceforge.net/上免費下載到,其中包含所有的庫文件、文檔、例子程序和其他有趣的素材。
在Win32環(huán)境下,可以在VC++(6.0或更新版本)中使用CPPUnit,由于CPPUnit采用的是ANSI C++,所以可應用于C++ Builder等開發(fā)環(huán)境中的版本較少。
構建庫文件的步驟可以在CPPUnit發(fā)布版本的INSTALL-WIN32.txt文件中找到。構
建好庫文件之后可以著手編寫Test Suite了。
在VC++下編寫單元測試程序的步驟如下:
創(chuàng)建一個基于MFC的對話框應用程序(或者文檔應用程序)
開啟RTTI:Project Settings -> C++ -> C++ Language
在include目錄中加入CPPUnitinclude:Tools -> Options -> Directories -> Include
連接cppunitd.lib(靜態(tài)連接)或者cppunitd_dll.lib(動態(tài)連接),testrunnerd.lib。如果是在“Release”配置下編譯,同樣需要連接這些庫文件,只是需要把名稱中的“d”字母去掉。
拷貝testrunnerd.dll文件到可執(zhí)行文件夾的下面(或者路徑下的其他文件夾中),如果是動態(tài)連接的話,還需要拷貝cppunitd_dll.dll(“Release”配置下需要拷貝testrunner.dll和cppunit_dll.dll)。
配置好之后即可以著手進行單元測試類編碼了。
待測試的DiskData類,主要實現兩個功能:讀取和存儲磁盤上的數據。要測試這兩個功能,需要兩個Test Case:一個負責讀取數據、一個負責存儲數據。