1. 背景
单元测试的收益
- 单元测试能更快地发现问题
- 单元测试的性价比很高,因为发现错误越晚,修复的代价越高
- 有助于源码的优化,可以放心进行重构
单元测试的痛点
- 单元测试浪费了太多的时间,写单元测试的时间比写代码的时间还长
- 代码逻辑过于复杂,单元测试很难写
- 部分项目主要和数据库交互,造数据复杂
测试金字塔
2. Spock
Spock 是一个 Java 和 Groovy 应用程序的测试框架。Spock 结合 Groovy 动态语言的特点,提供了各种标签,并采用简单、通用、结构化的描述语言 让编写测试代码更加简洁、高效。
Junit 单纯用于测试,不提供 Mock 功能。Mockito 虽然可以把接口等依赖屏蔽掉,但是他们之间需要整合,语法繁琐。
Spock 自带 Mock 功能,使用简单方便,并且提供了规范化的描述,定义了多种标签(given、when、then、where 等),去描述代码 ”应该做什么“, “输入条件是什么”,“输出是否符合预期”,从语义层面规范代码的编写。再加上 Groovy 动态语言的强大语法,能写出简洁的测试代码。
3. 内存数据库H2
H2 非常适合在测试程序中使用,程序关闭时自动清理数据,H2 数据库表结构和表数据可以通过spring.datasource进行指定。单元测试中使用非常地简单, 仅需要修改 jdbc 连接即可。
引入依赖
1
2
3
4
5
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
数据源连接
1
2
3
4
5
6
7
spring.datasource.druid.url=jdbc:h2:mem:goods;INIT=CREATE SCHEMA IF NOT EXISTS goods\\;SET SCHEMA goods
spring.datasource.druid.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# 开启控制台需要启动web
spring.h2.console.enabled=true
spring.h2.console.path=/h2
数据初始化
1
2
3
spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql
spring.datasource.initialization-mode=always
某些 MySQL 语法对于H2来说是不支持的。 初始化 H2 的 Schema 最好能给每个 not null 字段都加上默认值。
4. 数据层单元测试框架 DbUnit
DbUnit 基于 xml 优雅地构造测试数据集,例如:
1
2
3
4
5
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<user id="1" username="test-user1" password="test-user1" name="test-user1"/>
<user id="2" username="test-user2" password="test-user2" name="test-user2"/>
</dataset>
user代表表名,后面的键值对为列名和对应的值。 但是通过xml构造数据集还是太麻烦,加入对于 Spock 扩展的处理框架,使用注解的方式构造测试数据集。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ExtensionAnnotation(DbunitExtension)
@interface Dbunit {
/**
* <pre>
* @Dbunit(content = {
* t_goods_price_discount(goods_attr_id: 1, tenancy_threshold: 1, price_discount: 10)
* t_goods_price_discount(goods_attr_id: 2, tenancy_threshold: 2, price_discount: 20)
* T_GOODS_PRICE (goods_attr_id: 1, start_date: '2022-03-18', end_date: '2022-03-25', price: 10, km_price: 20, min_days: 4)
* })
* </pre>
*/
Class<? extends Closure> content() default Closure.class;
String schema() default "";
}
5. 单元测试覆盖率
在pom文件里引用Jacoco的插件:jacoco-maven-plugin,然后执行mvn package 命令, 成功后会在target目录下生成单元测试覆盖率的报告,点开报告找到对应的被测试类查看覆盖情况。
6. Groovy在单元测试相比于Java的优势
- 在单元测试环节,可以对一些访问控制权限进行放开,例如,构造函数中指定字段名和字段值的方式创建对象,更加的方便、简洁。