您的位置:软件测试 > 开源软件测试 > 开源单元测试工具 >
用EasyMock更轻松地测试
作者:网络转载 发布时间:[ 2013/2/21 13:56:57 ] 推荐标签:

设置预期

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。

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);
}


EasyMock.verify() 究竟做哪些检查取决于它采用的操作模式:

    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 进行测试肯定比根本不测试强。

上一页12下一页
软件测试工具 | 联系我们 | 投诉建议 | 诚聘英才 | 申请使用列表 | 网站地图
沪ICP备07036474 2003-2017 版权所有 上海泽众软件科技有限公司 Shanghai ZeZhong Software Co.,Ltd