测试驱动的开发是软件开发的重要组成部分。 如果未测试代码,则该代码已损坏。 所有代码都必须经过测试,理想情况下,应在模型代码之前编写测试。 但是有些事情比其他事情更容易测试。 如果您要编写一个简单的类来表示货币,则可以很容易地测试出可以将$ 1.23加到$ 2.28并获得$ 4.03,而不是$ 3.03或4.029999998。 测试创建像7.465美元这样的货币并不是难事。 但是,您如何测试将7.50美元转换为5.88欧元的方法,尤其是当通过连接到实时数据库并每秒更新一次信息来找到汇率时? 每当您运行该程序时, amount.toEuros()
的正确结果可能会更改。
答案是模拟对象 。 测试未连接到提供最新汇率信息的真实服务器,而是连接到始终返回相同汇率的模拟服务器。 这样您便可以测试出可预测的结果。 毕竟,目标是测试toEuros()
方法中的逻辑,而toEuros()
测试服务器是否发送了正确的值。 (让构建服务器的开发人员对此有所担心。)这种模拟对象有时称为false 。
模拟对象对于测试错误条件也很有用。 例如,如果toEuros()
方法尝试获取最新汇率,但网络中断,会发生什么情况? 您可以从计算机上拔下以太网电缆,然后运行测试,但是编写模拟网络故障的模拟对象的工作量大大减少。
模拟对象也可以用来监视类的行为。 通过将断言放置在模拟代码中,您可以验证被测代码是否在正确的时间将正确的参数传递给其协作者。 模拟可以让您查看和测试类的私有部分,而无需通过其他不必要的公共方法公开它们。
最后,模拟对象有助于从测试中删除较大的依赖项。 它们使测试更加统一。 包含模拟对象的测试失败很可能是被测试方法的失败,而不是其依赖项之一。 这有助于隔离问题并简化调试。
EasyMock是Java编程语言的开源模拟对象库,可帮助您快速轻松地创建用于所有这些目的的模拟对象。 通过动态代理的魔力,EasyMock使您能够只用一行代码就可以创建任何接口的基本实现。 通过添加EasyMock类扩展,您也可以为类创建模拟。 可以出于各种目的对这些模拟进行配置,范围从用于填充方法签名的简单伪参数到用于验证较长方法调用序列的多调用间谍。
介绍EasyMock
我将从一个具体的示例开始,以演示EasyMock的工作方式。 清单1是假设的ExchangeRate
接口。 像任何接口一样,它仅说明实例的作用而无需指定实例的操作方式。 例如,它并没有说汇率数据是来自雅虎财务部门,政府还是其他地方。
清单1. ExchangeRate
import java.io.IOException;
public interface ExchangeRate {
double getRate(String inputCurrency, String outputCurrency) throws IOException;
}
清单2是假定的Currency
类的框架。 它实际上相当复杂,并且很可能包含错误。 (我会为您悬念:存在错误-实际上有很多错误。)
清单2. Currency
类
import java.io.IOException;
public class Currency {
private String units;
private long amount;
private int cents;
public Currency(double amount, String code) {
this.units = code;
setAmount(amount);
}
private void setAmount(double amount) {
this.amount = new Double(amount).longValue();
this.cents = (int) ((amount * 100.0) % 100);
}
public Currency toEuros(ExchangeRate converter) {
if ("EUR".equals(units)) return this;
else {
double input = amount + cents/100.0;
double rate;
try {
rate = converter.getRate(units, "EUR");
double output = input * rate;
return new Currency(output, "EUR");
} catch (IOException ex) {
return null;
}
}
}
public boolean equals(Object o) {
if (o instanceof Currency) {
Currency other = (Currency) o;
return this.units.equals(other.units)
&& this.amount == other.amount
&& this.cents == other.cents;
}
return false;
}
public String toString() {
return amount + "." + Math.abs(cents) + " " + units;
}
}
乍一看,关于Currency
类设计的重要事项可能并不明显。 汇率是从班级外部传递的。 它不是在类内部构造的。 这对于使我能够模拟汇率至关重要,因此测试可以在不与真实汇率服务器对话的情况下运行。 它还使客户端应用程序可以提供不同的汇率数据源。
清单3演示了一个JUnit测试,该测试验证汇率为1.5时,2.50美元是否可以转换为3.75欧元。 EasyMock用于创建始终提供值1.5的ExchangeRate
对象。