使用EasyMock更轻松地进行测试
作者:网络转载 发布时间:[ 2014/7/10 15:13:44 ] 推荐标签:EasyMock 单元测试
EasyMock 为基本数据类型提供相似的方法:
EasyMock.anyInt()
EasyMock.anyShort()
EasyMock.anyByte()
EasyMock.anyLong()
EasyMock.anyFloat()
EasyMock.anyDouble()
EasyMock.anyBoolean()
对于数字类型,还可以使用 EasyMock.lt(x) 接受小于 x 的任何值,或使用 EasyMock.gt(x) 接受大于 x 的任何值。
在检查一系列预期时,可以捕捉一个方法调用的结果或参数,然后与传递给另一个方法调用的值进行比较。后,通过定义定制的匹配器,可以检查参数的任何细节,但是这个过程比较复杂。但是,对于大多数测试,EasyMock.anyInt()、EasyMock.matches() 和 EasyMock.eq() 这样的基本匹配器已经足够了。
严格的 mock 和次序检查
EasyMock 不仅能够检查是否用正确的参数调用预期的方法。它还可以检查是否以正确的次序调用这些方法,而且只调用了这些方法。在默认情况下,不执行这种检查。要想启用它,应该在测试方法末尾调用 EasyMock.verify(mock)。例如,如果 toEuros() 方法不只一次调用getRate(),清单 6 会失败。
清单 6. 检查是否只调用 getRate() 一次
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);
EasyMock.verify(mock);
}
|
Normal — EasyMock.createMock():必须用指定的参数调用所有预期的方法。但是,不考虑调用这些方法的次序。调用未预期的方法会导致测试失败。
Strict — EasyMock.createStrictMock():必须以指定的次序用预期的参数调用所有预期的方法。调用未预期的方法会导致测试失败。
Nice — EasyMock.createNiceMock():必须以任意次序用指定的参数调用所有预期的方法。调用未预期的方法不会 导致测试失败。Nice mock 为没有显式地提供 mock 的方法提供合理的默认值。返回数字的方法返回 0,返回布尔值的方法返回 false。返回对象的方法返回 null。
检查调用方法的次序和次数对于大型接口和大型测试更有意义。例如,请考虑 org.xml.sax.ContentHandler 接口。如果要测试一个 XML 解析器,希望输入文档并检查解析器是否以正确的次序调用 ContentHandler 中正确的方法。例如,请考虑清单 7 中的简单 XML 文档:
清单 7. 简单的 XML 文档
<root>
Hello World!
</root>
根据 SAX 规范,在解析器解析文档时,它应该按以下次序调用这些方法:
setDocumentLocator()
startDocument()
startElement()
characters()
endElement()
endDocument()
但是,更有意思的是,对 setDocumentLocator() 的调用是可选的;解析器可以多次调用 characters()。它们不需要在一次调用中传递尽可能多的连续文本,实际上大多数解析器不这么做。即使是对于清单 7 这样的简单文档,也很难用传统的方法测试 XML 解析器,但是 EasyMock 大大简化了这个任务,见清单 8:
清单 8. 测试 XML 解析器
import java.io.*;
import org.easymock.EasyMock;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import junit.framework.TestCase;
public class XMLParserTest extends TestCase {
private XMLReader parser;
protected void setUp() throws Exception {
parser = XMLReaderFactory.createXMLReader();
}
public void testSimpleDoc() throws IOException, SAXException {
String doc = "<root>
Hello World!
</root>";
ContentHandler mock = EasyMock.createStrictMock(ContentHandler.class);
mock.setDocumentLocator((Locator) EasyMock.anyObject());
EasyMock.expectLastCall().times(0, 1);
mock.startDocument();
mock.startElement(EasyMock.eq(""), EasyMock.eq("root"), EasyMock.eq("root"),
(Attributes) EasyMock.anyObject());
mock.characters((char[]) EasyMock.anyObject(),
EasyMock.anyInt(), EasyMock.anyInt());
EasyMock.expectLastCall().atLeastOnce();
mock.endElement(EasyMock.eq(""), EasyMock.eq("root"), EasyMock.eq("root"));
mock.endDocument();
EasyMock.replay(mock);
parser.setContentHandler(mock);
InputStream in = new ByteArrayInputStream(doc.getBytes("UTF-8"));
parser.parse(new InputSource(in));
EasyMock.verify(mock);
}
}
|
这个测试展示了几种新技巧。首先,它使用一个 strict mock,因此要求符合指定的次序。例如,不希望解析器在调用 startDocument() 之前调用 endDocument()。
第二,要测试的所有方法都返回 void。这意味着不能把它们作为参数传递给 EasyMock.expect()(像对 getRate() 所做的)。(EasyMock 在许多方面能够 “欺骗” 编译器,但是还不足以让编译器相信 void 是有效的参数类型)。因此,要在 mock 上调用 void 方法,由 EasyMock 捕捉结果。如果需要修改预期的细节,那么在调用 mock 方法之后立即调用 EasyMock.expectLastCall()。另外注意,不能作为预期参数传递任何 String、int 和数组。必须先用 EasyMock.eq() 包装它们,这样才能在预期中捕捉它们的值。
清单 8 使用 EasyMock.expectLastCall() 调整预期的方法调用次数。在默认情况下,预期的方法调用次数是一次。但是,我通过调用.times(0, 1) 把 setDocumentLocator() 设置为可选的。这指定调用此方法的次数必须是零次或一次。当然,可以根据需要把预期的方法调用次数设置为任何范围,比如 1-10 次、3-30 次。对于 characters(),我实际上不知道将调用它多少次,但是知道必须至少调用一次,所以对它使用 .atLeastOnce()。如果这是非 void 方法,可以对预期直接应用 times(0, 1) 和 atLeastOnce()。但是,因为这些方法返回 void,所以必须通过 EasyMock.expectLastCall() 设置它们。
后注意,这里对 characters() 的参数使用了 EasyMock.anyObject() 和 EasyMock.anyInt()。这考虑到了解析器向 ContentHandler 传递文本的各种方式。
mock 和真实性
有必要使用 EasyMock 吗?其实,手工编写的 mock 类也能够实现 EasyMock 的功能,但是手工编写的类只能适用于某些项目。例如,对于 清单 3,手工编写一个使用匿名内部类的 mock 也很容易,代码很紧凑,对于不熟悉 EasyMock 的开发人员可读性可能更好。但是,它是一个专门为本文构造的简单示例。在为 org.w3c.dom.Node(25 个方法)或 java.sql.ResultSet(139 个方法而且还在增加)这样的大型接口创建 mock 时,EasyMock 能够大大节省时间,以低的成本创建更短更可读的代码。
后,提出一条警告:使用 mock 对象可能做得太过分。可能把太多的东西替换为 mock,导致即使在代码质量很差的情况下,测试仍然总是能够通过。替换为 mock 的东西越多,接受测试的东西越少。依赖库以及方法与其调用的方法之间的交互中可能存在许多 bug。把依赖项替换为 mock 会隐藏许多实际上可能发现的 bug。在任何情况下,mock 都不应该是您的第一选择。如果能够使用真实的依赖项,应该这么做。mock 是真实类的粗糙的替代品。但是,如果由于某种原因无法用真实的类可靠且自动地进行测试,那么用 mock 进行测试肯定比根本不测试强。
相关推荐
更新发布
功能测试和接口测试的区别
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