# 追逐代碼質量: 決心采用 FIT
_試用 FIT 和 JUnit 進行需求測試工作!_
JUnit 假定測試的所有方面都是開發人員的地盤,而集成測試框架(FIT)在編寫需求的業務客戶和實現需求的開發人員之間做了協作方面的試驗。這是否意味著 FIT 和 JUnit 是競爭關系呢?絕對不是!代碼質量完美主義者 Andrew Glover 介紹了如何把 FIT 和 JUnit 兩者最好的地方結合在一起,實現更好的團隊工作和有效的端到端測試。
在軟件開發的生命周期中,_每個人都對質量負有責任_。理想情況下,開發人員在開發周期中,用像 Junit 和 TestNG 這樣的測試工具保證早期質量,而質量保證團隊用功能性系統測試在周期末端跟進,使用像 Selenium 這樣的工具。但是即使擁有優秀的質量保證,有些應用程序在交付的時候仍然被認為是質量低下的。為什么呢?_因為它們并沒有做它們應當做的事。_
在客戶、(編寫應用程序需求的)業務部門和(實現需求的)開發團隊之間的溝通錯誤,通常是摩擦的原因,有時還是開發項目徹底失敗的常見原因。幸運的是,存在一些方法可以幫助需求作者和實現者之間_盡早_ 溝通。
## 下載 FIT
集成測試框架(FIT)最初是由 Ward Cunningham 創建的,他就是 wiki 的發明人。請訪問 Cunningham 的 Web 站點了解關于 FIT 的更多知識并 [免費下載它](http://fit.c2.com/)。
## FIT 化的解決方案
_集成測試框架_ (FIT)是一個測試平臺,可以幫助需求編寫人員和把需求變成可執行代碼的人員之間的溝通。使用 FIT,需求被做成表格模型,充當開發人員編寫的測試的數據模型。表格本身充當輸入和測試的預期輸出。
圖 1 顯示了用 FIT 創建的結構化模型。第一行是測試名稱,下一行的三列是與輸入(`value1` 和 `value2`)和預期結果(`trend()`)有關的標題。
##### 圖 1\. 用 FIT 創建的結構化模型

好消息是,對于編程沒有經驗的人也能編寫這個表格。FIT 的設計目的就是讓消費者或業務團隊在開發周期中,盡早與實現他們想法的開發人員協作。創建應用程序需求的簡單表格式模型,可以讓每個人清楚地看出代碼和需求是否是一致的。
清單 1 是與圖 1 的數據模型對應的 FIT 代碼。不要太多地擔心細節 —— 只要注意代碼有多么簡單,而且代碼中沒有包含驗證邏輯(例如,斷言等)。可能還會注意到一些與表 1 中的內容匹配的變量和方法名稱;關于這方面的內容后面介紹。
##### 清單 1\. 根據 FIT 模型編寫的代碼
```
package test.com.acme.fit.impl;
import com.acme.sedlp.trend.Trender;
import fit.ColumnFixture;
public class TrendIndicator extends ColumnFixture {
public double value1;
public double value2;
public String trend(){
return Trender.determineTrend(value1, value2).getName();
}
}
```
清單 1 中的代碼由研究上面表格并插入適當代碼的開發人員編寫。最后,把所有東西合在一起,FIT 框架讀取表 1 的數據,調用對應的代碼,并確定結果。
* * *
## FIT 和 JUnit
FIT 的優美之處在于,它讓組織的消費者或業務端能夠盡早參與測試過程(例如,在開發期間)。JUnit 的力量在于編碼過程中的單元測試,而 FIT 是更高層次的測試工具,用來判斷規劃的需求實現的正確性。
例如,雖然 JUnit 擅長驗證兩個 `Money` 對象的合計與它們的兩個值的合計相同,但 FIT 可以驗證總的訂單價格是其中商品的價格減去任何相關折扣之后的合計。區別雖然細微,但的確重大!在 JUnit 示例中,要處理具體的對象(或者需求的實現),但是使用 FIT 時要處理的是高級的_業務過程_。
這很有意義,因為編寫需求的人通常不太考慮 `Money` 對象 —— 實際上,他們可能根本不知道這類東西的存在!但是,他們確實要考慮,當商品被添加到訂單時,總的訂單價格應當是商品的價格減去所有折扣。
FIT 和 JUnit 之間絕不是競爭關系,它們是保證代碼質量的好搭檔,正如在后面的 [案例研究](#case) 中將要看到的。
* * *
## 測試用的 FIT 表格
表格是 FIT 的核心。有幾種不同類型的表格(用于不同的業務場景),FIT 用戶可以用不同的格式編寫表格。用 HTML 編寫表格甚至用 Microsoft Excel 編寫都是可以的,如圖 2 所示:
##### 圖 2\. 用 Microsoft Excel 編寫的表格

也有可能用 Microsoft Word 這樣的工具編寫表格,然后用 HTML 格式保存,如圖 3 所示:
##### 圖 3\. 用 Microsoft Word 編寫的表格

開發人員編寫的用來執行表格數據的代碼叫作_裝備(fixture)_。要創建一個裝備類型,必須擴展對應的 FIT 裝備,它映射到對應的表。如前所述,不同類型的表映射到不同的業務場景。
* * *
## 用裝備進行裝配
最簡單的表和裝備組合,也是 FIT 中最常用的,是一個簡單的列表格,其中的列映射到預期過程的輸入和輸出。對應的裝備類型是 `ColumnFixture`。
如果再次查看 [清單 1](#code1),將注意到 `TrendIndicator` 類擴展了 `ColumnFixture`,而且也與圖 3 對應。請注意在圖 3 中,第一行的名稱匹配完全限定名稱(`test.com.acme.fit.impl.TrendIndicator`)。下一行有三列。頭兩個單元格的值匹配 `TrendIndicator` 類的 `public` 實例成員(`value1` 和 `value2`),最后一個單元格的值只匹配 `TrendIndicator` 中的方法(`trend`)。
現在來看清單 1 中的 `trend` 方法。它返回一個 `String` 值。可以猜測得到,對于表中每個剩下的行,FIT 都會替換值并比較結果。在這個示例中,有三個 “數據” 行,所以 FIT 運行 `TrendIndicator` 裝備三次。第一次,`value1` 被設置成 84.0,`value2` 設置成 71.2。然后 FIT 調用 `trend` 方法,并把從方法得到的值與表中的值比較,應當是 “decreasing”。
通過這種方式,FIT 用裝備代碼測試 `Trender` 類,每次 FIT 執行 `trend` 方法時,都執行類的 `determineTrend` 方法。當代碼測試完成時,FIT 生成如圖 4 所示的報告:
##### 圖 4\. FIT 報告 trend 測試的結果

trend 列單元格的綠色表明測試通過(例如,FIT 設置 `value1` 為 84.0,`value2` 為 71.2,調用 `trend` 得到返回值 “decreasing”)。
* * *
## 查看 FIT 運行
可以通過命令行,用 Ant 任務并通過 Maven 調用 FIT,從而簡單地把 FIT 測試插入構建過程。因為自動進行 FIT 測試,就像 JUnit 測試一樣,所以也可以定期運行它們,例如在持續集成系統中。
最簡單的命令行運行器,如清單 2 所示,是 FIT 的 `FolderRunner`,它接受兩個參數 —— 一個是 FIT 表格的位置,一個是結果寫入的位置。不要忘記配置類路徑!
##### 清單 2\. FIT 的命令行
```
%>java fit.runner.FolderRunner ./test/fit ./target/
```
FIT 通過插件,還可以很好地與 Maven 一起工作,如清單 3 所示。只要下載插件,運行 `fit:fit` 命令,就 OK 了!(請參閱 [參考資料](#resources) 獲得 Maven 插件。)
##### 清單 3\. Maven 得到 FIT
```
C:\dev\proj\edoa>maven fit:fit
__ __
| \/ |__ _Apache__ ___
| |\/| / _` \ V / -_) ' \ ~ intelligent projects ~
|_| |_\__,_|\_/\___|_||_| v. 1.0.2
build:start:
java:prepare-filesystem:
java:compile:
[echo] Compiling to C:\dev\proj\edoa/target/classes
java:jar-resources:
test:prepare-filesystem:
test:test-resources:
test:compile:
fit:fit:
[java] 2 right, 0 wrong, 0 ignored, 0 exceptions
BUILD SUCCESSFUL
Total time: 4 seconds
Finished at: Thu Feb 02 17:19:30 EST 2006
```
* * *
## 試用 FIT:案例研究
現在已經了解了 FIT 的基礎知識,我們來做一個練習。如果還沒有 [下載 FIT](http://fit.c2.com/),現在是下載它的時候了!如前所述,這個案例研究顯示出可以容易地把 FIT 和 JUnit 測試組合在一起,形成多層質量保證。
假設現在要為一個釀酒廠構建一個訂單處理系統。釀酒廠銷售各種類型的酒類,但是它們可以組織成兩大類:季節性的和全年性的。因為釀酒廠以批發方式運作,所以酒類銷售都是按桶銷售的。對于零售商來說,購買多桶酒的好處就是折扣,而具體的折扣根據購買的桶數和酒是季節性還是全年性的而不同。
麻煩的地方在于管理這些需求。例如,如果零售店購買了 50 桶季節性酒,就沒有折扣;但是如果這 50 桶_不是_ 季節性的,那么就有 12% 的折扣。如果零售店購買100 桶季節性酒,那就有折扣,但是只有 5%。100 桶更陳的非季節性酒的折扣達到 17%。購買量達到 200 時,也有類似的規矩。
對于開發人員,像這樣的需求集可能讓人摸不著頭腦。但是請看,我們的啤酒-釀造行業分析師用 FIT 表可以很容易地描述出這個需求,如圖 5 所示:
##### 圖 5\. 我的業務需求非常清晰!

### 表格語義
這個表格從業務的角度來說很有意義,它確實很好地規劃出需求。但是作為開發人員,還需要對表格的語言了解更多一些,以便從表格得到值。首先,也是最重要的,表格中的初始行說明表格的名稱,它恰好與一個匹配的類對應(`org.acme.store.discount.DiscountStructureFIT`)。命名要求表格作者和開發人員之間的一些協調。至少,需要指定完全限定的表格名稱(也就是說,必須包含包名,因為 FIT 要動態地裝入對應的類)。
請注意表格的名稱以 _FIT_ 結束。第一個傾向可能是用 _Test_ 結束它,但要是這么做,那么在自動環境中運行 FIT 測試和 JUnit 測試時,會與 JUnit 產生些沖突,JUnit 的類通常通過命名模式查找,所以最好避免用 _Test_ 開始或結束 FIT 表格名稱。
下一行包含五列。每個單元格中的字符串都特意用斜體格式,這是 FIT 的要求。前面學過,單元格名稱與裝備的實例成員和方法匹配。為了更簡潔,FIT 假設任何值以括號結束的單元格是方法,任何值不以括號結束的單元格是實例成員。
### 特殊智能
FIT 在處理單元格的值,進行與對應裝備類的匹配時,采用智能解析。如 [圖 5](#fig5) 所示,第二行單元格中的值是用普通的英文編寫的,例如 “number of cases”。FIT 試圖把這樣的字符串按照首字母大寫方式連接起來;例如,“number of cases” 變成 “`numberOfCases`”,然后 FIT 試圖找到對應的裝備類。這個原則也適用于方法 —— 如圖 5 所示,“discount price()” 變成了 “`discountPrice()`”。
FIT 還會智能地猜測單元格中值的具體_類型_。例如,在 [圖 5](#fig5) 余下的八行中,每一列都有對應的類型,或者可以由 FIT 準確地猜出,或者要求一些定制編程。在這個示例中,圖 5 有三種不同類型。與 “number of cases” 關聯的列匹配到 `int`,而與 “is seasonal” 列關聯的值則匹配成 `boolean`。
剩下的三列,“list price per case”、“discount price()” 和 “discount amount()” 顯然代表當前值。這幾列要求定制類型,我將把它叫作 `Money`。有了它之后,應用程序就要求一個代表錢的對象,所以在我的 FIT 裝備中遵守少量語義就可以利用上這個對象!
### FIT 語義總結
表 1 總結了命名單元格和對應的裝備實例變量之間的關系:
##### 表 1\. 單元格到裝備的關系:實例變量
| 單元格值 | 對應的裝備實例變量 | 類型 |
| --- | --- | --- |
| list price per case | `listPricePerCase` | `Money` |
| number of cases | `numberOfCases` | `int` |
| is seasonal | `isSeasonal` | `boolean` |
表 2 總結了 FIT 命名單元格和對應的裝備方法之間的關系:
##### 表 2\. 單元格到裝備的關系:方法
| 表格單元格的值 | 對應的裝備方法 | 返回類型 |
| --- | --- | --- |
| discount price() | `discountPrice` | `Money` |
| discount amount() | `discountAmount` | `Money` |
* * *
## 該構建了!
要為釀酒廠構建的訂單處理系統有三個主要對象:一個 `PricingEngine` 處理包含折扣的業務規則,一個 `WholeSaleOrder` 代表訂單,一個 `Money` 類型代表錢。
### Money 類
第一個要編寫的類是 `Money` 類,它有進行加、乘和減的方法。可以用 JUnit 測試新創建的類,如清單 14 所示:
##### 清單 4\. JUnit 的 MoneyTest 類
```
package org.acme.store;
import junit.framework.TestCase;
public class MoneyTest extends TestCase {
public void testToString() throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
assertEquals("$100.00", total.toString());
}
public void testEquals() throws Exception{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
public void testMultiply() throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
Money discountAmount = total.mpy(0.05);
assertEquals("$5.00", discountAmount.toString());
}
public void testSubtract() throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
Money discountAmount = total.mpy(0.05);
Money discountedPrice = total.sub(discountAmount);
assertEquals("$95.00", discountedPrice.toString());
}
}
```
### WholeSaleOrder 類
然后,定義 `WholeSaleOrder` 類型。這個新對象是應用程序的核心:如果 `WholeSaleOrder` 類型配置了桶數、每桶價格和產品類型(季節性或全年性),就可以把它交給 `PricingEngine`,由后者確定對應的折扣并相應地在 `WholeSaleOrder` 實例中配置它。
`WholesaleOrder` 類的定義如清單 5 所示:
##### 清單 5\. WholesaleOrder 類
```
package org.acme.store.discount.engine;
import org.acme.store.Money;
public class WholesaleOrder {
private int numberOfCases;
private ProductType productType;
private Money pricePerCase;
private double discount;
public double getDiscount() {
return discount;
}
public void setDiscount(double discount) {
this.discount = discount;
}
public Money getCalculatedPrice() {
Money totalPrice = this.pricePerCase.mpy(this.numberOfCases);
Money tmpPrice = totalPrice.mpy(this.discount);
return totalPrice.sub(tmpPrice);
}
public Money getDiscountedDifference() {
Money totalPrice = this.pricePerCase.mpy(this.numberOfCases);
return totalPrice.sub(this.getCalculatedPrice());
}
public int getNumberOfCases() {
return numberOfCases;
}
public void setNumberOfCases(int numberOfCases) {
this.numberOfCases = numberOfCases;
}
public void setProductType(ProductType productType) {
this.productType = productType;
}
public String getProductType() {
return productType.getName();
}
public void setPricePerCase(Money pricePerCase) {
this.pricePerCase = pricePerCase;
}
public Money getPricePerCase() {
return pricePerCase;
}
}
```
從清單 5 中可以看到,一旦在 `WholeSaleOrder` 實例中設置了折扣,就可以通過分別調用 `getCalculatedPrice` 和 `getDiscountedDifference` 方法得到折扣價格和節省的錢。
### 更好地測試這些方法(用 JUnit)!
定義了 `Money` 和 `WholesaleOrder` 類之后,還要編寫 JUnit 測試來驗證 `getCalculatedPrice` 和 `getDiscountedDifference` 方法的功能。測試如清單 6 所示:
##### 清單 6\. JUnit 的 WholesaleOrderTest 類
```
package org.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
import org.acme.store.discount.engine.WholesaleOrder;
public class WholesaleOrderTest extends TestCase {
/*
* Test method for 'WholesaleOrder.getCalculatedPrice()'
*/
public void testGetCalculatedPrice() {
WholesaleOrder order = new WholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$95.00", order.getCalculatedPrice().toString());
}
/*
* Test method for 'WholesaleOrder.getDiscountedDifference()'
*/
public void testGetDiscountedDifference() {
WholesaleOrder order = new WholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$5.00", order.getDiscountedDifference().toString());
}
}
```
### PricingEngine 類
`PricingEngine` 類利用_業務規則引擎_,在這個示例中,是 Drools(請參閱 “[關于 Drools](#sidebar2)”)。`PricingEngine` 極為簡單,只有一個 `public` 方法:`applyDiscount`。只要傳遞進一個 `WholeSaleOrder` 實例,引擎就會要求 Drools 應用折扣,如清單 7 所示:
##### 清單 7\. PricingEngine 類
```
package org.acme.store.discount.engine;
import org.drools.RuleBase;
import org.drools.WorkingMemory;
import org.drools.io.RuleBaseLoader;
public class PricingEngine {
private static final String RULES="BusinessRules.drl";
private static RuleBase businessRules;
private static void loadRules() throws Exception{
if (businessRules==null){
businessRules = RuleBaseLoader.
loadFromUrl(PricingEngine.class.getResource(RULES));
}
}
public static void applyDiscount(WholesaleOrder order) throws Exception{
loadRules();
WorkingMemory workingMemory = businessRules.newWorkingMemory( );
workingMemory.assertObject(order);
workingMemory.fireAllRules();
}
}
```
## 關于 Drools
Drools 是一個為 Java? 語言度身定制的規則引擎實現。它提供可插入的語言實現,目前規則可以用 Java、Python 和 Groovy 編寫。要獲得更多信息,或者下載 Drools,請參閱 [Drools 主頁](http://drools.codehaus.org/)。
### Drools 的規則
必須在特定于 Drools 的 XML 文件中定義計算折扣的業務規則。例如,清單 8 中的代碼段就是一個規則:如果桶數大于 9,小于 50,不是季節性產品,則訂單有 5% 的折扣。
##### 清單 8\. BusinessRules.drl 文件的示例規則
```
<rule-set name="BusinessRulesSample"
xmlns="http://drools.org/rules"
xmlns:java="http://drools.org/semantics/java"
xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xs:schemaLocation="http://drools.org/rules rules.xsd
http://drools.org/semantics/java java.xsd">
<rule name="1st Tier Discount">
<parameter identifier="order">
<class>WholesaleOrder</class>
</parameter>
<java:condition>order.getNumberOfCases() > 9 </java:condition>
<java:condition>order.getNumberOfCases() < 50 </java:condition>
<java:condition>order.getProductType() == "year-round"</java:condition>
<java:consequence>
order.setDiscount(0.05);
</java:consequence>
</rule>
</rule-set>
```
* * *
## 標記團隊測試
有了 `PricingEngine` 并定義了應用程序規則之后,可能渴望驗證所有東西都工作正確。現在問題就變成,用 JUnit 還是 FIT?為什么不兩者都用呢?通過 JUnit 測試所有組合是可能的,但是要進行許多編碼。最好是用 JUnit 測試少數幾個值,迅速地驗證代碼在工作,然后依靠 FIT 的力量運行想要的組合。請看看當我這么嘗試時發生了什么,從清單 9 開始:
##### 清單 9\. JUnit 迅速地驗證了代碼在工作
```
package org.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
import org.acme.store.discount.engine.PricingEngine;
import org.acme.store.discount.engine.ProductType;
import org.acme.store.discount.engine.WholesaleOrder;
public class DiscountEngineTest extends TestCase {
public void testCalculateDiscount() throws Exception{
WholesaleOrder order = new WholesaleOrder();
order.setNumberOfCases(20);
order.setPricePerCase(new Money(10.00));
order.setProductType(ProductType.YEAR_ROUND);
PricingEngine.applyDiscount(order);
assertEquals(0.05, order.getDiscount(), 0.0);
}
public void testCalculateDiscountNone() throws Exception{
WholesaleOrder order = new WholesaleOrder();
order.setNumberOfCases(20);
order.setPricePerCase(new Money(10.00));
order.setProductType(ProductType.SEASONAL);
PricingEngine.applyDiscount(order);
assertEquals(0.0, order.getDiscount(), 0.0);
}
}
```
### 還沒用 FIT?那就用 FIT!
在 [圖 5](#fig5) 的 FIT 表格中有八行數據值。可能已經在 [清單 7](#code7) 中編寫了前兩行的 JUnit 代碼,但是真的想編寫整個測試嗎?編寫全部八行的測試或者在客戶添加新規則時再添加新的測試,需要巨大的耐心。好消息就是,現在有了更容易的方法。不過,不是忽略測試 —— 而是用 FIT!
FIT 對于測試業務規則或涉及組合值的內容來說非常漂亮。更好的是,其他人可以完成在表格中定義這些組合的工作。但是,在為表格創建 FIT 裝備之前,需要給 `Money` 類添加一個特殊方法。因為需要在 FIT 表格中代表當前貨幣值(例如,像 $100.00 這樣的值),需要一種方法讓 FIT 能夠認識 `Money` 的實例。做這件事需要兩步:首先,必須把 `static parse` 方法添加到定制數據類型,如清單 10 所示:
##### 清單 10\. 添加 parse 方法到 Money 類
```
public static Money parse(String value){
return new Money(Double.parseDouble(StringUtils.remove(value, '$')));
}
```
`Money` 類的 `parse` 方法接受一個 `String` 值(例如,FIT 從表格中取出的值)并返回配置正確的 `Money` 實例。在這個示例中,`$` 字符被刪除,剩下的 `String` 被轉變成 `double`,這與 `Money` 中現有的構造函數匹配。
不要忘記向 `MoneyTest` 類添加一些測試來來驗證新添加的 `parse` 方法按預期要求工作。兩個新測試如清單 11 所示:
##### 清單 11\. 測試 Money 類的 parse 方法
```
public void testParse() throws Exception{
Money money = Money.parse("$10.00");
assertEquals("$10.00", money.toString());
}
public void testEquals() throws Exception{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
```
### 編寫 FIT 裝備
現在可以編寫第一個 FIT 裝備了。實例成員和方法已經在表 1 和表 2 中列出,所以只需要把事情串在一起,添加一兩個方法來處理定制類型:`Money`。為了在裝備中處理特定類型,還需要添加另一個 `parse` 方法。這個方法的簽名與前一個略有不同:這個方法是個對 `Fixture` 類進行覆蓋的實例方法,這個類是 `ColumnFixture` 的雙親。
請注意在清單 12 中,`DiscountStructureFIT` 的 `parse` 方法如何_比較_ `class` 類型。如果存在匹配,就調用 `Money` 的定制 `parse` 方法;否則,就調用父類(`Fixture`)的 `parse` 版本。
清單 12 中剩下的代碼是很簡單的。對于圖 5 所示的 FIT 表格中的每個數據行,都設置值并調用方法,然后 FIT 驗證結果!例如,在 FIT 測試的第一次運行中,`DiscountStructureFIT` 的 `listPricePerCase` 被設為 $10.00,`numberOfCases` 設為 10,`isSeasonal` 為 true。然后執行 `DiscountStructureFIT` 的 `discountPrice`,返回的值與 $100.00 比較,然后執行 `discountAmount`,返回的值與 $0.00 比較。
##### 清單 12\. 用 FIT 進行的折扣測試
```
package org.acme.store.discount;
import org.acme.store.Money;
import org.acme.store.discount.engine.PricingEngine;
import org.acme.store.discount.engine.ProductType;
import org.acme.store.discount.engine.WholesaleOrder;
import fit.ColumnFixture;
public class DiscountStructureFIT extends ColumnFixture {
public Money listPricePerCase;
public int numberOfCases;
public boolean isSeasonal;
public Money discountPrice() throws Exception {
WholesaleOrder order = this.doOrderCalculation();
return order.getCalculatedPrice();
}
public Money discountAmount() throws Exception {
WholesaleOrder order = this.doOrderCalculation();
return order.getDiscountedDifference();
}
/**
* required by FIT for specific types
*/
public Object parse(String value, Class type) throws Exception {
if (type == Money.class) {
return Money.parse(value);
} else {
return super.parse(value, type);
}
}
private WholesaleOrder doOrderCalculation() throws Exception {
WholesaleOrder order = new WholesaleOrder();
order.setNumberOfCases(numberOfCases);
order.setPricePerCase(listPricePerCase);
if (isSeasonal) {
order.setProductType(ProductType.SEASONAL);
} else {
order.setProductType(ProductType.YEAR_ROUND);
}
PricingEngine.applyDiscount(order);
return order;
}
}
```
現在,比較 [清單 9](#code9) 的 JUnit 測試用例和清單 12。是不是清單 12 更有效率?當然_可以_ 用 JUnit 編寫所有必需的測試,但是 FIT 可以讓工作容易得多!如果感覺到滿意(應當是滿意的!),可以運行構建,調用 FIT 運行器生成如圖 6 所示的結果:
##### 圖 6\. 這些結果真的很 FIT !

* * *
## 結束語
FIT 可以幫助企業避免客戶和開發人員之間的溝通不暢、誤解和誤讀。把編寫需求的人_盡早_ 帶入測試過程,是在問題成為開發惡夢的根源之前發現并修補它們的明顯途徑。而且,FIT 與現有的技術(比如 JUnit)完全兼容。實際上,正如本文所示,JUnit 和 FIT 互相補充。請把今年變成您_追逐代碼質量_ 的重要紀年 —— 由于決心采用 FIT!
- 追求代碼質量
- 追求代碼質量: 對 Ajax 應用程序進行單元測試
- 追求代碼質量: 使用 TestNG-Abbot 實現自動化 GUI 測試
- 追求代碼質量: 用 AOP 進行防御性編程
- 追求代碼質量: 探究 XMLUnit
- 追求代碼質量: 用 JUnitPerf 進行性能測試
- 追求代碼質量: 通過測試分類實現敏捷構建
- 追求代碼質量: 可重復的系統測試
- 追求代碼質量: JUnit 4 與 TestNG 的對比
- 追求代碼質量: 馴服復雜的冗長代碼
- 追求代碼質量: 用代碼度量進行重構
- 追求代碼質量: 軟件架構的代碼質量
- 讓開發自動化: 除掉構建腳本中的氣味
- 追逐代碼質量: 決心采用 FIT
- 追求代碼質量: 不要被覆蓋報告所迷惑