lsekfe 发表于 2016-7-18 14:57:10

【转】请问高手大牛们都是怎么进行UnitTest的?

本人C++菜鸟码农一枚,写了一段时间代码了,平时测试代码就是随便测测,没有系统性,身边的人貌似也很少进行系统的UnitTest,几个问题请教一下。 1.大牛们都是怎么测的,要用什么工具吗? 2.对于GUI的部分一般怎么测? 3.想系统的从零开始学学UnitTest应该怎么做呢?

lsekfe 发表于 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

lsekfe 发表于 2016-7-18 15:01:09

测试驱动开发的艺术 (豆瓣)
1 测试驱动的开发周期组织为测试——编码——重构三个过程。
2 测试驱动适应于自底向上开发过程。小步构建过程。逐渐完善。适用于大型项目。
3 如果是简单小项目,直接编码测试即可。
4 简单与复杂是项目的结构对于开发领导者的相对而言。

lsekfe 发表于 2016-7-18 15:03:03

说说我对UnitTest这种技术的看法,主要偏向于Java下的JUnit等测试技术。
单元测试是为了保障系统某些粒度,特别是最细节到方法的粒度上的正确性(狭义的单元测试)。通过详尽而适度的UnitTest case,可以在重构和修改代码时,保障不影响我们以前的代码(回归测试),也可以在实现具体业务之前写测试来保障开发时的迭代实现都是正确的(测试驱动开发),有时候也可以对比方法级别更高的层次做一次封装实现一定程度的(集成测试),有时候也可以实现对某些远程业务的集成测试(接口自动化测试),或者借助一些特定的库或技术实现对UI系统的功能测试(UI自动化测试)。
单元测试本身就是利用机器去执行测试,从而代替人工的测试或调试,节约程序员的时间和生命,并且可以做到常态化(结合项目质量和持续集成工具,Sonar、Hudson/Jenkins等),保障代码的(逻辑、边界等)正确性和系统质量。所以单元测试应该越简单越好。当体系比较庞大时,为了一个大流程中间的某个小零件能run起来,需要准备非常多这个点的测试环境不需要的东西,非脚本语言环境下的单元测试(比如java)可以结合Mock框架(Jmock,mockito,easyMock)使用,简单方便、不需要拖泥带水。复杂环境下的UnitTest,需要测试数据的准备、测试完成后的数据清理,特别要注意Test上下文环境的污染,从而影响测试的准确性。Spring下的UnitTest,可以使用SpringTest,对配置环境的管理,测试上下文的管理,事务的管理,都可以很好支持。对于远程方法或借口的测试可以用HttpUnit。WEB的自动化测试可以走Selenium或Tellurium。RCP的桌面程序,可以用SWTBot等等。。。有点跑题了。

hejiayuan 发表于 2016-7-18 16:21:51

之前一直测试C,用工具单元测试很简单
页: [1]
查看完整版本: 【转】请问高手大牛们都是怎么进行UnitTest的?