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 的 FIT 表格中有八行数据值。可能已经在 清单 7 中编写了前两行的 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 的 JUnit 测试用例和清单 12。是不是清单 12 更有效率?当然可以 用 JUnit 编写所有必需的测试,但是 FIT 可以让工作容易得多!如果感觉到满意(应当是满意的!),可以运行构建,调用 FIT 运行器生成如图 6 所示的结果:
图 6. 这些结果真的很 FIT !

结束语
FIT 可以帮助企业避免客户和开发人员之间的沟通不畅、误解和误读。把编写需求的人尽早 带入测试过程,是在问题成为开发恶梦的根源之前发现并修补它们的明显途径。而且,FIT 与现有的技术(比如 JUnit)完全兼容。实际上,正如本文所示,JUnit 和 FIT 互相补充。请把今年变成您追逐代码质量 的重要纪年 —— 由于决心采用 FIT!
上一页 [1] [2] [3]

【责编:wayen】