使用EasyMock更轻松地进行测试
作者:网络转载 发布时间:[ 2014/7/10 15:13:44 ] 推荐标签:EasyMock 单元测试
Currency 类设计的一些重点可能不容易一下子看出来。汇率是从这个类之外 传递进来的,并不是在类内部构造的。因此,很有必要为汇率创建 mock,这样在运行测试时不需要与真正的汇率服务器通信。这还使客户机应用程序能够使用不同的汇率数据源。
清单 3 给出一个 JUnit 测试,它检查在汇率为 1.5 的情况下 $2.50 是否会转换为 €3.75。使用 EasyMock 创建一个总是提供值 1.5 的ExchangeRate 对象。
清单 3. CurrencyTest 类
import junit.framework.TestCase;
import org.easymock.EasyMock;
import java.io.IOException;
public class CurrencyTest extends TestCase {
public void testToEuros() throws IOException {
Currency expected = new Currency(3.75, "EUR");
ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
EasyMock.replay(mock);
Currency actual = testObject.toEuros(mock);
assertEquals(expected, actual);
}
}
|
老实说,在我第一次运行 清单 3 时失败了,测试中经常出现这种问题。但是,我已经纠正了 bug。这是我们采用 TDD 的原因。
运行这个测试,它通过了。发生了什么?我们来逐行看看这个测试。首先,构造测试对象和预期的结果:
Currency testObject = new Currency(2.50, "USD");
Currency expected = new Currency(3.75, "EUR");
这不是新东西。
接下来,通过把 ExchangeRate 接口的 Class 对象传递给静态的 EasyMock.createMock() 方法,创建这个接口的 mock 版本:
ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
这是到目前为止不可思议的部分。注意,我可没有编写实现 ExchangeRate 接口的类。另外,EasyMock.createMock() 方法无法返回ExchangeRate 的实例,它根本不知道这个类型,这个类型是我为本文创建的。即使它能够通过某种奇迹返回 ExchangeRate,但是如果需要模拟另一个接口的实例,又会怎么样呢?
我初看到这个时也非常困惑。我不相信这段代码能够编译,但是它确实可以。这里的 “黑魔法” 来自 Java 1.3 中引入的 Java 5 泛型和动态代理(见 参考资料)。幸运的是,您不需要了解它的工作方式(发明这些诀窍的程序员确实非常聪明)。
下一步同样令人吃惊。为了告诉 mock 期望什么结果,把方法作为参数传递给 EasyMock.expect() 方法。然后调用 andReturn() 指定调用这个方法应该得到什么结果:
EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
EasyMock 记录这个调用,因此知道以后应该重放什么。
如果在使用 mock 之前忘了调用 EasyMock.replay(),那么会出现 IllegalStateException 异常和一个没有什么帮助的错误消息:missing behavior definition for the preceding method call。
接下来,通过调用 EasyMock.replay() 方法,让 mock 准备重放记录的数据:
EasyMock.replay(mock);
这是让我比较困惑的设计之一。EasyMock.replay() 不会实际重放 mock。而是重新设置 mock,在下一次调用它的方法时,它将开始重放。
现在 mock 准备好了,我把它作为参数传递给要测试的方法:
为类创建 mock
从实现的角度来看,很难为类创建 mock。不能为类创建动态代理。标准的 EasyMock 框架不支持类的 mock。但是,EasyMock 类扩展使用字节码操作产生相同的效果。您的代码中采用的模式几乎完全一样。只需导入org.easymock.classextension.EasyMock 而不是org.easymock.EasyMock。为类创建 mock 允许把类中的一部分方法替换为 mock,而其他方法保持不变。
Currency actual = testObject.toEuros(mock);
后,检查结果是否符合预期:
assertEquals(expected, actual);
这完成了。如果有一个需要返回特定值的接口需要测试,可以快速地创建一个 mock。这确实很容易。ExchangeRate 接口很小很简单,很容易为它手工编写 mock 类。但是,接口越大越复杂,越难为每个单元测试编写单独的 mock。通过使用 EasyMock,只需一行代码能够创建 java.sql.ResultSet 或 org.xml.sax.ContentHandler 这样的大型接口的实现,然后向它们提供运行测试所需的行为。
测试异常
mock 常见的用途之一是测试异常条件。例如,无法简便地根据需要制造网络故障,但是可以创建模拟网络故障的 mock。
当 getRate() 抛出 IOException 时,Currency 类应该返回 null。清单 4 测试这一点:
清单 4. 测试方法是否抛出正确的异常
public void testExchangeRateServerUnavailable() throws IOException {
ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
EasyMock.expect(mock.getRate("USD", "EUR")).andThrow(new IOException());
EasyMock.replay(mock);
Currency actual = testObject.toEuros(mock);
assertNull(actual);
}
|
这里的新东西是 andThrow() 方法。顾名思义,它只是让 getRate() 方法在被调用时抛出指定的异常。
可以抛出您需要的任何类型的异常(已检查、运行时或错误),只要方法签名支持它即可。这对于测试极其少见的条件(例如内存耗尽错误或无法找到类定义)或表示虚拟机 bug 的条件(比如 UTF-8 字符编码不可用)尤其有帮助。
设置预期
EasyMock 不只是能够用固定的结果响应固定的输入。它还可以检查输入是否符合预期。例如,假设 toEuros() 方法有一个 bug(见清单 5),它返回以欧元为单位的结果,但是获取的是加拿大元的汇率。这会让客户发一笔意外之财或遭受重大损失。
清单 5. 有 bug 的 toEuros() 方法
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, "CAD");
double output = input * rate;
return new Currency(output, "EUR");
} catch (IOException e) {
return null;
}
}
}
|
但是,不需要为此编写另一个测试。清单 4 中的 testToEuros 能够捕捉到这个 bug。当对这段代码运行清单 4 中的测试时,测试会失败并显示以下错误消息:
"java.lang.AssertionError:
Unexpected method call getRate("USD", "CAD"):
getRate("USD", "EUR"): expected: 1, actual: 0".
注意,这并不是我设置的断言。EasyMock 注意到我传递的参数不符合测试用例。
在默认情况下,EasyMock 只允许测试用例用指定的参数调用指定的方法。但是,有时候这有点儿太严格了,所以有办法放宽这一限制。例如,假设希望允许把任何字符串传递给 getRate() 方法,而不于 USD 和 EUR。那么,可以指定 EasyMock.anyObject() 而不是显式的字符串,如下所示:
EasyMock.expect(mock.getRate(
(String) EasyMock.anyObject(),
(String) EasyMock.anyObject())).andReturn(1.5);
还可以更挑剔一点儿,通过指定 EasyMock.notNull() 只允许非 null 字符串:
EasyMock.expect(mock.getRate(
(String) EasyMock.notNull(),
(String) EasyMock.notNull())).andReturn(1.5);
静态类型检查会防止把非 String 对象传递给这个方法。但是,现在允许传递 USD 和 EUR 之外的其他 String。还可以通过EasyMock.matches() 使用更显式的正则表达式。下面指定需要一个三字母的大写 ASCII String:
EasyMock.expect(mock.getRate(
(String) EasyMock.matches("[A-Z][A-Z][A-Z]"),
(String) EasyMock.matches("[A-Z][A-Z][A-Z]"))).andReturn(1.5);
使用 EasyMock.find() 而不是 EasyMock.matches(),可以接受任何包含三字母大写子 String 的 String。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11