单元测试实践三板斧-Double Kill

注意:本篇教程是采用Junit的写法进行举例的,在单测实践过程中,为了验证不同框架混合开发可行性,导致不同层使用的框架不同,还未进行统一。

一、数据层单元测试

关于数据层的单元测试,为了遵从单元测试的原则,可能我们需要脱离数据库依赖;假设我们想这么做,那么就按照前面讲的业务逻辑层单元测试的方法,我们同样把数据库依赖Mock掉就可以了。

但是,如果我们这么做,那么大多情况下我们的数据层是没有什么逻辑的,数据依赖都被Mock了,那么单测可验证的东西就太少了,所以我建议可以依赖测试数据库或者是依赖内存型数据库H2进行单元测试,这样我们就可以快速的验证编写的Sql是否有问题,也可以依赖数据库中的数据驱动测试的执行。

二、数据层单测写法

我们只测试数据层,一定不要使用@SpringBootTest注解,在这里我们认识一个新的注解@MybatisTest,使用这个注解编写单元测试可以达到我们只加载数据层Bean的目的,不用加载Spring项目整体上下文。下面通过示例来看一下如何使用:

引入依赖

<!-- @MybatisTest的依赖 -->
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter-test</artifactId>
   <version>2.0.1</version>
   <scope>test</scope>
</dependency>

新建单测环境配置文件
单测配置文件
pom配置文件中指定了默认启用的环境为dev配置(也可以单测中用@ActiveProfiles注解指定profile),所以单测在test文件夹下的resources目录新建一个application-dev.properties的配置文件即可默认加载。配置文件中指定单测使用的测试数据库,同时在集成Bamboo平台时流水线的绑定资源池选择测试环境即可连通

<!-- ################################ 开发,测试,生产环境配置 Start #####################-->
<profiles>
    <!-- 开发环境 -->
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <env>dev</env>
        </properties>
    </profile>
    <!-- 预发测试环境 -->
    <profile>
        <id>yf</id>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
        <properties>
            <env>yf</env>
        </properties>
    </profile>
    <!-- 生产环境 -->
    <profile>
        <id>pro</id>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
        <properties>
            <env>pro</env>
        </properties>
    </profile>
</profiles>
<!-- ################################ 开发,测试,生产环境配置 End #####################-->

测试库的配置,最简配置即可,单测默认不会使用数据库连接池等复杂配置

### mysql datasource config ###
spring.datasource.url=jdbc:mysql://xxx.xxx.xxx.xxx:3306/databasename?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root

封装数据层单测基类

@MybatisTest
@MapperScan("com.jd.eco.market.mysql.mapper")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public abstract class DatabaseAbstractTest extends Specification {
}
@Slf4j
public class StrategyMapperTest extends DatabaseAbstractTest {
    @Resource
    private StrategyMapper strategyMapper;

    @Test
    void selectStrategyByPoolId() {
        List<StrategyDO> strategyDOList = strategyMapper.selectStrategyByPoolId(1L);
        assertEquals(strategyDOList.size(), 4);
    }
}

执行结果
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)的作用是使用自定义的数据源,而非使用自动配置的嵌入式内存数据源
@MapperScan(“com.jd.eco.market.mysql.mapper”) 用来指定mapper的位置,如果mapper使用@Mapper注解方式编码则可以忽略此注解。

三、内存数据库使用

如果我们想要保证单测可以在离线的状态下运行,同时避免污染测试数据,我们可以在单测中使用内存数据库H2。

H2内存数据库介绍
H2 是一个用 Java 开发的嵌入式数据库,它本身只是一个类库,即只有一个 jar 文件,可以直接嵌入到应用项目中。H2 主要有如下三个用途:

  1. 可以同应用程序打包在一起发布,这样可以非常方便地存储少量结构化数据,最常使用。
  2. 用于单元测试,启动速度快,而且可以关闭持久化功能,每一个用例执行完随即还原到初始状态。
  3. 作为缓存,即当做内存数据库,作为NoSQL的一个补充。当某些场景下数据模型必须为关系型,可以拿它当Memcached使,作为后端MySQL/Oracle的一个缓冲层,缓存一些不经常变化但需要频繁访问的数据,比如字典表、权限表。

H2支持三种模式:

  1. 内存模式
  2. 嵌入式模式
  3. 服务器模式

我们在此就是使用H2来做单元测试,使用他的内存模式,

引入依赖

<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <version>1.4.200</version>
  <scope>test</scope>
</dependency>

数据链接配置

Springboot环境可以省略这部分配置,如果不配置会有自己的一套默认配置的内存数据库,其它环境手动配置如下:

spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL //h2数据库的连接地址,数据库名称自定义
spring.datasource.driver-class-name=org.h2.Driver // 配置JDBC Driver
spring.datasource.username=root // 用户名,自定义
spring.datasource.password=123456 //数据库密码,自定义

/** 注:连接参数详解
DB_CLOSE_DELAY:要求最后一个正在连接的连接断开后,不要关闭数据库
MODE=MySQL:兼容模式,H2兼容多种数据库,该值可以为:DB2、Derby、HSQLDB、SQLServer、MySQL、Oracle、PostgreSQL
AUTO_RECONNECT=TRUE:连接丢失后自动重新连接
AUTO_SERVER=TRUE:启动自动混合模式,允许开启多个连接,该参数不支持在内存中运行模式
TRACE_LEVEL_SYSTEM_OUT、TRACE_LEVEL_FILE:输出跟踪日志到控制台或文件, 取值0为OFF,1为ERROR(默认值),2为INFO,3为DEBUG
SET TRACE_MAX_FILE_SIZE mb:设置跟踪日志文件的大小,默认为16M
*/

数据初始化配置

在测试过程中,一般都需要对数据库进行初始化操作。这些操作基本分为两类,一个是数据库的schema操作,如建表,建sequence,用户授权等等。第二个是数据库的数据初始化,一般是把一些公共的测试数据在这个时机导入。依旧是在application.properties文件中进行配置

### 每次启动程序,对数据库的表结构
spring.datasource.schema=classpath:sql/strategy_ddl.sql
### 每次启动程序,导入数据
spring.datasource.data=classpath:sql/strategy_data.sql

开发单元测试的过程中,sql配置文件放在指定目录下,例如:

src/test/resources/sql/strategy_ddl.sql,
src/test/resources/sql/strategy_data.sql 

sql配置文件内容如下:

// 注意:建表语句后面不能有ENGINE=InnoDB DEFAULT CHARSET=utf8,不然将会报错
CREATE TABLE promotion_timeline (
  id bigint(20) NOT NULL COMMENT '主键id',
  promo_festival int(11) DEFAULT NULL COMMENT '促销节日编码',
  promo_stage int(11) DEFAULT NULL COMMENT '促销阶段',
  timeline_name varchar(32) DEFAULT NULL COMMENT '时间轴名称',
  begin_time datetime DEFAULT NULL COMMENT '开始时间',
  end_time datetime DEFAULT NULL COMMENT '结束时间',
  remark varchar(256) DEFAULT NULL COMMENT '备注说明',
  is_delete tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除',
  create_user varchar(32) NOT NULL COMMENT '创建人',
  created datetime NOT NULL COMMENT '创建时间',
  update_user varchar(32) NOT NULL COMMENT '更新人',
  modified datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (id)
);
INSERT INTO promotion_timeline (id, promo_festival, promo_stage, timeline_name, begin_time, end_time, remark, is_delete, create_user, created, update_user, modified)
VALUES
	(201, 20200618, 1, '预热期', '2020-05-21 00:00:00', '2020-05-31 23:59:59', NULL, 0, 'system', '2020-05-14 11:43:15', 'system', '2020-05-14 11:43:15'),
	(202, 20200618, 2, '专场期', '2020-06-01 00:00:00', '2020-06-15 23:59:59', NULL, 0, 'system', '2020-05-14 11:43:15', 'system', '2020-05-14 11:43:15'),
	(203, 20200618, 3, '高潮期', '2020-06-16 00:00:00', '2020-06-18 23:59:59', NULL, 0, 'system', '2020-05-14 11:43:15', 'system', '2020-05-14 11:43:15'),
	(204, 20200618, 4, '返场期', '2020-06-19 00:00:00', '2020-06-21 23:59:59', NULL, 0, 'system', '2020-05-14 11:43:15', 'system', '2020-05-14 11:43:15'),
	(401, 20200601, NULL, '新品营销中心', '2020-06-01 00:00:00', '2099-01-01 00:00:00', NULL, 0, 'system', '2020-06-11 15:04:45', 'system', '2020-06-11 15:04:45'),
	(601, 20200701, NULL, '日常策略', '2020-06-25 00:00:00', '2099-01-01 00:00:00', NULL, 0, 'system', '2020-06-27 18:03:36', 'system', '2020-06-27 18:03:36'),
	(1316717177201463298, 20200702, 1, '线上验证-wl', '2020-10-15 00:00:00', '2020-10-30 23:59:59', NULL, 1, 'wangling113', '2020-10-15 20:26:53', 'wangling113', '2020-10-15 20:27:47'),
	(1316736623085326338, 20200702, 1, '预售期', '2020-10-21 00:00:00', '2020-10-31 23:59:59', NULL, 0, 'jiangjian45', '2020-10-15 21:44:09', 'zhangqiu8', '2020-11-13 14:29:45'),
	(1316736623114686466, 20200702, 2, '专场期', '2020-11-01 00:00:00', '2020-11-08 23:59:59', NULL, 0, 'jiangjian45', '2020-10-15 21:44:09', 'zhangqiu8', '2020-11-13 14:29:45'),
	(1316736623165018113, 20200702, 3, '高潮期', '2020-11-09 00:00:00', '2020-11-11 23:59:59', NULL, 0, 'jiangjian45', '2020-10-15 21:44:09', 'zhangqiu8', '2020-11-13 14:29:45');

单元测试编写
使用内存数据库我们只需删除这个注解@AutoConfigureTestDatabase或者不指定"replace = AutoConfigureTestDatabase.Replace.NONE"即可。

@MybatisTest
@MapperScan("com.jd.eco.market.mysql.mapper")
@AutoConfigureTestDatabase
public abstract class DatabaseAbstractTest extends Specification {
}
public class PromotionTimelineMapperTest extends DatabaseAbstractTest {
    @Resource
    private PromotionTimelineMapper promotionTimelineMapper;

	// @Sql({ "classpath:sql/strategy_data.sql" }) 如果单测需要特定数据可以通过这种方式加载
    @Test
    void selectBaseByFestival() {

        List<PromotionTimelineDO> timelineDOList = promotionTimelineMapper.selectBaseByFestival(20200618);
        assertEquals(timelineDOList.size(), 4);
    }
}

如下图所示,启动单测时加载了h2内存数据库
运行示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值