一、什么是单元测试
单元测试是指对软件中的最小可测试单元进行检查和验证。单元在质量保证中是非常重要的环节,根据测试金字塔原理,越往上层的测试,所需的测试投入比例越大,效果也越差,而单元测试的成本要小的多,也更容易发现问题。 然而然而我们大多数人是不会写单元测试的,甚至不知道单元测试究竟是为了干什么。单元测试几个典型场景如下:
- 开发前写单元测试,通过测试描述需求,由测试驱动开发。
- 在开发过程中及时得到反馈,提前发现问题。
- 应用于自动化构建或持续集成流程,对每次代码修改做回归测试(我们要继承Bamboo)。
- 作为重构的基础,验证重构是否可靠。
二、单元测试核心原则
优秀的单元测试可以很好的在早起捕捉到导致系统奔溃的问题,而编写一个优秀的单元测试要遵循一定的原则,测试相关原则有很多,这里总结了我个人认为比较重要的几条原则:
- 自动化原则:单元测试应该是全自动执行的,利用断言Assert进行结果验证,自动给出结果。
- 独立原则:保持单元测试的独立性。单元测试用例之间互不依赖,对外部资源(网络、服务、中间件等)无依赖,也不能依赖执行的先后次序。
- 可重复原则:单元测试是可以重复执行的,每次运行的结果应该是相同的。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
- 快速原则:单元测试应该是可以快速运行的,在各种测试方法中,单元测试的运行速度是最快的,保证开发人员可以对每一个小变更快速测试,不应该启动加载整个环境。
- 全面性原则:除了正确的输入得到预期的结果,还需要强制错误信息输入得到预期的结果,为了系统的鲁棒性,应加入边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
- 专注性原则:保证测试小而专注,有助于精确定位问题。单测粒度一般是方法级别,测试某个具体行为。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
三、单元测试痛点
对于单元测试而言,我们在编写的过程中通常会遇到如下几类问题:
- 介绍单元测试相关的资料比较少,大部分资料介绍单元测试仅仅介绍了一个JUnit测试函数类怎么写,Assert断言怎么写就结束了,实际应用到复杂的系统工程里应该怎么做很少涉及。
- 微服务架构下,系统依赖各种外部接口和中间件,要写一个纯粹的无依赖的单元测试是很困难的。大部分java开发同学写单元测试,都是基于Junit框架启动整个Spring上下文环境来编写单元测试,因此完美的绕开了上面提到的几个单测原则。
- 单元测试难以理解和维护,无论什么单元测试框架,如果要维持较高的单元测试覆盖率,均要有约三倍于业务代码,再有单测代码本身不像业务代码直观,还有对单测代码可读性不够重视的习惯,导致单测难以阅读和维护。
基于以上几个痛点,经过多方考量权衡,我在项目中使用spock框架作为单测主要框架进行了单元测试的编写,后续单测介绍大部分为Spock写法。将单元测试实践分为以下3篇: First Blood —— 业务逻辑层测试实践 Double Kill —— Controller层测试实践 Triple Kill —— 数据层测试实践
四、前期知识储备
4.1 Groovy语言基础
Spock是基于Groovy脚本语言的测试框架,所以再正式开始之前,我们有必要先简单了解一下语法基础,使用非常简单,开发人员几分钟上手不是问题。详细教程可参考:https://www.w3cschool.cn/groovy
Groovy和Java的语法对比
- 不需public修饰符,Groovy的默认访问修饰符就是public,如果Groovy类成员需要public修饰,则根本不用写它
- 不需要构造函数,不再需要程序员声明任何构造函数,因为实际上只需要两个构造函数(1个不带参数的默认构造函数,1个只带一个map参数的构造函数–由于是map类型,通过这个参数可以构造对象时任意初始化它的成员变量)
- 数组(列表)用中括号表示,[v1, v2, v3]
- 字典映射使用冒号分隔键值对,[k1: v1, k2: v2, k3: v3],初始化一个空的Map,写法为[:]
- 支持类型自动推断,使用关键字 def,比如 def var = “value”; 也可以使用java的方式显示定义String var = “value”
- for-in循环示例,int[] array = [0,1,2,3], for(int i in array) { println(i) }
- 支持范围类型,比如 1…3 相当于 Java 中的数组 { 1, 2, 3 };1.2…3 相当于 Java 中的数组 { 1.2, 2.2 };1…?? 相当于数组 { 1, 2 };‘a’…‘z’ 相当于所有小写字母的字符集合。范围类型是可迭代的。
- Groovy 中的除法默认是小数除法,不是整数除法。
4.2 单测插件配置
<!-- 生成单元测试数据插件 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
<!-- 生成JaCoCo覆盖率数据插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<!-- attached to Maven test phase -->
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
|