单元测试

一.简介

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。

总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

它最直接的好处有两点:

  1. 让你写出更好的代码:职业高内聚、低耦合而且接口设计合理的代码才易于测试;
  2. 让你在修改代码时更有信心。

然后我们来举个例子,假设我们有个 add(a, b) 函数:
单元测试

现在想测试它,于是用 JUnit 写个简单的测试函数:
单元测试

你传入参数,调用 add 函数,然后很快能得到结果,对比期望值就能知道函数功能是否正常。这种验证方式叫做状态验证(state verification)。

但是真实项目中的代码要复杂的多。

二.实际测试

这段代码从数据库中查询数据:
单元测试

如果要测试以上代码,你有几种选择:

  1. 连接一个“真实”的数据库
  2. 选择内存数据库,比如 h2
  3. 创建 Mock 对象,在测试时代替 jdbcTemplate。

第1种方式最直接,不过依赖外部数据库会降低单元测试的效率,如果环境出现问题,比如网络不稳定,单元测试还会出错。

第2种方式是我们推荐的做法,我们可以认为 h2 是 MySQL 的 fake 对象(当然你选用其他内存数据库,或者自己写一个内存数据库也可以 : P)。我们在生产环境使用 MySQL,而在单元测试/集成测试的时候,则选用 h2,当然这种方法也有一个问题:h2 不可能和 MySQL 完全兼容,所以我们需要改写部分不兼容的语句,或者用其他方式比如第 3 种方式测试它们。以上两种测试方法也是状态验证(state verification)。

我们来详细谈谈第 3 种方式。让我们看看 Mockito 版本的单元测试代码:
单元测试

单元测试一般分为四个步骤:

  1. setup
  2. exercise
  3. verify
  4. teardown

用上面的代码举例,setup 阶段,我们创建了 mock 对象,并且设置了 queryForInt 的行为;exercise 阶段调用了测试函数;verify 阶段做了两件事情:1. 确认 queryForInt 函数被正确调用,2. count() 返回值符合预期;teardown 阶段一般用来清理和释放资源,我们这里不需要,直接跳过了。

这种利用 Mock 对象的测试叫做行为(behavior verification)。回到我们要测试的 count() 函数:我们调用了 jdbcTemplate 类的 queryForObject 函数从数据库中查询数据。我们要测试的是自己的业务逻辑,所以我们认为 jdbcTemplate 和数据库是可靠的(即使不可靠也不应该由我们的单元测试来验证),如果我们向 jdbcTemplate 传了正确的参数,后者就会向数据库发起正确的请求,然后得到正确的结果。

换句话说,我们只要验证是否正确地调用了 jdbcTemplate 就行了。于是,我们可以创建一个模拟对象,即 Mock 对象,在测试的时候代替 jdbcTemplate。 这个对象可以完全由我们自己编写:实现特定接口,继承特定类,或者用动态代理,甚至修改字节码,它不会真正的访问数据库,但会保存你的调用行为,以便你来验证是否请发起了正确的请求。 当然,绝大部分情况, Mock 对象的实现细节不用你辛辛苦苦写出来。

Java 社区有一大堆开源项目可以选择,比如:Mockito、EasyMock、PowerMock、JMock 和 JMockit 等。这么多工具,该如何选择呢,可以看看 * 上的一个问题: What's the best mock framework for Java。简单的建议:使用 Mockito 结合 PowerMock,需要自己写 fake 对象时选择 JMockit。

测试中过程中,什么时候使用 Mock 对象,也形成了 Classical 和 Mockist 两种不同的测试风格。我个人以前是 Mockist 风格,现在偏向 Classical,不过这里不展开了,如果想进一步了解,可以看 Martin Fowler 的经典文章 Mocks Aren't Stubs。

说回到项目,实际的项目往往依赖了各种框架和组件,在动手为它们写 fake/mock 对象之前,可以看看社区是不是已经有了支持,比如:Spring 有 Spring Test、Spring MVC Test;涉及到 Zookeeper,Netflix 提供了in-process ZooKeeper server 。

如果你使用 maven 等构件工具构件你的项目,你还可以利用构件工具以及它们的插件做更多事情,比如:利用多线程提高测试效率,只执行特定的测试代码,生成测试报告等等。通常,我们也会利用 jenkins 等持续集成工具定时/有代码变更时运行单元测试,保证修改不会破坏已有的代码功能。

另外,测试遗留代码也是一个巨大的挑战,你需要把代码重构到“可测试”的状态,《修改代码的艺术》在这方面一定可以帮到你。

上一篇:前端自动化构建工具gulp记录


下一篇:睿联嘉业边缘融合大屏幕多媒体会议系统方案