专栏首页7DGroup走进Java接口测试之从0到1搭建数据驱动框架(多数据源和业务持久层)

走进Java接口测试之从0到1搭建数据驱动框架(多数据源和业务持久层)

前言

在前三篇文章中,我们分别介绍了需求、设计、以及测试管理的实现功能,本篇我们一起来实现多数据源和业务持久层开发。

走进Java接口测试之从0到1搭建数据驱动框架(需求篇)

走进Java接口测试之从0到1搭建数据驱动框架(设计篇)

走进Java接口测试之从0到1搭建数据驱动框架(用例管理)

全部代码骨架结构

├─logs
│  └─spring-boot-logback             # 日志文件
│          all_api-test-logback.log # 所有日志
│          err_api-test-logback.log # 错误日志
├─src
│  ├─main
│  │  ├─java
│  │  │  └─com
│  │  │      └─zuozewei
│  │  │          └─springbootdatadrivendemo
│  │  │              │  SpringbootDataDrivenDemoApplication.java # 启动类
│  │  │              │  
│  │  │              ├─db
│  │  │              │  ├─auto      # 存放MyBatis Generator生成器生成的数据层代码,可以随时删除再生成
│  │  │              │  │  ├─mapper # DAO 接口
│  │  │              │  │  └─model  # Entity 实体
│  │  │              │  └─manual    # 存放自定义的数据层代码,包括对MyBatis Generator自动生成代码的扩展
│  │  │              │      ├─mapper # DAO 接口
│  │  │              │      └─model  # Entity 实体
│  │  │              ├─handler  # 数据转换
│  │  │              └─service # 业务逻辑
│  │  │                  └─impl # 实现类
│  │  │                          
│  │  └─resources
│  │      │  application.yml       # 全局配置文件
│  │      │  generatorConfig.xml # Mybatis Generator 配置文件
│  │      │  logback-spring.xml     # logback 配置文件
│  │      │  spy.properties      # P6Spy 配置文件
│  │      │  
│  │      ├─db
│  │      ├─mapper
│  │      │  └─com
│  │      │      └─zuozewei
│  │      │          └─springbootdatadrivendemo
│  │      │              └─db
│  │      │                  ├─auto      # 存放MyBatis Generator生成器生成的数据层代码,可以随时删除再生成
│  │      │                  │  └─mapper # 数据库 Mapping 文件
│  │      │                  │          
│  │      │                  └─manual    # 存放自定义的数据层代码,包括对MyBatis Generator自动生成代码的扩展
│  │      │                      └─mapper # 数据库 Mapping 文件
│  │      └─testng
│  │          │  APICollection-TestSuite.xml # 所用测试用例集
│  │          └─jdbcbapi
│  │                  jdbcAPI-TestSuite.xml  # 某API测试用例集
│  │                  
│  └─test
│      └─java
│          └─com
│              └─zuozewei
│                  └─springbootdatadrivendemo
│                      └─demo   # 接口测试用例
├─pom.xml

多数据源

上面介绍了我们的用例管理使用的是 MySQL 数据库,而本文我们演示的是业务数据库使用的是 H2,那么必然的我们需要面对处理多数据源的问题。而在需求篇我们分析过了传统方式,确实可用,不足在于需要根据不同数据源建立不同的 package,一旦数据源发生变更,需要更改所在的 package。也看过了动态数据源,不能满足我们多数据源的需求。

而经过查找,我们发现一个开源的项目,即 dynamic-datasource-spring-boot-starter 可以满足我们的需求。

工具简介

dynamic-datasource-spring-boot-starter 是一个基于 springboot 的快速集成多数据源的启动器。其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.0.x。 特性:

  • 数据源分组,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 内置敏感参数加密和启动初始化表结构 schema 数据库 database。
  • 提供对 Druid,Mybatis-Plus,P6sy,Jndi 的快速集成。
  • 简化 Druid 和 HikariCp 配置,提供全局参数配置。
  • 提供自定义数据源来源接口(默认使用 yml 或 properties 配置)。
  • 提供项目启动后增减数据源方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 使用 spel 动态参数解析数据源,如从 session,header 或参数中获取数据源。(多租户架构神器)
  • 提供多层数据源嵌套切换。(ServiceA >>> ServiceB >>> ServiceC,每个 Service 都是不同的数据源)
  • 提供 不使用注解 而 使用 正则 或 spel 来切换数据源方案(实验性功能)。

约定:

  • 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
  • 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
  • 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
  • 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
  • 使用 @DS 切换数据源。@DS 可以注解在方法上和类上,同时存在方法注解优先于类上注解。

常见示例:

# 多主多从                      纯粹多库(记得设置primary)                   混合配置
spring:                               spring:                               spring:
  datasource:                           datasource:                           datasource:
    dynamic:                              dynamic:                              dynamic:
      datasource:                           datasource:                           datasource:
        master_1:                             mysql:                                master:
        master_2:                             oracle:                               slave_1:
        slave_1:                              sqlserver:                            slave_2:
        slave_2:                              postgresql:                           oracle_1:
        slave_3:                              h2:                                   oracle_2:

我们这里使用需要注意点有:

  • 与 Druid,P6sy 快速集成;
  • 对 Druid 进行全局参数配置;
  • 只使用 切换数据源 这件核心的事情,并不涉及具体其他操作,切换了数据源可以做任何 CRUD;
  • 设置默认的数据源;
  • 使用 @DS 切换数据源。@DS 同时注解在方法上和类上。

配置数据源

引包:

<!--快速集成多数据源的启动器-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>2.5.6</version>
</dependency>

配置 application.yml 文件:

spring:
  datasource:
    dynamic:
      primary: mysql    # 设置默认的数据源或者数据源组,默认值即为 master
      strict: false    # 设置严格模式,默认 false 不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
      datasource:
        mysql_1:     # 测试用例库
          username: zuozewei
          password: zuozewei
          url: jdbc:mysql://172.16.106.188:3306/autotest?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          continue-on-error: true   # 默认true,初始化失败是否继续
          separator: ";"             # sql 默认分号分隔符
        mysql_2:   # 业务数据库
          username: zuozewei
          password: zuozewei
          url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false
          driver-class-name: com.mysql.cj.jdbc.Driver
          continue-on-error: true   # 默认true,初始化失败是否继续
          separator: ";"             # sql 默认分号分隔符
        h2:       # 业务数据库
          username: sa
          password: ""
          url: jdbc:h2:mem:test
          driver-class-name: org.h2.Driver
          schema: db/schema.sql       # 配置则生效,自动初始化表结构
          data: db/data.sql           # 配置则生效,自动初始化数据
          continue-on-error: true    # 默认true,初始化失败是否继续
          separator: ";"             # sql 默认分号分隔符
          druid:                      # 这里可以重写默认值
            initial-size: 5
            public-key: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUVA/IL/iON8f63bv2i/pIAK+1sXY228slLkTKrI9axwBMIoPV7+PqdRTv6uqMl3j6nei0EDBWEu/Wp/qOQ/ScCAwEAAQ==

使用 @DS 切换数据源。建议只注解在 service 实现类上。比如我们指定取用例 service 方法的数据源:

**
 * 描述: 参数化自定义查询实现类
 *
 * @author zuozewei
 * @create 2019-11-21 16:04
 */

@Service
public class TestDataServiceImpl implements TestDataService {

    @Resource
    private TestDataMapper testDataMapper;

    @DS("mysql_1")
    @Override
    public List<LinkedHashMap<String, Object>> selectBysql(String sql) {
        return testDataMapper.selectBysql(sql);
    }

}

启动工程

启动的时候,我们可以看到各数据源已初始化成功。

业务持久层

业务持久层的框架,我们依旧选择统一使用 Mybatis。在使用 Mybatis 的时候,Dao 接口,Entity 实体类,还有每个实体类对应的 xml 都得自己写,这其实也是工作量很大的事情,维护起来也很费劲,使用我们这里选用的是mybatis-generatormybatis-generator-gui 来快速生成 Mybatis 的Java POJO文件及数据库 Mapping 文件。

初始化数据

这里我们演示的业务数据库是 H2,数据库的表结构脚本 schema.sql:

drop table t_coffee if exists;
create table t_coffee (
    id bigint not null auto_increment,
    name varchar(255),
    price bigint not null,
    create_time timestamp,
    update_time timestamp,
    primary key (id)
);

初始化数据脚本 data.sql:

insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());

这两个SQL文件都放到 src\main\resources\db 下。

处理自定义类型

这里的 price 我们扩展了自定义类型,所以我们需要使用 TypeHandler 解决自定义类型预处理。因为 price 是joda-money 类型,数据库中却是 bigint 类型。MyBatis 为我们提供的方法即是 TypeHandler 来应对 Java 和 jdbc 字段类型不匹配的情况。MyBatis 中内置了不少的TypeHandler,如果我们想要自己自定义一个 TypeHandler 可以实现 TypeHandler 接口,也可以继承 BaseTypeHandler 类。下面我们实现一个将 Java 中的 joda-money 类型利用我们自定义的 MoneyTypeHandler 来转换为 JDBC 的 bigint 类型。

引包:

!--money类型-->
<dependency>
    <groupId>org.joda</groupId>
    <artifactId>joda-money</artifactId>
    <version>LATEST</version>
</dependency>

新建一个 handler package,编写 MoneyTypeHandler.java

/**
 * 在 Money 与 Long 之间转换的 TypeHandler,处理 CNY 人民币
 */

public class MoneyTypeHandler extends BaseTypeHandler<Money> {

    /**
     *  设置非空参数
     * @param ps
     * @param i
     * @param parameter
     * @param jdbcType
     * @throws SQLException
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Money parameter, JdbcType jdbcType) throws SQLException {
        ps.setLong(i, parameter.getAmountMinorLong());
    }

    /**
     * 根据列名,获取可以为空的结果
     * @param rs
     * @param columnName
     * @return
     * @throws SQLException
     */
    @Override
    public Money getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return parseMoney(rs.getLong(columnName));
    }

    /**
     * 根据列索引,获取可以为空的结果
     * @param rs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    @Override
    public Money getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return parseMoney(rs.getLong(columnIndex));
    }

    /**
     *
     * @param cs
     * @param columnIndex
     * @return
     * @throws SQLException
     */
    @Override
    public Money getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return parseMoney(cs.getLong(columnIndex));
    }

    /**
     *  处理 CNY 人民币
     * @param value
     * @return
     */
    private Money parseMoney(Long value) {
        return Money.of(CurrencyUnit.of("CNY"), value / 100.0);
    }
}

使用 mybatis-generator

MyBatis Generator是 MyBatis 的代码生成器,支持为 MyBatis 的所有版本生成代码。非常容易及快速生成 Mybatis 的Java POJO文件及数据库 Mapping 文件。

引包:

<!--mybatis-generator生成器-->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.7</version>
</dependency>

配置 generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="H2Tables" targetRuntime="MyBatis3">
        <!--支持流式 fluent 方法-->
        <plugin type="org.mybatis.generator.plugins.FluentBuilderMethodsPlugin" />
        <!-- 自动生成toString方法 -->
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
        <!-- 自动生成hashcode方法 -->
        <plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
        <!-- 分页插件 -->
        <plugin type="org.mybatis.generator.plugins.RowBoundsPlugin" />

        <!--数据库连接信息-->
        <jdbcConnection driverClass="org.h2.Driver"
                        connectionURL="jdbc:h2:mem:test"
                        userId="sa"
                        password="">
        </jdbcConnection>

        <!--模型生成器、Mapper生成器-->
        <javaModelGenerator targetPackage="com.zuozewei.springbootdatadrivendemo.db.auto.model"
                            targetProject="./src/main/java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <sqlMapGenerator targetPackage="com.zuozewei.springbootdatadrivendemo.db.auto.mapper"
                         targetProject="./src/main/resources/mapper">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <javaClientGenerator type="MIXEDMAPPER"
                             targetPackage="com.zuozewei.springbootdatadrivendemo.db.auto.mapper"
                             targetProject="./src/main/java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>

        <!--表映射-->
        <table tableName="t_coffee" domainObjectName="Coffee" >
            <generatedKey column="id" sqlStatement="CALL IDENTITY()" identity="true" />
            <columnOverride column="price" javaType="org.joda.money.Money" jdbcType="BIGINT"
                            typeHandler="com.zuozewei.springbootdatadrivendemo.handler.MoneyTypeHandler"/>
        </table>
    </context>
</generatorConfiguration>

注意:

  • id 是自增的;
  • price 字段需要映射到 MoneyTypeHandler。

启动方法

在工程启动类编写一个调用方法:

@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
@Slf4j
@MapperScan("com.zuozewei.springbootdatadrivendemo.db")
public class SpringbootDataDrivenDemoApplication  implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDataDrivenDemoApplication.class, args);
        log.info("程序启动!");
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        generateArtifacts();
        log.info("启动generateArtifacts");
    }

    /**
     * 执行MyBatisGenerator
     * @throws Exception
     */
    private void generateArtifacts() throws Exception {
        List<String> warnings = new ArrayList<>();
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(
                this.getClass().getResourceAsStream("/generatorConfig.xml"));
        DefaultShellCallback callback = new DefaultShellCallback(true);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }

}

启动工程:

检查配置文件指定路径是否生成文件:

使用 mybatis-generator-gui

还有一种简易的方法生成 Mybatis 的 Java POJO 文件及数据库 Mapping 文件,即使用 mybatis-generator-gui 工具。

简介

mybatis-generator-gui 是基于 mybatis generator 开发一款界面工具, 本工具可以使你非常容易及快速生成 Mybatis 的 Java POJO 文件及数据库 Mapping 文件。 核心特性:

  • 按照界面步骤轻松生成代码,省去 XML 繁琐的学习与配置过程
  • 保存数据库连接与 Generator 配置,每次代码生成轻松搞定
  • 内置常用插件,比如分页插件
  • 支持 OverSSH 方式,通过 SSH 隧道连接至公司内网访问数据库
  • 把数据库中表列的注释生成为 Java 实体的注释,生成的实体清晰明了
  • 可选的去除掉对版本管理不友好的注释,这样新增或删除字段重新生成的文件比较过来清楚
  • 目前已经支持 Mysql、Mysql8、Oracle、PostgreSQL 与 SQL Server,暂不对其他非主流数据库提供支持。(MySQL 支持的比较好)

要求:由于使用了 Java 8 的众多特性,所以要求 JDK 1.8.0.60 以上版本,另外 JDK 1.9 暂时还不支持。

git clone https://github.com/zouzg/mybatis-generator-gui
cd mybatis-generator-gui
mvn jfx:jar
cd target/jfx/app/
java -jar mybatis-generator-gui.jar

使用示例

由于目前不支持 H2 数据库,故使用示意图代替:

实现Service方法

在 service package 下新建 Service 接口 CoffeeService.java

/**
 * 描述: coffee Service
 *
 * @author zuozewei
 * @create 2019-11-21 18:00
 */

public interface CoffeeService {

  // 插入
  int addCoffee(Coffee coffee);

  // 查询
  List selectCoffeeFromDs(CoffeeExample coffeeExample) throws InterruptedException;

}

实现 CoffeeService 接口,并使用注解指定 h2 数据源,新建 CoffeeServiceImpl.java

/**
 * 描述: CoffeeService 实现类
 *
 * @author zuozewei
 * @create 2019-11-21 18:00
 */

@Service
@DS("h2")
public class CoffeeServiceImpl implements CoffeeService {

      @Resource
      private CoffeeMapper coffeeMapper;

      @Override
      public int addCoffee(Coffee coffee) {
        return coffeeMapper.insert(coffee);
      }

      @Override
      public List selectCoffeeFromDs(CoffeeExample coffeeExample) throws InterruptedException {
        return coffeeMapper.selectByExample(coffeeExample);
      }

    }

配置mybatis

application.yml 中配置 mybatis

mybatis:
  type-aliases-package: com.zuozewei.db # 自动扫描实体类所在的包
  type-handlers-package: com.zuozewei.handler # 指定 TypeHandler 所在的包
  configuration: 
    map-underscore-to-camel-case: true # 开启驼峰功能
    call-setters-on-nulls: true     # 调用setter null,返回空也必须设置到bean中  (直接执行sql专用)
  mapper-locations: classpath*:/mapper/**/*.xml # 扫描类路径下所有以xml文件结尾的文件

工程结构

最后,多数据源和业务持久层的工程结构大概是以下的样子:

小结

在今天这篇文章中,主要和大家分享了实现注解式多数据源和业务持久层开发的过程。在实现过程中,你最需要关注的几部分内容是:

  • 基于 MybatisGenerator 或者 MybatisGeneratorGUI 自动化生成持久层代码,节省大量重复开发工作;
  • 使用 TypeHandler 解决自定义类型预处理;
  • 实现注解式动态数据源,满足 N 个业务数据源测试需求。

希望对你能有所启发。

本文分享自微信公众号 - 7DGroup(Zee_7DGroup),作者:左泽位

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-12-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 走进Java接口测试之从0到1搭建数据驱动框架(需求篇)

    一个 “好的” 数据驱动框架,需要从“时间”、“人力”、“收益”这三个方面出发,做好“取舍”。

    高楼Zee
  • 性能测试之java程序观察简单步骤

    在做性能测试中不断思考java应用,性能怎么观察,怎么通过方法定位到代码,是否有通用步骤,通过查找资料与查看网上知识、帮助文档之后,才有如下文章,话说知...

    高楼Zee
  • 性能分析之Linux PS&TOP中CPU百分比为什么不一致

    今天在7DGroup的群里,老郑提了个问题,ps统计出来的CPU百分比为什么比TOP统计出来的少很多。图如下:

    高楼Zee
  • 解决在同一个线程下数据源多次切换的回溯问题

    版本号:1.0.6-RELEASE 日期:2020/04/24 更新内容:解决在同一个线程下数据源多次切换的回溯问题

    wujiuye
  • ​【SpringBoot2.0系列06】SpringBoot之多数据源动态切换数据源

    【SpringBoot2.0系列02】SpringBoot之使用Thymeleaf视图模板

    yukong
  • springboot整合多数据源

    贪挽懒月
  • 基于注解多数据源解决方案

    前一段时间研究了一下spring多数据源的配置和使用,为了后期从多个数据源拉取数据定时进行数据分析和报表统计做准备。由于之前做过的项目都是单数据源的,没有遇到这...

    yaphetsfang
  • 听说你们家的NotifyDataSetChanged不起作用了

    前几天,公司项目准备上线,就在前一晚,出现了一个BUG:主页界面刷新无效。千钧一发之际,用了一个笨方法,每次刷新的时候重新setAdapter一下算是实现了基本...

    代码咖啡
  • windows建立Oracle数据库的ODBC数据源

    参考 https://blog.csdn.net/BlueCY/article/details/76164941

    程裕强
  • Spring 下,关于动态数据源的事务问题的探讨

    看着文章的标题,不知道大家能否想到具体是什么问题,如果你有点懵,那就对了! (你不懵的话我这篇文章就没存在的意义了,嘿嘿)

扫码关注云+社区

领取腾讯云代金券