2012年12月23日

初嚐 JUnit

所謂測試驅動開發 (Test-Driven Development),就是要先寫測試再寫程式,透過單元測試的撰寫除了對需求規格和設計方式更加深入了解之外,更能確保程式寫完後可以馬上進行自動化測試。

Erich Gamma 和 Kent Beck 就替 Java 開發了一個 JUnit 框架提供單元測試程式的撰寫。 

下圖為此框架的核心:
 
來源: http://junit.sourceforge.net/doc/cookstour/cookstour.htm 

從此類別圖當中可以歸納出幾個有名的設計模式:

命令模式

以 Test 作為抽象介面,其中只有 run() 這個方法用來執行命令的動作,而繼承了 Test 介面的 TestCase 類別,將視為一個具體的命令,並實作了 run() 方法來執行測試操作。

樣板方法模式

然而,在 TestCase 類別當中的 run() 方法實際上是以setUp() -> runTest() -> tearDown() 的順序分別呼叫這三個方法的樣板方法,主要對測試動作做操作的是 runTest() 方法,會在 TestCase 類別當中由框架撰寫者安排好(如何安排待會再說),而框架使用者只須要繼承 TestCase 類別後,針對此單元測試操作之前(可能要初始化一個測試所需要用到的物件)和之後(在測試結束後要消滅測試所用到的物件)必須做處理的動作將之分別寫在 setUp() 和 tearDown() 這兩個掛勾方法當中就可以了。

合成模式

透過此模式可以隨心所欲的將實作了 Test 介面的 TestCase 和 TestSuite 給組合起來。TestSuite 類別當中有一個 Collection<test> 的成員,並可利用 add() 方法來將不同的 Test 組合起來,而其 run() 方法的實作方式就是將 Collection<test> 當中 Test 型態的物件以迭代的方式執行一遍。


從以上可以得知,Test 介面的 run() 方法分別會被 TestCase 和 TestSuite 這兩個繼承他的類別 Override 並實作,而 TestCase 是以樣板方法的方式實作,TestSuite 則是將組合好的 Test 以迭代的方式全部執行一遍,並且最終都是執行到 TestCase 的 run() 方法。

因此,所有的測試操作動作的都集中在 TestCase 的 run() 方法當中,而在此方法當中會執行測試操作動作的關鍵則落到了 runTest() 方法當中。此方法已由框架撰寫者安排好了,其使用的手法是以 Reflection 的方式找到與此 TestCase 的 fName 屬性同名的 public 方法後執行之,目的是為了減少過多的 Unit Test 使得類別越寫越多過度雜亂,透過如此方式便能在同一個 TestCase 當中寫多個 Unit Test(每個都以 testXXX 為命名方式,並以 public 宣告之),並由框架透過該 TestCase 物件的 fName 反射並執行之。