单测写得好,年终少不了———聊聊如何用单元测试惊艳老板

你是否有这样的窘境,在执行一次单元测试后,再次执行却会失败。这是因为上一次的单元测试修改了数据,导致下一次执行时业务逻辑不同,必须重新构建数据才能再次执行单元测试。每写一次单元测试只能使用一次,耗费时间与精力,久而久之,我们就不愿意维护单元测试了。

这篇文章将告诉你如何优雅的解决这类问题。

单元测试不应该依赖真实环境的数据库,而应该使用嵌入式数据库。通过使用嵌入式数据库,每次测试创建或修改的数据都不会对真实环境造成污染,数据将被写入全新的数据库,从而保证每次单元测试执行前的条件完全一致。(如果使用测试环境的真实数据库,两次测试执行之间数据库可能已经发生了变化,导致测试结果不一致)

嵌入式中间件 提供了非常理想的隔离环境。保证单测的结果不会被历史数据影响。

H2 内存数据库介绍

H2 数据库是一个用 Java 开发的嵌入式(内存级别)数据库,它本身只是一个类库,也就是只有一个 jar 文件,可以直接嵌入到项目中。H2数据库又被称为内存数据库,因为它支持在内存中创建数据库和表。所以如果我们使用H2数据库的内存模式,那么我们创建的数据库和表都只是保存在内存中,一旦应用重启,那么内存中的数据库和表就不存在了。

H2数据库由于只是一个jar包,和Java应用运行在jvm中,所以进程终止,数据就没了,下次再启动,还是全新的环境。这些特性非常适合应用于单元测试。

引入H2,单测可以不再依赖测试环境数据库,每次单测执行也都不会修改测试数据库。所以单测可以重复执行。方便调试代码和自动化回归测试。(调用下游的RPC如果是写接口依然需要mock!!!)

接入方式

pom 依赖

pom依赖等级是test,完全不会影响到正式代码。无须担心线上环境的风险

xml 复制代码
<dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>test</scope>
</dependency>

替换数据源

单元测试执行时,如果没有特别修改数据源,默认会使用测试环境数据库。为了使用H2数据库,需要在单测执行时替换原有的数据源。

在 Java 中,数据源都是java.sql.DataSource的实现类。访问数据库都是基于DataSource接口,因此只需要将原来的DataSource对象替换为H2 DataSource对象即可。

一般情况下,项目都是基于Spring框架开发,数据源DataSource会由Spring进行托管,通过Spring获取DataSource的bean进行访问。所以问题可以简化为,在项目启动时将原DataSource bean替换为H2 DataSource bean。

使用 BeanPostProcessor

Java 复制代码
public interface BeanPostProcessor {
   Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
   Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

Spring提供了该接口,允许开发者在Bean实例化完成后对bean进行特殊代理。postProcessAfterInitialization 方法是有返回值的,如果不需要对bean进行特殊处理,则直接返回原始的bean,如果需要特殊处理,则返回新的bean。

在下面的代码示例中,我展示了如何创建一个新的H2数据源,并在postProcessAfterInitialization方法中将原始数据源替换为H2数据源。这样系统就不会再访问测试数据库,而是使用新的H2数据库。这种方法的好处是,无需修改正式代码,也无需添加额外的配置。

Java 复制代码
public static class MyBeanPostProcessor implements BeanPostProcessor {

		@Override
		public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
			return bean;
		}

		@Override
		public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
			if (beanName.equals("testDataSource")) {//xml 中配置的数据源 Bean 名称
				return initDataSource();
			}

			return bean;
		}
	}

	public static DataSource initDataSource() {
		EmbeddedDatabaseBuilder builder = (new EmbeddedDatabaseBuilder())
				.setType(EmbeddedDatabaseType.H2)  
				.setName("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;mode=MySQL;TRACE_LEVEL_SYSTEM_OUT=2") //不用改动
				.setScriptEncoding("UTF-8")
				.ignoreFailedDrops(true);
		builder.addScripts("h2_db.schema", "h2_db.data");  // 在resources 目录下 创建Sql脚本
		DataSource dataSource = builder.build();
		return dataSource;
	}

配置数据库 schema 文件

在创建 H2 数据库时,需要执行包含创建数据库的 SQL 脚本。下面我给出一个简单的例子,大家可以根据实际需求拷贝测试环境中创建表的语句。

h2_db.schema如下。

sql 复制代码
SET MODE MYSQL;
CREATE TABLE `order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '用户ID',
  `phone` varchar(50)  NOT NULL DEFAULT '' COMMENT '用户手机号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;

值得注意的是 H2 数据库的关键字和 MySQL 并不完全一致,需要注意下

  1. COLLATE utf8mb4_unicode_ci 这类关键词要去掉。
  2. 注释:不支持表级别的Comment
  3. 索引:H2中的索引是数据库内唯一,MySQL中的索引是每张表唯一
  4. CURRENT_TIMESTAMP:H2不支持记录更新时自动刷新字段时间,也就是不支持语句ON UPDATE CURRENT_TIMESTAMP
  5. JSON:H2不兼容MySqlJSON字段类型,建议换成longtext
  6. 双引号转义:H2不兼容双引号转义",直接写"即可(在插入JSON 字符串时注意) 。也使用线上工具,可以去掉多余的转义 www.lzltool.com/Escape/Stri...

在单测启动类添加 BeanPostProcessor

上述内容中,我们已经编写好了 BeanPostProcessor,接下来的一步是将它注入到Spring中。可以使用单元测试注解来实现将该类注入到Spring中。

例如,在单元测试启动基类中添加如下注解:

Java 复制代码
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {MyBeanPostProcessor.class})
@SpringBootTest(classes = XXXAppStarter.class,webEnvironment =
        SpringBootTest.WebEnvironment.NONE)
public abstract class BaseTest {

}

这样单测环境启动项目时,测试环境数据库就会被替换为 H2 数据库了。

快去试试吧!如果有问题,可以在文章下评论留言。

聊点其他的事情。

最近我找了出版社的编辑和几位大佬聊了一件事------------出本关于电商业务架构的书,分享一下 商品、营销、订单、会员、支付各细分业务系统的建设经验。最后因法律风险这个计划将长期搁置。为什么出本书会涉及法律问题呢?

因为业务系统架构不可避免的要说明实际的业务背景和痛点,这一定会涉及公司的商业机密。即使做了大量脱敏工作,也无法保证公司不找茬。这也很容易理解,你凭什么把公司的商业机密和业务背景作为代价,去宣传、出书和赚钱呢?

换句话说现有的出版物中很少涉及业务背景和业务逻辑,这类被阉割的内容千篇一律大同小异。之所以你看完了,觉得没学到啥,因为本来就没啥干货。真正有价值的内容,大家不敢在书里发布。

所以接下来,我将继续在掘金认真分享在互联网大厂的业务系统建设经验,分享更多的干货和经验,也尽可能做到业务脱敏,不损害公司的利益。 大家可以关注我,随时看到不注水的经验分享

相关推荐
大厂码农老A5 分钟前
面试官:“聊聊你最复杂的项目?” 为什么90%的候选人第一句就栽了?
java·面试
爱读源码的大都督12 分钟前
Java已死?别慌,看我如何用Java手写一个Qwen Code Agent,拯救Java
java·人工智能·后端
lssjzmn12 分钟前
性能飙升!Spring异步流式响应终极指南:ResponseBodyEmitter实战与架构思考
java·前端·架构
LiuYaoheng28 分钟前
【Android】View 的基础知识
android·java·笔记·学习
勇往直前plus36 分钟前
Sentinel微服务保护
java·spring boot·微服务·sentinel
星辰大海的精灵36 分钟前
SpringBoot与Quartz整合,实现订单自动取消功能
java·后端·算法
小鸡脚来咯39 分钟前
一个Java的main方法在JVM中的执行流程
java·开发语言·jvm
江团1io040 分钟前
深入解析三色标记算法
java·开发语言·jvm
天天摸鱼的java工程师1 小时前
RestTemplate 如何优化连接池?—— 八年 Java 开发的踩坑与优化指南
java·后端
你我约定有三1 小时前
java--泛型
java·开发语言·windows