首页 | 互联网 | IT动态 | IT培训 | Cisco | Windows | Linux | Java | .Net | Oracle | 软件测试 | C/C++ | 嵌入式开发 | 存储世界 | 服务器
网络设备 | IDC | 安全 | 求职招聘 | 数字网校 | 网页设计 | 平面设计 | 技术专题 | 电子书下载 | 教学视频 | 源码下载 | 搜索 | 博客 | 论坛
中国IT实验室Linux频道
中国IT教育
Google
首页 资讯动态 认证考试 新手入门 核心技术 高级技术 J2EE J2ME Java&XML 开源技术 其他技术 RSS订阅 论坛 专题
您现在的位置: 中国IT实验室 >> Java >> 高级技术 >> 设计模式 >> 正文

追逐代码质量: 决心采用 FIT

  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 了!


  清单 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,现在是下载它的时候了!如前所述,这个案例研究显示出可以容易地把 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 所示,第二行单元格中的值是用普通的英文编写的,例如 “number of cases”。FIT 试图把这样的字符串按照首字母大写方式连接起来;例如,“number of cases” 变成 “numberOfCases”,然后 FIT 试图找到对应的装备类。这个原则也适用于方法 —— 如图 5 所示,“discount price()” 变成了 “discountPrice()”。

  FIT 还会智能地猜测单元格中值的具体类型。例如,在 图 5 余下的八行中,每一列都有对应的类型,或者可以由 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 实例中设置了折扣,就可以通过分别调用 getCalculatedPricegetDiscountedDifference 方法得到折扣价格和节省的钱。

  更好地测试这些方法(用 JUnit)!

  定义了 MoneyWholesaleOrder 类之后,还要编写 JUnit 测试来验证 getCalculatedPricegetDiscountedDifference 方法的功能。测试如清单 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。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();		
  }
}

 

上一页  [1] [2] [3] 下一页

【责编:wayen】

中国IT教育

相关产品和培训
文章评论
 友情推荐链接
 认证培训
 专题推荐

 ·关于Java框架技术专题
 ·XML全攻略技术专题
 ·JAVA开源技术介绍专题
 ·Java嵌入式开发之J2ME技术专题
 ·超前体验 Oracle 11g的5个新特性…
 ·揭密使用VB.NET的五个实用技巧
 ·Oracle和SQL Server常用函数对比专题…
 ·展现C#世界 C#程序设计专题…
 ·Java入门 Tomcat的配置技巧精华专题…
 ·Oracle RMAN物理备份技术详解…
 今日更新
 社区讨论
 博客论点
 频道精选
 Java 频道导航