Welcome

首页 / 软件开发 / 数据结构与算法 / Mock不是测试的银弹

Mock不是测试的银弹2011-12-01 infoq 胡凯开发者编写高质量测试的征途上可谓布满荆棘,数据库、中间件、不同的文件系统等复杂外部系统的存在,令开发者在编写、运行测试时觉得苦恼异常。由于外部系 统常常运行在不同机器上或者本地单独的进程中,开发者很难在测试中操作和控制它们。外部系统以及网络连接的不稳定性(外部系统停止响应或者网络连接超 时),将有可能导致测试运行过程随机失败。另外,外部系统缓慢的响应速度(HTTP访问、启动服务、创建删除文件等),还可能会造成测试运行时间过长、成 本过高。种种问题使开发者不断寻找一种更廉价的方式来进行测试,mock便是开发人员解决上述问题时祭出的法宝。mock对象运行在本地完全可控环境内,利用mock对象模拟被依赖的资源,使开发者可以轻易的创建一个稳定的测试环境。mock对象本地创建,本地运行的特性更是加快测试的不二法门。

我所在团队设计开发的产品是持续集成服务器,产品特性决定了它需要在各个平台(Windows, Mac, Linux等)与各种版本管理工具(svn, mercurial,git等)、构建工具(ant, nant, rake等)进行集成,对于外部系统的严重依赖让我们在编写测试时遇到了很多困难,我们自然而然的选用了JMock作为测试框架,利用它来隔离外部系统对 于测试的影响,的的确确在使用JMock框架后测试编写起来更容易,运行速度更快,也更稳定,然而出乎意料的是产品质量并没有如我们所预期的随着不断添加 的测试而变得愈加健壮,虽然产品代码的单元测试覆盖率超过了80%,然而在发布前进行全面测试时,常常发现严重的功能缺陷而不得不一轮轮的修复缺陷、回归 测试。为什么编写了大量的测试还会频繁出现这些问题呢? 在讨论之前先来看一个真实的例子:

我们的产品需要与Perforce(一种版本管理工具)进行集成,检测某段时间内Perforce服务器上是否存在更新,如果有,将更新解析为 Modification对象。将这个需求反应在代码中,便是首先通过Perforce对象检测服务器更新,然后将标准输出(stdout)进行解析:

 public class PerforceMaterial {    private Perforce perforce;    .....    .....    public List findModifications(Date start, Date end) {        String changes = perforce.changes(start, end);    //检测更新,返回命令行标准输出(stdout)                List modifications = parseChanges(changes);//将标准输出解析为Modification        return modifications;    }    private List parseChanges(String output) {         //通过正则表达式将stdout解析为Modification对象    }}public class Perforce {    public String changes(Date start, Date end) {          //通过命令行检测更新,将命令行标准输出(stdout)返回    }}
相应的mock测试也非常容易理解:

     .....    .....      @Test    public void shouldCreateModifiationWhenChangesAreFound() {        final String stdout = "Chang 4 on 2008/09/01 by p4user@Dev01 "Added build.xml""; //设置标准输出样本        final Date start = new Date();        final Date end = new Date();        context.checking(new Expectations() {{            one(perforce).changes(start, end);            will(returnValue(stdout));        }});//设置perforce对象的行为,令其返回设定好的stdout        List list = perforceMaterial.findModifications(start, end);//调用被测方法        assertThat(list.get(0).revision(), is("4"));        assertThat(list.get(0).user(), is("p4user@Dev01"));        assertThat(list.get(0).modifiedTime(), is("2008/09/01"));    }