C++Builder 2007系列1-如何使用TDD For C/C++
現在不管開發人員是使用什麼程式語言,測試驅動開發(TDD)已經成為許多開發人員不二的選擇,在測試驅動開發框架JUnit於Java開發界發行之後,xUnit系列很快的在各種不同的程式語言界中盛行,例如C#使用的NUnit,Delphi使用的DUnit等。即使是如最近的新星Ruby也有著支援良好的測試驅動開發框架。但奇怪的是對於C/C++這樣老牌的程式語言,測試驅動開發框架的支援卻一直無法像Java,C#或是Delphi那樣的自然又能夠整合在IDE中讓C/C++的開發人員能夠很直覺又舒服的使用。
但別誤會是C/C++沒有測試驅動開發框架,反之,目前有數個被使用的C/C++測試驅動開發框架,例如CppUnit,Boost.Test,Unit++和CxxTest等。筆者本身在BCB2007沒有推出之前就是使用CxxTest,雖然我個人覺得CxxTest是C/C++測試驅動開發框架最好用的,但是CxxTest仍然不是C/C++開發人員First-Class型態的C/C++測試驅動開發框架,它需要開發人員使用Perl或是Python轉換,又無法充分整合在筆者使用的IDE 中,因此不管是再熟練使用CxxTest,由於不是First-Class型態的C/C++測試驅動開發框架的原因,因此或多或少都拖慢了使用TDD For C/C++的開發速度。
C++Builder 2007對於底層開發功能的強化之一就是它終於提供了TDD For C/C++的開發能力,C++Builder 2007藉由在DUnit之外提供了充分的C/C++ Wrapper類別和表頭檔而讓C/C++的開發人員能夠很自然的使用xUnit系列的標準測試驅動開發框架,而且又充分的整合在C++Builder 2007的IDE中(後文開始使用CUnit來代表C++Builder 2007提供的測試驅動開發框架),終於讓C/C++的開發人員擁有了First-Class型態的C/C++測試驅動開發框架,現在C++Builder的開發人員終於不必再屈就於不標準的C/C++測試驅動開發框架,也不必羨慕Java/C#/Delphi開發人員能夠擁有整合在IDE中的測試驅動開發框架,CUnit可以大幅增加C++Builder開發人員使用TDD方式開發軟體的速度。
在下文中筆者將以一個簡單的範例來說明如何使用CUnit。
假設現在筆者需要撰寫一個C/C++類別來計算如下圖的汽車稅:
從上圖中我們可以看到汽車稅種類非常的繁雜,使用TDD開發方式可以幫助我們快速的測試我們的程式碼是否正確。
因此,首先我在C++Builder 2007中建立一個Package專案pkTDdDemo.bpl,並且在其中先建立一個TCarTax類別,其中TcarTax定義了一個Calculate方法,它最初的程式碼只提供上圖中『自用小客車』種類,1201CC到1800CC級距中『燃料稅』的金額4800元:
int TCarTax::Calculate(const int iCC)
{
int iResult = 0;
if (iCC >= 1201 && iCC <= 1800)
iResult = 4800;
return iResult;
}
一旦有了這個類別之後,我們可以立刻開始建立TDD專案,並且在TDD專案中建立測試案例(Test Case)來測試TCarTax::Calculate方法是否正確,如果一旦正確的話,那麼當我們繼續的加入更多的程式碼時,我們可以不斷的再執行測試案例以確保稍後加入的程式碼並沒有影響以前程式碼的正確性,也可以持續的增加新的測試案例以測試新加入的程式碼。
要建立TDD專案,讓我們先點選專案管理員中的專案群組節點,再點選滑鼠右鍵,選擇在專案群組中建立新的專案:
再選擇建立Unit Test|Test Project圖像建立TDD專案:
C++Builder 2007便會顯示如下的精靈詢問您有關TDD專案的細節,它會要求您建立TDD專案的名稱,例如在下面的精靈中我設定TDD專案的名稱為pkTDDDemoTestTCarTax。在Location中您可以選擇TDD專案儲存的目錄以及是否要把這個TDD專案加入目前的專案群組中:
點選Next之後,接著C++Builder 2007會詢問您使用的TDD測試界面為何,xUnit系列通常提供2種界面,GUI或Console模式,筆者習慣使用GUI模式,因此在下面的精靈中選擇GUI:
最後點選Finish即可在產生如下的專案群組:
有了TDD專案之後,現在我們就可以繼續在其中建立測試案例了。在pkTDDDemoTestTCarTax專案中點選File|New|Other…功能表,在Unit Test中現在便會出現Test Case圖像,選擇它:
再於下一個對話盒中選擇我們要測試的C/C++類別TCarTax.h,此時精靈便會掃瞄整個表頭檔中定義在public的函式,這時它只找到Calculate,因此精靈便自動選擇要為這個函式產生測試案例。如果表頭檔中定義了許多的public函式,精靈會全部顯示出來並且讓開發人員選擇要為那些函式產生測試案例:
點選Finish之後,C++Builder便會產生如下的骨架測試類別和程式碼。由於本文目的不是教導TDD,因此不再解釋TDD基本的觀念:
#include
#pragma hdrstop
#include
class TTestTCarTax : public TTestCase
{
public:
__fastcall virtual TTestTCarTax(AnsiString name) : TTestCase(name) {}
virtual void __fastcall SetUp();
virtual void __fastcall TearDown();
__published:
void __fastcall TestCalculate();
};
void __fastcall TTestTCarTax::SetUp()
{
}
void __fastcall TTestTCarTax::TearDown()
{
}
void __fastcall TTestTCarTax::TestCalculate()
{
// int Calculate(const int iCC)
}
static void registerTests()
{
_di_ITestSuite iSuite;
TTestSuite* testSuite = new TTestSuite("Testing TCarTax.h");
if (testSuite->GetInterface(iSuite)) {
testSuite->AddTests(__classid(TTestTCarTax));
Testframework::RegisterTest(iSuite);
} else {
delete testSuite;
}
}
現在先include TCarTax的表頭檔定義在TTestTCarTax中:
#include "..\TCarTax.h"
再於private中宣告一個物件變數:
private:
TCarTax *pTax;
再於SetUp和TearDown中分別建立和刪除物件變數:
void __fastcall TTestTCarTax::SetUp()
{
pTax = new TCarTax();
}
void __fastcall TTestTCarTax::TearDown()
{
delete pTax;
}
最後就是在TTestTCarTax::TestCalculate()中撰寫測試程式碼了,例如首先我們可以撰寫如下的程式碼:
void __fastcall TTestTCarTax::TestCalculate()
{
// int Calculate(const int iCC)
CheckEquals(6120, pTax->Calculate(2000));
}
我們根據圖1測試TCarTax物件是否能夠正確的計算2000CC汽車的燃料稅是6120元。
這時準備編譯pkTDDDemoTestTCarTax專案,先在專案管理員中加入TCarTax.cpp檔案以便可以連結到這個C/C++類別, 接著在IDE中執行它。我們就可以看到下面的TDD GUI程式出現中,點選上方綠色箭頭按鈕以執行測試TTestTCarTax::TestCalculate()的測試案例:
很快我們發現測試案例出錯了,它告訴我們TestCalculate期望回傳值是6120,但是實際的回傳值是0,所以這個測試案例不正確。為什麼? 原來我們還沒有撰寫2000CC汽車燃料稅的程式碼,目前只實作了1201~1800的汽車燃料稅,因此我們可以修改TestCalculate如下,:
void __fastcall TTestTCarTax::TestCalculate()
{
// int Calculate(const int iCC)
CheckEquals(4800, pTax->Calculate(1600));
}
再次執行測試:
測試案例就正確了。
現在我們可以回到TCarTax類別的Calculate方法,再加入更多的實作程式碼,例如:
int TCarTax::Calculate(const int iCC)
{
int iResult = 0;
if (iCC >= 1201 && iCC <= 1800)
iResult = 4800;
else
if (iCC >= 1801 && iCC <= 2400)
iResult = 6212;
return iResult;
}
然後在TTestTCarTax::TestCalculate()加入測試1801~2400CC的汽車燃料稅有沒有錯誤:
void __fastcall TTestTCarTax::TestCalculate()
{
// int Calculate(const int iCC)
CheckEquals(4800, pTax->Calculate(1600));
CheckEquals(6210, pTax->Calculate(2000));
}
如此一直使用撰寫實作程式碼,撰寫測試案例周而復始下去,我們可以一直測試後加入的程式碼是否有影響到以前程式碼的正確性。
好了,介紹到這裡我相信C++Builder的開發人員就瞭解如何使用C++Builder 2007中的TDD了。事實上如果讀者執行上照著上面步驟執行最後的TTestTCarTax::TestCalculate()測試案例是會產生失敗的,為什麼? 呵呵,因為我故意把實作程式碼寫錯了,留給讀者去改正好了,Have Fun!