TA的每日心情 | 无聊 昨天 09:06 |
---|
签到天数: 1051 天 连续签到: 1 天 [LV.10]测试总司令
|
2#
楼主 |
发表于 2016-7-18 14:58:07
|
只看该作者
作者:谢龙
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
贴一篇我专栏里的文章(一篇文章带你了解 Java 服务端单元测试的方方面面 - Not A Geek? - 知乎专栏),主要写的是 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 版本的单元测试代码:
单元测试一般分为四个步骤:setup、exercise、verify、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 等。这么多工具,该如何选择呢,可以看看 stackoverflow 上的一个问题: 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 等持续集成工具定时/有代码变更时运行单元测试,保证修改不会破坏已有的代码功能。
另外,测试遗留代码也是一个巨大的挑战,你需要把代码重构到“可测试”的状态,《修改代码的艺术》在这方面一定可以帮到你。
扩展阅读:
1.《单元测试之道》
2.《修改代码的艺术》
3. Mocks Aren't Stubs |
|