fit測試主要目的?
在軟件開發(fā)的生命周期中,每個人都對質(zhì)量負有責(zé)任。理想情況下,開發(fā)人員在開發(fā)周期中,用像 Junit 和 TestNG 這樣的測試工具保證早期質(zhì)量,而質(zhì)量保證團隊用功能性系統(tǒng)測試在周期末端跟進,使用像 Selenium 這樣的工具。但是即使擁有優(yōu)秀的質(zhì)量保證,有些應(yīng)用程序在交付的時候仍然被認為是質(zhì)量低下的。為什么呢?因為它們并沒有做它們應(yīng)當(dāng)做的事。
在客戶、(編寫應(yīng)用程序需求的)業(yè)務(wù)部門和(實現(xiàn)需求的)開發(fā)團隊之間的溝通錯誤,通常是摩擦的原因,有時還是開發(fā)項目徹底失敗的常見原因。幸運的是,存在一些方法可以幫助需求作者和實現(xiàn)者之間盡早 溝通。
FIT 化的解決方案
集成測試框架 (FIT)是一個測試平臺,可以幫助需求編寫人員和把需求變成可執(zhí)行代碼的人員之間的溝通。使用 FIT,需求被做成表格模型,充當(dāng)開發(fā)人員編寫的測試的數(shù)據(jù)模型。表格本身充當(dāng)輸入和測試的預(yù)期輸出。
下載 FIT
集成測試框架(FIT)最初是由 Ward Cunningham 創(chuàng)建的,他就是 wiki 的發(fā)明人。請訪問 Cunningham 的 Web 站點了解關(guān)于 FIT 的更多知識并 免費下載它。
圖 1 顯示了用 FIT 創(chuàng)建的結(jié)構(gòu)化模型。第一行是測試名稱,下一行的三列是與輸入(value1 和 value2)和預(yù)期結(jié)果(trend())有關(guān)的標題。
圖 1. 用 FIT 創(chuàng)建的結(jié)構(gòu)化模型
好消息是,對于編程沒有經(jīng)驗的人也能編寫這個表格。FIT 的設(shè)計目的就是讓消費者或業(yè)務(wù)團隊在開發(fā)周期中,盡早與實現(xiàn)他們想法的開發(fā)人員協(xié)作。創(chuàng)建應(yīng)用程序需求的簡單表格式模型,可以讓每個人清楚地看出代碼和需求是否是一致的。
清單 1 是與圖 1 的數(shù)據(jù)模型對應(yīng)的 FIT 代碼。不要太多地擔(dān)心細節(jié) —— 只要注意代碼有多么簡單,而且代碼中沒有包含驗證邏輯(例如,斷言等)。可能還會注意到一些與表 1 中的內(nèi)容匹配的變量和方法名稱;關(guān)于這方面的內(nèi)容后面介紹。
清單 1. 根據(jù) FIT 模型編寫的代碼
package test.com.acme.fit.impl;import com.acme.sedlp.trend.Trender;
import fit.ColumnFixture;
public class TrendIndicatorextends ColumnFixture {
public double value1;
public double value2;
public String trend(){
return Trender.determineTrend(value1, value2).getName();
}
}
清單 1 中的代碼由研究上面表格并插入適當(dāng)代碼的開發(fā)人員編寫。最后,把所有東西合在一起,F(xiàn)IT 框架讀取表 1 的數(shù)據(jù),調(diào)用對應(yīng)的代碼,并確定結(jié)果。
FIT 和 JUnit
FIT 的優(yōu)美之處在于,它讓組織的消費者或業(yè)務(wù)端能夠盡早參與測試過程(例如,在開發(fā)期間)。JUnit 的力量在于編碼過程中的單元測試,而 FIT 是更高層次的測試工具,用來判斷規(guī)劃的需求實現(xiàn)的正確性。
例如,雖然 JUnit 擅長驗證兩個 Money 對象的合計與它們的兩個值的合計相同,但 FIT 可以驗證總的訂單價格是其中商品的價格減去任何相關(guān)折扣之后的合計。區(qū)別雖然細微,但的確重大!在 JUnit 示例中,要處理具體的對象(或者需求的實現(xiàn)),但是使用 FIT 時要處理的是高級的業(yè)務(wù)過程。
這很有意義,因為編寫需求的人通常不太考慮 Money 對象 —— 實際上,他們可能根本不知道這類東西的存在!但是,他們確實要考慮,當(dāng)商品被添加到訂單時,總的訂單價格應(yīng)當(dāng)是商品的價格減去所有折扣。
FIT 和 JUnit 之間絕不是競爭關(guān)系,它們是保證代碼質(zhì)量的好搭檔,正如在后面的 案例研究中將要看到的。
測試用的 FIT 表格
表格是 FIT 的核心。有幾種不同類型的表格(用于不同的業(yè)務(wù)場景),F(xiàn)IT 用戶可以用不同的格式編寫表格。用 HTML 編寫表格甚至用 Microsoft Excel 編寫都是可以的,如圖 2 所示:
圖 2. 用 Microsoft Excel 編寫的表格
也有可能用 Microsoft Word 這樣的工具編寫表格,然后用 HTML 格式保存,如圖 3 所示:
圖 3. 用 Microsoft Word 編寫的表格
開發(fā)人員編寫的用來執(zhí)行表格數(shù)據(jù)的代碼叫作裝備(fixture)。要創(chuàng)建一個裝備類型,必須擴展對應(yīng)的 FIT 裝備,它映射到對應(yīng)的表。如前所述,不同類型的表映射到不同的業(yè)務(wù)場景。
用裝備進行裝配
最簡單的表和裝備組合,也是 FIT 中最常用的,是一個簡單的列表格,其中的列映射到預(yù)期過程的輸入和輸出。對應(yīng)的裝備類型是 ColumnFixture。
如果再次查看 清單 1,將注意到 TrendIndicator 類擴展了 ColumnFixture,而且也與圖 3 對應(yīng)。請注意在圖 3 中,第一行的名稱匹配完全限定名稱(test.com.acme.fit.impl.TrendIndicator)。下一行有三列。頭兩個單元格的值匹配 TrendIndicator 類的 public 實例成員(value1 和 value2),最后一個單元格的值只匹配 TrendIndicator 中的方法(trend)。
現(xiàn)在來看清單 1 中的 trend 方法。它返回一個 String 值。可以猜測得到,對于表中每個剩下的行,F(xiàn)IT 都會替換值并比較結(jié)果。在這個示例中,有三個 “數(shù)據(jù)” 行,所以 FIT 運行 TrendIndicator 裝備三次。第一次,value1 被設(shè)置成 84.0,value2 設(shè)置成 71.2。然后 FIT 調(diào)用 trend 方法,并把從方法得到的值與表中的值比較,應(yīng)當(dāng)是 “decreasing”。
通過這種方式,F(xiàn)IT 用裝備代碼測試 Trender 類,每次 FIT 執(zhí)行 trend 方法時,都執(zhí)行類的 determineTrend 方法。當(dāng)代碼測試完成時,F(xiàn)IT 生成如圖 4 所示的報告:
圖 4. FIT 報告 trend 測試的結(jié)果
trend 列單元格的綠色表明測試通過(例如,F(xiàn)IT 設(shè)置 value1為 84.0,value2 為 71.2,調(diào)用 trend 得到返回值 “decreasing”)。
查看 FIT 運行
可以通過命令行,用 Ant 任務(wù)并通過 Maven 調(diào)用 FIT,從而簡單地把 FIT 測試插入構(gòu)建過程。因為自動進行 FIT 測試,就像 JUnit 測試一樣,所以也可以定期運行它們,例如在持續(xù)集成系統(tǒng)中。
最簡單的命令行運行器,如清單 2 所示,是 FIT 的 FolderRunner,它接受兩個參數(shù) —— 一個是 FIT 表格的位置,一個是結(jié)果寫入的位置。不要忘記配置類路徑!
清單 2. FIT 的命令行
%>java fit.runner.FolderRunner ./test/fit ./target/
FIT 通過插件,還可以很好地與 Maven 一起工作,如清單 3 所示。只要下載插件,運行 fit:fit命令,就 OK 了!(請參閱 參考資料 獲得 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, 0exceptions
BUILD SUCCESSFUL
Total time: 4 seconds
Finished at: Thu Feb 02 17:19:30 EST 2006
試用 FIT:案例研究
現(xiàn)在已經(jīng)了解了 FIT 的基礎(chǔ)知識,我們來做一個練習(xí)。如果還沒有 下載 FIT,現(xiàn)在是下載它的時候了!如前所述,這個案例研究顯示出可以容易地把 FIT 和 JUnit 測試組合在一起,形成多層質(zhì)量保證。
假設(shè)現(xiàn)在要為一個釀酒廠構(gòu)建一個訂單處理系統(tǒng)。釀酒廠銷售各種類型的酒類,但是它們可以組織成兩大類:季節(jié)性的和全年性的。因為釀酒廠以批發(fā)方式運作,所以酒類銷售都是按桶銷售的。對于零售商來說,購買多桶酒的好處就是折扣,而具體的折扣根據(jù)購買的桶數(shù)和酒是季節(jié)性還是全年性的而不同。
麻煩的地方在于管理這些需求。例如,如果零售店購買了 50 桶季節(jié)性酒,就沒有折扣;但是如果這 50 桶不是 季節(jié)性的,那么就有 12% 的折扣。如果零售店購買100 桶季節(jié)性酒,那就有折扣,但是只有 5%。100 桶更陳的非季節(jié)性酒的折扣達到 17%。購買量達到 200 時,也有類似的規(guī)矩。
對于開發(fā)人員,像這樣的需求集可能讓人摸不著頭腦。但是請看,我們的啤酒-釀造行業(yè)分析師用 FIT 表可以很容易地描述出這個需求,如圖 5 所示:
圖 5. 我的業(yè)務(wù)需求非常清晰:
表格語義
這個表格從業(yè)務(wù)的角度來說很有意義,它確實很好地規(guī)劃出需求。但是作為開發(fā)人員,還需要對表格的語言了解更多一些,以便從表格得到值。首先,也是最重要的,表格中的初始行說明表格的名稱,它恰好與一個匹配的類對應(yīng)(org.acme.store.discount.DiscountStructureFIT)。命名要求表格作者和開發(fā)人員之間的一些協(xié)調(diào)。至少,需要指定完全限定的表格名稱(也就是說,必須包含包名,因為 FIT 要動態(tài)地裝入對應(yīng)的類)。
請注意表格的名稱以 FIT 結(jié)束。第一個傾向可能是用 Test結(jié)束它,但要是這么做,那么在自動環(huán)境中運行 FIT 測試和 JUnit 測試時,會與 JUnit 產(chǎn)生些沖突,JUnit 的類通常通過命名模式查找,所以最好避免用 Test 開始或結(jié)束 FIT 表格名稱。
下一行包含五列。每個單元格中的字符串都特意用斜體格式,這是 FIT 的要求。前面學(xué)過,單元格名稱與裝備的實例成員和方法匹配。為了更簡潔,F(xiàn)IT 假設(shè)任何值以括號結(jié)束的單元格是方法,任何值不以括號結(jié)束的單元格是實例成員。
特殊智能
FIT 在處理單元格的值,進行與對應(yīng)裝備類的匹配時,采用智能解析。如 圖 5 所示,第二行單元格中的值是用普通的英文編寫的,例如 “number of cases”。FIT 試圖把這樣的字符串按照首字母大寫方式連接起來;例如,“number of cases” 變成 “numberOfCases”,然后 FIT 試圖找到對應(yīng)的裝備類。這個原則也適用于方法 —— 如圖 5 所示,“discount price()” 變成了 “discountPrice()”。
FIT 還會智能地猜測單元格中值的具體類型。例如,在 圖 5 余下的八行中,每一列都有對應(yīng)的類型,或者可以由 FIT 準確地猜出,或者要求一些定制編程。在這個示例中,圖 5 有三種不同類型。與 “number of cases” 關(guān)聯(lián)的列匹配到 int,而與 “is seasonal” 列關(guān)聯(lián)的值則匹配成 boolean。
剩下的三列,“l(fā)ist price per case”、“discount price()” 和 “discount amount()” 顯然代表當(dāng)前值。這幾列要求定制類型,我將把它叫作 Money。有了它之后,應(yīng)用程序就要求一個代表錢的對象,所以在我的 FIT 裝備中遵守少量語義就可以利用上這個對象!
FIT 語義總結(jié)
表 1 總結(jié)了命名單元格和對應(yīng)的裝備實例變量之間的關(guān)系:
表 1. 單元格到裝備的關(guān)系:實例變量
單元格值對應(yīng)的裝備實例變量類型list price per caselistPricePerCaseMoneynumber of casesnumberOfCasesintis seasonalisSeasonalboolean
表 2 總結(jié)了 FIT 命名單元格和對應(yīng)的裝備方法之間的關(guān)系:
表 2. 單元格到裝備的關(guān)系:方法
表格單元格的值對應(yīng)的裝備方法返回類型discount price()discountPriceMoneydiscount amount()discountAmountMoney
該構(gòu)建了!
要為釀酒廠構(gòu)建的訂單處理系統(tǒng)有三個主要對象:一個 PricingEngine 處理包含折扣的業(yè)務(wù)規(guī)則,一個 WholeSaleOrder 代表訂單,一個 Money 類型代表錢。
Money 類
第一個要編寫的類是 Money類,它有進行加、乘和減的方法。可以用 JUnit 測試新創(chuàng)建的類,如清單 14 所示:
清單 4. JUnit 的 MoneyTest 類
package org.acme.store;
import junit.framework.TestCase;
public class MoneyTest extendsTestCase {
public void testToString()throws Exception{
Money money = new Money(10.00);
Money total = money.mpy(10);
assertEquals("$100.00", total.toString());
}
public void testEquals() throwsException{
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 類型。這個新對象是應(yīng)用程序的核心:如果 WholeSaleOrder 類型配置了桶數(shù)、每桶價格和產(chǎn)品類型(季節(jié)性或全年性),就可以把它交給 PricingEngine,由后者確定對應(yīng)的折扣并相應(yīng)地在 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(doublediscount) {
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);
returntotalPrice.sub(this.getCalculatedPrice());
}
public int getNumberOfCases() {
return numberOfCases;
}
public void setNumberOfCases(intnumberOfCases) {
this.numberOfCases = numberOfCases;
}
public voidsetProductType(ProductType productType) {
this.productType = productType;
}
public String getProductType() {
return productType.getName();
}
public voidsetPricePerCase(Money pricePerCase) {
this.pricePerCase = pricePerCase;
}
public Money getPricePerCase() {
return pricePerCase;
}
}
從清單 5 中可以看到,一旦在 WholeSaleOrder 實例中設(shè)置了折扣,就可以通過分別調(diào)用 getCalculatedPrice 和 getDiscountedDifference 方法得到折扣價格和節(jié)省的錢。
更好地測試這些方法(用 JUnit)!
定義了 Money 和 WholesaleOrder 類之后,還要編寫 JUnit 測試來驗證 getCalculatedPrice 和 getDiscountedDifference 方法的功能。測試如清單 6 所示:
清單 6. JUnit 的 WholesaleOrderTest 類
packageorg.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
importorg.acme.store.discount.engine.WholesaleOrder;
public class WholesaleOrderTestextends TestCase {
/*
* Test method for 'WholesaleOrder.getCalculatedPrice()'
*/
public void testGetCalculatedPrice() {
WholesaleOrder order = newWholesaleOrder();
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 = newWholesaleOrder();
order.setDiscount(0.05);
order.setNumberOfCases(10);
order.setPricePerCase(new Money(10.00));
assertEquals("$5.00", order.getDiscountedDifference().toString());
}
}
PricingEngine 類
PricingEngine 類利用業(yè)務(wù)規(guī)則引擎,在這個示例中,是 Drools(請參閱 “關(guān)于 Drools”)。PricingEngine 極為簡單,只有一個 public 方法:applyDiscount。只要傳遞進一個 WholeSaleOrder 實例,引擎就會要求 Drools 應(yīng)用折扣,如清單 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 voidapplyDiscount(WholesaleOrder order) throws Exception{
loadRules();
WorkingMemory workingMemory = businessRules.newWorkingMemory( );
workingMemory.assertObject(order);
workingMemory.fireAllRules();
}
}
關(guān)于 Drools
Drools 是一個為 Java? 語言度身定制的規(guī)則引擎實現(xiàn)。它提供可插入的語言實現(xiàn),目前規(guī)則可以用 Java、Python 和 Groovy 編寫。要獲得更多信息,或者下載 Drools,請參閱 Drools 主頁。
Drools 的規(guī)則
必須在特定于 Drools 的 XML 文件中定義計算折扣的業(yè)務(wù)規(guī)則。例如,清單 8 中的代碼段就是一個規(guī)則:如果桶數(shù)大于 9,小于 50,不是季節(jié)性產(chǎn)品,則訂單有 5% 的折扣。
清單 8. BusinessRules.drl 文件的示例規(guī)則
<rule-setname="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 并定義了應(yīng)用程序規(guī)則之后,可能渴望驗證所有東西都工作正確。現(xiàn)在問題就變成,用 JUnit 還是 FIT?為什么不兩者都用呢?通過 JUnit 測試所有組合是可能的,但是要進行許多編碼。最好是用 JUnit 測試少數(shù)幾個值,迅速地驗證代碼在工作,然后依靠 FIT 的力量運行想要的組合。請看看當(dāng)我這么嘗試時發(fā)生了什么,從清單 9 開始:
清單 9. JUnit 迅速地驗證了代碼在工作
packageorg.acme.store.discount.engine.junit;
import junit.framework.TestCase;
import org.acme.store.Money;
importorg.acme.store.discount.engine.PricingEngine;
importorg.acme.store.discount.engine.ProductType;
importorg.acme.store.discount.engine.WholesaleOrder;
public class DiscountEngineTestextends TestCase {
public void testCalculateDiscount() throwsException{
WholesaleOrder order = newWholesaleOrder();
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 = newWholesaleOrder();
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 的 FIT 表格中有八行數(shù)據(jù)值。可能已經(jīng)在 清單 7 中編寫了前兩行的 JUnit 代碼,但是真的想編寫整個測試嗎?編寫全部八行的測試或者在客戶添加新規(guī)則時再添加新的測試,需要巨大的耐心。好消息就是,現(xiàn)在有了更容易的方法。不過,不是忽略測試 —— 而是用 FIT!
FIT 對于測試業(yè)務(wù)規(guī)則或涉及組合值的內(nèi)容來說非常漂亮。更好的是,其他人可以完成在表格中定義這些組合的工作。但是,在為表格創(chuàng)建 FIT 裝備之前,需要給 Money 類添加一個特殊方法。因為需要在 FIT 表格中代表當(dāng)前貨幣值(例如,像 $100.00 這樣的值),需要一種方法讓 FIT 能夠認識 Money 的實例。做這件事需要兩步:首先,必須把 static parse 方法添加到定制數(shù)據(jù)類型,如清單 10 所示:
清單 10. 添加 parse 方法到 Money 類
public static Money parse(String value){
return newMoney(Double.parseDouble(StringUtils.remove(value, '
Money 類的 parse 方法接受一個 String 值(例如,F(xiàn)IT 從表格中取出的值)并返回配置正確的 Money 實例。在這個示例中,$ 字符被刪除,剩下的 String 被轉(zhuǎn)變成 double,這與 Money 中現(xiàn)有的構(gòu)造函數(shù)匹配。
不要忘記向 MoneyTest 類添加一些測試來來驗證新添加的 parse 方法按預(yù)期要求工作。兩個新測試如清單 11 所示:
清單 11. 測試 Money 類的 parse 方法
public void testParse() throwsException{
Money money = Money.parse("$10.00");
assertEquals("$10.00", money.toString());
}
public void testEquals() throwsException{
Money money = Money.parse("$10.00");
Money control = new Money(10.00);
assertEquals(control, money);
}
編寫 FIT 裝備
現(xiàn)在可以編寫第一個 FIT 裝備了。實例成員和方法已經(jīng)在表 1 和表 2 中列出,所以只需要把事情串在一起,添加一兩個方法來處理定制類型:Money。為了在裝備中處理特定類型,還需要添加另一個 parse 方法。這個方法的簽名與前一個略有不同:這個方法是個對 Fixture 類進行覆蓋的實例方法,這個類是 ColumnFixture 的雙親。
請注意在清單 12 中,DiscountStructureFIT 的 parse方法如何比較 class 類型。如果存在匹配,就調(diào)用 Money 的定制 parse 方法;否則,就調(diào)用父類(Fixture)的 parse 版本。
清單 12 中剩下的代碼是很簡單的。對于圖 5 所示的 FIT 表格中的每個數(shù)據(jù)行,都設(shè)置值并調(diào)用方法,然后 FIT 驗證結(jié)果!例如,在 FIT 測試的第一次運行中,DiscountStructureFIT 的 listPricePerCase 被設(shè)為 $10.00,numberOfCases 設(shè)為 10,isSeasonal 為 true。然后執(zhí)行 DiscountStructureFIT 的 discountPrice,返回的值與 $100.00 比較,然后執(zhí)行 discountAmount,返回的值與 $0.00 比較。
清單 12. 用 FIT 進行的折扣測試
package org.acme.store.discount;
import org.acme.store.Money;
importorg.acme.store.discount.engine.PricingEngine;
importorg.acme.store.discount.engine.ProductType;
importorg.acme.store.discount.engine.WholesaleOrder;
import fit.ColumnFixture;
public class DiscountStructureFITextends ColumnFixture {
public Money listPricePerCase;
public int numberOfCases;
public boolean isSeasonal;
public Money discountPrice() throwsException {
WholesaleOrder order = this.doOrderCalculation();
return order.getCalculatedPrice();
}
public Money discountAmount() throwsException {
WholesaleOrder order = this.doOrderCalculation();
return order.getDiscountedDifference();
}
/**
* required by FIT for specific types
*/
public Object parse(String value, Classtype) throws Exception {
if (type == Money.class) {
return Money.parse(value);
} else {
return super.parse(value, type);
}
}
private WholesaleOrderdoOrderCalculation() throws Exception {
WholesaleOrder order = newWholesaleOrder();
order.setNumberOfCases(numberOfCases);
order.setPricePerCase(listPricePerCase);
if (isSeasonal) {
order.setProductType(ProductType.SEASONAL);
} else {
order.setProductType(ProductType.YEAR_ROUND);
}
PricingEngine.applyDiscount(order);
return order;
}
}
現(xiàn)在,比較 清單 9 的 JUnit 測試用例和清單 12。是不是清單 12 更有效率?當(dāng)然可以 用 JUnit 編寫所有必需的測試,但是 FIT 可以讓工作容易得多!如果感覺到滿意(應(yīng)當(dāng)是滿意的!),可以運行構(gòu)建,調(diào)用 FIT 運行器生成如圖 6 所示的結(jié)果: