摘要:分享一些个人在编写单元测试过程中遇到的问题,以及从写单元测试的角度出发,分析如何让我们写出”容易单元测试的代码”。
前言:单元测试是所有测试体系里的一个部分,而非全部,不能解决所有问题。覆盖率并不是越高,就能保证系统绝对不出错。为了单元测试,而单元测试是不可取的。单元测试最主要的目的之一,是为了提高我们的代码质量。
关于单元测试
一、什么是单元测试?
单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是必须的,但也不坏,这牵涉到项目管理的政策决定。
总结:对于程序单元(软件设计的最小单位)进行正确性检验。
二、关于编写单元测试的一些声音
不愿写单元测试的人,千篇一律——这里收集了一些网友们不愿意写单元测试的原因:
- 开发时间太紧张了,还要写单元测试?没时间呀!!!有时间我也愿意写。
- 我直接进行接口测试、功能测试、集成测试、系统测试。。。不就行了,速度快,而且效率高,要写啥单元测试。
- 有测试工程师了,我要写啥单元测试,让测试工程师测试不就好了,分工明确。
- 单元测试仅仅是证明这些代码做了什么,写了没有意义呀,这些代码都是我写的,我知道它们是做什么的。
- 测试代码的正确性,是测试工程师的工作,如果我找出了所有问题,我的工资可以翻倍吗?
- 我在开发环境发布后,通过测试业务,来验证我代码的正确性就好了,不需要通过单元测试。
总结:
- 开发时间太紧张,写单元测试太耗费时间。
- 有其他测试流程做了单元测试做的事情,没必要重复劳动。
- 开发工程师负责开发,测试工程师负责测试。
- 写单元测试对于我没有收益。
- 我可以直接执行代码,来验证正确性,没必要通过单元测试。
- 代码都是我写的,我知道是做什么的,写单元测试没有意义。
二、单元测试的好处
1、适应变更
单元测试允许程序员在未来重构代码,并且确保模块依然工作正确(复合测试)。这个过程就是为所有函数和方法编写单元测试,一旦变更导致错误发生,借助于单元测试可以快速定位并修复错误。
可读性强的单元测试可以使程序员方便地检查代码片断是否依然正常工作。良好设计的单元测试案例覆盖程序单元分支和循环条件的所有路径。
在连续的单元测试环境,通过其固有的持续维护工作,单元测试可以延续用于准确反映当任何变更发生时可执行程序和代码的表现。借助于上述开发实践和单元测试的覆盖,可以分分秒秒维持准确性。
总结:
- 代码变更后,快速的验证业务逻辑是否正确,快速定位问题。一定程度上,缓解不敢更改老旧逻辑的情况。
2、简化集成
单元测试消除程序单元的不可靠,采用自底向上的测试路径。通过先测试程序部件再测试部件组装,使集成测试变得更加简单。
业界对于人工集成测试的必要性存在较大争议。尽管精心设计的单元测试体系看上去实现了集成测试,因为集成测试需要人为评估一些人为因素才能证实的方面,单元测试替代集成测试不可信。一些人认为在足够的自动化测试系统的条件下,人力集成测试组不再是必需的。事实上,真实的需求最终取决于开发产品的特点和使用目标。另外,人工或手动测试很大程度上依赖于组织的可用资源。
总结:
- 在开发过程中,可以屏蔽部分外部依赖,验证程序的正确性。
3、文档记录
单元测试提供了系统的一种文档记录。借助于查看单元测试提供的功能和单元测试中如何使用程序单元,开发人员可以直观的理解程序单元的基础API。
单元测试具体表现了程序单元成功的关键特点。这些特点可以指出正确使用和非正确使用程序单元,也能指出需要捕获的程序单元的负面表现(译注:异常和错误)。尽管很多软件开发环境不仅依赖于代码做为产品文档,在单元测试中和单元测试本身确实文档化了程序单元的上述关键特点。
另一方面,传统文档易受程序本身实现的影响,并且时效性难以保证(如设计变更、功能扩展等在不太严格时经常不能保持文档同步更新)。
- 有助于他人或者自己(在自己遗忘的情况下)快速了解业务逻辑。
4、表达设计(测试驱动开发)
在测试驱动开发的软件实践中,单元测试可以取代正式的设计。每一个单元测试案例均可以视为一项类、方法和待观察行为等设计元素。
不同于其他基于图的设计方法,用单元测试表达设计有一项显著优点:设计文档(单元测试本身)可以用于验证程序实现符合设计。UML可能会遇到这样的问题:尽管图上一个类被命名为Customer,但开发人员可以称其为Wibble,而且系统中没有任何地方会显示出这个差异。基于单元测试设计方法,开发人员不遵循设计要求的解决方案永远不会通过测试。
当然,单元测试缺乏图的可读性,但UML图可以在自由工具(通常可从IDE扩展获取)中为大多数现代程序语言生成UML图,很难要求采购昂贵的UML设计套装软件。自由工具,类似于基于xUnit框架的工具,测试结果输出到一些可生成供人工识读的图形化工具系统中去。
总结:
- 测试驱动开发,利用单元测试表达接口设计
如何写好单元测试?
虚假的标题:如何写好单元测试
真实的标题:如何写容易测试的代码
编写单元测试没有任何技巧,只有编写可测试代码的技巧。
为什么说编写单元测试没有任何技巧呢?因为单元测试的本质就是验证你代码的正确性,代码如果写的不好、不可被单独测试,那么单元测试也无能为力。单元测试本来就是枯燥的事情,并没有任何捷径。
一、为什么写容易测试的代码很重要?
渣男:我在忙,晚点聊
暖男:我正在开会,大概6.30左右结束,到时候回复你。
容易测试的代码通常意味:
- 更容易理解:更容易理解意图,写出有效的测试case
- 更好维护:容易单元测试的代码一般解耦做的更好,便于后期维护
于是乎,容易测试——》容易理解——》容易维护,三者形成了闭环。
二、导致测试不好写的几类问题
1、复杂的构造函数 or 无参的构造函数
2、混合业务逻辑和构建依赖对象的逻辑
3、太多、嵌套太深的条件判断语句
4、不必要的、太深的继承
5、在一个方法内混合纯计算与IO
6、不写毫无价值的单元测试
三、让测试更简单的一些建议
- 使用依赖注入器(dependency injector), 而不是尝试手动创建 object graph
- 坚持单一原则:努力做到能用一句话描述一个 method 的功能
- 坚持最少知识原则:只使用直接依赖对象的API,不使用间接依赖对象的API(不要有 .getX().doY())
- 警惕使用 static 和 new 关键字
- 优先使用组合而不是继承
- 使用多态替代复杂的条件语句
- 尽量写无副作用的函数
- 用 give-when-then(准备、执行、校验) 为每个 test case 做三段式注释
一些 mock 技巧
一、推荐使用构造器注入
推荐阅读文章:
spring 官方文章:Setter injection versus constructor injection and the use of @Required
腾讯云专栏:【Spring】浅谈spring为什么推荐使用构造器注入
使用构造器注入的好处,总结:
- 保证依赖不可变(final关键字)
- 保证依赖不为空(省去了我们对其检查)
- 保证返回客户端(调用)的代码的时候是完全初始化的状态
- 避免了循环依赖
- 提升了代码的可复用性
对于单元测试的好处:更容易的 mock 依赖对象
二、如何mock @Value注解修饰的变量
参考文章:
三、如何mock静态方法
参考文章:
四、如何校验没有返回值的方法
校验关键逻辑是否都被调用。
附:单元测试神器
一、前言
说起来,这是一个悲伤的故事,公司要求今年第四季度结束前,所有项目的单元测试覆盖率要达到50%。
于是乎,我开始炸毛了,新项目进度紧张吧,编写单元测试非常耗时,而且还有一堆老旧项目单元测试覆盖率基本为0%,这要慢慢写,不得补到天荒地老?????
而且在编写单元测试的过程中,其实是有很大一部份内容是重复劳动,作为一名开发者,对于这种重复且量大的工作,肯定第一个想法就是可不可以通过技术手段,减少这部分工作的耗时,以及操作步骤。
二、神器介绍
https://weirddev.com/testme/
https://squaretest.com/ 付费
http://www.evosuite.org/
https://randoop.github.io/randoop/
三、神器之TestMe
一、介绍
TestMe 是一款 IntelliJ 插件,用于生成单元测试样板代码。
TestMe 支持的单元测试模板:
1 |
|
主要特征
- 在 Java、Groovy 或 Scala 中自动生成单元测试代码
- 为测试类的非原始字段生成模拟
- 支持 Mockito 作为 Mock 框架
- 支持 JUnit4、JUnit5、TestNG 和 Spock 框架
- 为每个可访问的非私有方法生成测试方法,不包括 setter/getter
- 为测试方法生成默认输入参数
- 生成测试结果断言表达式
- 支持的目标测试类语言: Java、Groovy、Scala。
示例:
二、安装
1、 从 IntelliJ IDEA 中安装最新版本,并重启IDEA
在IDEA菜单:Preferences(Ctrl+ Shift+ S) - > Plugins- > Browse repositories…- >搜索:TestMe- >Install Plugin
2、生成测试代码模板
1)选择测试类:
选择一个需要测试的类 -》 alt + Insert 跳出如下选择框
2)选择测试工具:
并选择 TestMe 后跳出如下选项框
选择你最常用的测试工具即可,我比较常用的是 Unit4 + Mockito,所以我选择的是第一个。
3)根据生成的测试模板代码修改测试用例
ps:
默认值是为测试的方法参数自动生成的。目前,此行为不可配置。
作者解释:从某种意义上说,Groovy 测试生成器更健壮,可以使用映射构造函数(在适用时)通过内联 setter 初始化对象。Java 测试生成器尚不支持此类匹配功能,因为为测试参数初始化分配给局部变量会使测试生成模板复杂化。就我个人而言,在进行单元测试时,我总是建议使用 Groovy 而不是 Java。如果您还没有过渡到 Groovy作为您选择的测试代码语言 - 现在是开始的好时机:)
参考文章:
- 本文作者: th3ee9ine
- 本文链接: https://www.blog.ajie39.top/2021/10/25/如何写好单元测试(另有神器分享)/
- 版权声明: 本博客所有文章除特别声明外,均采用 LICENSE 下的许可协议。转载请注明出处!