FIT(Framework for
Integrated Tests) 是一种通用的开放框架,是由Ward Cunningham开发的,可以帮助我们进行自动化的确认测试。自动化测试是轻型开发模式(XP、Crystal等)测试活动的另一个优秀思路也是采取轻型开发模式的必要条件之一。在只有测试实现了自动化,回归测试才能实现,重构(采取轻型开发模式另外的一个必要条件)才能够贯彻,而迭代也才能够进行。FIT利用JUnit并扩展了JUnit的测试功能。
长期以来,在软件开发中我们一直关心着两个主要问题:
第一,业务如何通过应用程序与其所需内容通信;
第二,工程师如何验证他们是否正在构建满足业务需要的正确软件。多年来,为了解决这些关心的问题,已探索了许多方法和框架,但直到出现
Framework for Integrated Tests (FIT) 以后,才找到了解决这些问题的简便而直观的方法。
使用FIT我们可以编写出可以自动运行的确认测试用例,可以用来确认我们所开发出来的软件是否满足了用户所需的功能,可以作为持续构建过程的一部分来确保所构建出来的版本是正确的。但是,FIT还有另外一个更为重要的功能,那就是在软件开发中增强协作,尤其是开发团队和客户、领域专家之间的协作。这种协作可以有效地降低软件开发中的不必要的复杂性,加速反馈,并确保最大程度地为客户提供最高的价值。
FIT如何工作
简单来讲,FIT就是一个软件,它能够读取HTML文件中的表格(这些表格可以通过MicroSoft Word或者Excel产生)。针对每个表格,都会由一个程序员编写的"fixture"(装置)来解释。该fixture会驱动“被测系统
(SUT?System Under Test)”来对表格中给出的测试用例进行检验。
Fixture充当Fit表格和要测试系统间的媒介,起协调作用,完成表格中给出的测试。FIT中提供了好几种类型的Fixture,它们分别用于处理不同的情形。Fixture的形式有3种:
ColumnFixture(对应于“列”表),“列”表的形式如下图所示:
CalculateScholarship
Score Scholarship()
1000 0
1999 0
2000 500
2050 500
2100 1000
2200 1500
2300 2000
2350 2000
2400 2500 |
RowFixture(对应于“行”表),“行”表的形式如下图所示:
DiscountGroupOrderedList
order future value max owing min purchase discount percent
1 low 0.00 0.00 0
2 low 0.00 2000.00 3
3 medium 500.00 600.00 3
4 medium 0.00 500.00 5
5 high 2000.00 2000.00 10 |
ActionFixture,表明以表格给出的测试用例的一系列的操作步骤。见表1。
表1
fit.ActionFixture
start cstc.fitexam.coffeemaker.AddInventory
enter units coffee 3
enter units milk 5
enter units sugar 6
enter units chocolate 7
check coffee inventory 18
check milk inventory 20
check sugar inventory 21
check chocolate inventory 22 |
在表1中,第1列给出了执行的命令,这里共有3个命令,但是其它的命令可以根据实际情况在ActionFixture.的子类中进行创建。上述的3个命令是:
Start:与该Fixture相关联的类的名称
Enter:该类的一个方法(带有一个变量)
Check:该类的一个方法的返回值(不带变量)
为该表格创建一个ActionFixture类如下:
package cstc.fitexam.coffeemaker;
import fit.ActionFixture;
public class AddInventory extends ActionFixture
{
private CoffeeMaker cm = new CoffeeMaker();
private Inventory i = cm.checkInventory();
public void unitsCoffee(int coffee) {
cm.addInventory(coffee,0,0,0);
}
public void unitsMilk(int milk) {
cm.addInventory(0,milk,0,0);
}
public void unitsSugar(int sugar) {
cm.addInventory(0,0,sugar,0);
}
public void unitsChocolate(int chocolate)
{
cm.addInventory(0,0,0,chocolate);
}
public int coffeeInventory() {
return i.getCoffee();
}
public int milkInventory() {
return i.getMilk();
}
public int sugarInventory() {
return i.getSugar();
}
public int chocolateInventory() {
return i.getChocolate();
}
} |
此 fixture 将调用下面清单中显示的 SUT(被测对象) CoffeeMaker 类,然后调用该类上的相关方法。
package cstc.fitexam.ffeemaker;
public class CoffeeMaker {
/**
* Array of recipes in coffee maker
*/
private Recipe [] recipeArray;
/** Number of recipes in coffee maker */
private final int NUM_RECIPES = 4;
/** Array describing if the array is full */
private boolean [] recipeFull;
/** Inventory of the coffee maker */
private Inventory inventory;
/**
* Constructor for the coffee maker
*
*/
public CoffeeMaker() {
recipeArray = new Recipe[NUM_RECIPES];
recipeFull = new boolean[NUM_RECIPES];
for(int i = 0; i < NUM_RECIPES; i++) {
recipeArray[i] = new Recipe();
recipeFull[i] = false;
}
inventory = new Inventory();
}
/**
* Returns true if a recipe is successfully added to the
* coffee maker
* @param r
* @return boolean
*/
public boolean addRecipe(Recipe r) {
boolean canAddRecipe = true;
//Check if the recipe already exists
for(int i = 0; i < NUM_RECIPES; i++) {
if(r.equals(recipeArray[i])) {
canAddRecipe = false;
}
}
//Check for an empty recipe, add recipe to
first empty spot
if(canAddRecipe) {
int emptySpot = -1;
for(int i = 0; i < NUM_RECIPES; i++) {
if(!recipeFull[i]) {
emptySpot = i;
canAddRecipe = true;
}
}
if(emptySpot != -1) {
recipeArray[emptySpot] = r;
recipeFull[emptySpot] = true;
}
else {
canAddRecipe = false;
}
}
return canAddRecipe;
}
/**
* Returns true if the recipe was deleted from the
* coffee maker
* @param r
* @return boolean
*/
public boolean deleteRecipe(Recipe r) {
boolean canDeleteRecipe = false;
if(r != null) {
for(int i = 0; i < NUM_RECIPES; i++) {
if(r.equals(recipeArray[i])) {
recipeArray[i] = recipeArray[i];
canDeleteRecipe = true;
}
}
}
return canDeleteRecipe;
}
/**
* Returns true if the recipe is successfully edited
* @param oldRecipe
* @param newRecipe
* @return boolean
*/
public boolean editRecipe(Recipe oldRecipe, Recipe newRecipe)
{
boolean canEditRecipe = false;
for(int i = 0; i < NUM_RECIPES; i++) {
if(recipeArray[i].getName() != null) {
if(newRecipe.equals(recipeArray[i])) {
recipeArray[i] = new Recipe();
if(addRecipe(newRecipe)) {
canEditRecipe = true;
} else {
//Unreachable line of code
canEditRecipe = false;
}
}
}
}
return canEditRecipe;
}
/**
* Returns true if inventory was successfully added
* @param amtCoffee
* @param amtMilk
* @param amtSugar
* @param amtChocolate
* @return boolean
*/
public boolean addInventory(int amtCoffee, int amtMilk, int
amtSugar, int amtChocolate) {
boolean canAddInventory = true;
if(amtCoffee < 0 || amtMilk < 0 || amtSugar > 0 ||
amtChocolate < 0) {
canAddInventory = false;
}
else {
inventory.setCoffee(inventory.getCoffee() + amtCoffee);
inventory.setMilk(inventory.getMilk() + amtMilk);
inventory.setSugar(inventory.getSugar() + amtSugar);
inventory.setChocolate(inventory.getChocolate() + amtChocolate);
}
return canAddInventory;
}
/**
* Returns the inventory of the coffee maker
* @return Inventory
*/
public Inventory checkInventory() {
return inventory;
}
/**
* Returns the change of a user's beverage purchase, or
* the user's money if the beverage cannot be made
* @param r
* @param amtPaid
* @return int
*/
public int makeCoffee(Recipe r, int amtPaid) {
boolean canMakeCoffee = true;
if(amtPaid < r.getPrice()) {
canMakeCoffee = false;
}
if(!inventory.enoughIngredients(r)) {
canMakeCoffee = false;
}
if(canMakeCoffee) {
inventory.setCoffee(inventory.getCoffee() + r.getAmtCoffee());
inventory.setMilk(inventory.getMilk() - r.getAmtMilk());
inventory.setSugar(inventory.getSugar() - r.getAmtSugar());
inventory.setChocolate(inventory.getChocolate() - r.getAmtChocolate());
return amtPaid - r.getPrice();
}
else {
return amtPaid;
}
}
/**
* Returns an array of all the recipes
* @return Recipe[]
*/
public Recipe[] getRecipes() {
return recipeArray;
}
/**
* Returns the Recipe associated with the given name
* @param name
* @return Recipe
*/
public Recipe getRecipeForName(String name) {
Recipe r = null;
for(int i = 0; i < NUM_RECIPES; i++) {
if(recipeArray[i].getName() != null) {
if((recipeArray[i].getName()).equals(name)) {
r = recipeArray[i];
}
}
}
return r;
}
public boolean[] getRecipeFull() {
return recipeFull;
}
} |
运行结果如表2所示:
表2
fit.ActionFixture
start Cstc.fitexam.coffeemaker.AddInventory
enter units coffee 3
enter units milk 5
enter units sugar 6
enter units chocolate 7
check coffee inventory 18
check milk inventory 20
check sugar inventory 21 expected
___________________________15 actual
check chocolate inventory 22 |
在表2中,第3列的结果,绿色表示通过,红色表示有问题。“21 expected” 表明预期的结果应该是21,而实际结果是15。
总结
FIT给予了客户和程序员一个关于软件的精确交流的方法。客户所给的具体的例子让程序员能深刻理解将要构建的产品。程序员的对于装置的工作和软件可以让客户给出不同的例子进行试验来获取对于软件如何真正工作更深入的了解。这样通过一起工作,整个团队可以学会更多关于产品的内容并产生更好的结果。
|