单元测试的最佳实践

发表于:2020-9-11 09:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:佚名    来源:51CTO

  单元测试的运行速度重要吗?
  很多人会觉得单测反正也不是系统中的代码,运行的快慢无所谓,然后写出很多其慢无比的单测,以至于系统全量跑一次单测要几十分钟。这样的话就完全偏离了单测的定位,单测的目的就是为了方便快速迭代,改了两行代码就可以在本地用 30 秒到几分钟的时间全量跑一次单测来确定影响范围,而不是每次都要通读系统源码才能知道改动的影响范围,这样新人很快就可以大胆改代码了,而不是先花几个月通读系统源码,或者先踩好几个坑,才能上手干活。那些全量跑单测要几十分钟的系统,他的开发者根本就不会在本地全量运行单测,每次都在 aone 上跑半天才知道单测不过,这样的单测就形同虚设了。
  违背这个原则的典型反例,就是在单测中启动 Spring。
  数据驱动测试(Data Driven Test)
  不好的单元测试常常只用一组正常测试数据进行测试,实际上我们应该使用多组数据,包括正常和异常数据,输入模块,看返回值是否符合预期。使用多组测试数据是否就意味着多写很多代码呢?并不是,我们只要注意将测试用例的逻辑与数据分离就可以,测试代码依次读取测试数据,校验其是否符合预期。这样的逻辑与数据分离的测试一般称做 “数据驱动测试”,常见的单元测试框架都会提供这种支持。
  "数据驱动测试" 的概念还是太抽象了,这里我们看两段代码,左图未分离数据与用例,右图则做了分离,能够看出很明显的不同,右图是基于 Spock 单元测试框架来写的,不熟悉的人看上去可能比较奇怪,可以把 where 标签下的代码看成一张表格,每一行都是一组测试数据,Spock 框架会将其依次代入 testAdd 方法参数进行测试。
测试数据未与用例分离
测试数据与用例分离
  大家所熟悉的 junit 框架也是可以做的,但是需要写一个额外的内部类,加上@RunWith(Parameterized.class),写一个 data 静态方法,然后返回需要测试的数据组,然后 junit 就会依次将数据填入这个类的属性中,运行这个类中的全部测试用例。
基于 junit 的数据驱动测试
基于 Spock 的数据驱动测试
  如何测试私有方法
  大家写单测时常有的一个困惑就是私有方法怎么测试?虽然理论上私有方法不需要写单测,但是有些私有方法逻辑比较复杂,还是值得单独写测试的,目前公认比较好的实践就是将修饰符从 private 改成 protected, 这也是很多开源项目给单测留口子的方法。如果你的项目刚好有引入 guava 的话,可以再给方法加上一个 @VisibleForTesting 的注解,表示仅仅是出于单元测试需要修改的修饰符。
  一个典型的例子:
  TDD 与 BDD
  最后一篇来讲一两个大家可能经常听说过的理念,TDD 和 BDD。个人觉得这两个理念都比较极端,实际中很难应用,启发意义大于其实用意义,所以放在最后,希望能带来一些启发。
  TDD
  TDD 强调让写代码的过程形成一个循环,第一步是为你要做的功能写一个单元测试,跑一下发现没有通过(毕竟你还没有实现代码),即图中的 TEST FAILS,俗称“红灯”,之后编写能够通过全部测试的“最小代码”,之所以强调“最小代码”,就是为了防止过度优化,现实中我们经常会因为代码过度优化,或者过度设计,导致很多遗留问题,在这个阶段,只管用最快最脏的代码实现就好了,不用管太多设计问题。这个阶段俗称“绿灯”。
  最重要的就是下面的“重构”(REFACTOR)阶段了,前面的代码虽然可能很脏,但是至少是正确,也有足够的测试来保障逻辑的正确,这个时候就可以大刀阔斧地重构代码了,保证代码继续保持最优。
  这启发我们两点:
  ·单测必须能够快速运行,因为单测是经常要在本地全量运行的,只有运行足够快,才能在 TDD 的循环中快速迭代。
  ·好的代码并不是一次性就设计出来的,而是持续重构出来,而单测是持续重构的前提。
  BDD
  我常常抱怨产品经理在提需求时没有想清楚,比如下图,如果让产品经理也可以写出可执行的测试用例的话,情况想必会好很多。BDD 就是这么一个想法。
产品经理提需求
  不知道大家有没有在有的项目里见过 .story 文件,它本质上就是一种集成测试脚本,只不过是用自然语言描述,它包含叙述,场景和步骤三部分,比如上图就是一个书店管理应用的 .story 文件,文件中叙述(Narrative) 和 场景(Scenario) 只是帮助思考的,本身并包含在测试用例的逻辑中,测试用例主要由 Given, When 和 Then 开头的语句组成,含义如下:
story 文件示例
  story 文件自己当然是无法执行的,需要框架提供支持,JBehave 就是这么一种框架(右图),能够定义各种 Given,When,Then 语句的实现,下图的代码本质上就是个基于 Selenide 的自动化界面点击测试,它支撑 story 文件的执行。我们以这个 story 文件为依据,就可以像 TDD 循环一样,先测试不通过(红灯),然后用最小的代码让测试通过(绿灯),最后重构代码。只不过这个循环可能会耗时好好几天,乃至几个星期。而 TDD 一个循环可能只需要几个小时,所以说 BDD 是集成测试版的 TDD。
JBehave 框架
  敏捷
  我们往往会觉得 TDD 和 BDD 会严重拖慢迭代速度,值得讽刺的是,TDD 和 BDD 恰恰是敏捷开发实践的重要组成部分:
图源维基百科 Agile software development
  我们学习敏捷开发的时候,常常只学习到它的 “快”,而忽略了敏捷开发所提出的质量保证方法。敏捷开发所谓的“快”,是指在代码质量充分保证下的“快”,而不是做完功能就直接上线。
  如何学习写单测
  学习单测的关键还是多实践,多看看别人好的单测怎么写。比如可以给一些公认代码优秀的开源项目提交代码。
  总结
  ·单测能够帮助我们验证代码设计的合理性。
  ·含有核心业务的代码应该首先思考如何让主体业务逻辑可以写无 Mock 单测。
  ·用例数据尽量和测试逻辑分离。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号