单元测试之使用H2 Database模拟数据库环境

使用内存数据库,剔除单元测试的数据库环境依赖。

题图:from Zoommy

一. 背景

一般来说,在开发单元测试时,我们都是使用一套测试用数据库环境,或者直接使用开发机数据库环境进行测试。但是这种方式有个明显的弊端:前者会极大依赖网络状况、后者会因开发者环境不同而影响测试。

使用H2可以很大程度上解决上述问题。

what’s H2?H2是一个纯java编写的内存数据库,core只有1M左右,简小精悍。他支持模拟mysql,并且与spring配合良好,因此十分适合用来模拟单元测试中的数据库环境。不过也有不足之处,无法兼容所有mysql语法,毕竟是模拟,但还是可以满足大多数需求的。

二. 几种模拟数据库方式对比

三. 步骤

框架环境为:spring+junit+mybatis。

1. 依赖

建议使用1.3.176+的版本,因为1.3.176新添加了一些mysql语法的支持,使用起来会更加方便。

  • maven

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <properties>
    <h2.version>1.3.176</h2.version>
    </properties>
    <!--H2 单测-->
    <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>${h2.version}</version>
    <scope>test</scope>
    </dependency>
  • gradle

    1
    2
    3
    4
    // 方式一
    compile group: 'com.h2database', name: 'h2', version: '1.3.176'
    // 方式二
    compile 'com.h2database:h2:1.3.176'

2. 配置datasource和sql脚本

在test.resource包下,添加yourName.xml(在junit测试类上要用@ContextConfiguration注解引用该xml)。这个datasource会自动覆盖main.resource包下datasource的定义。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:H2_TYPE.sql"/>
<jdbc:script location="classpath:INIT_TABLE.sql"/>
<jdbc:script location="classpath:INIT_DATA.sql"/>
</jdbc:embedded-database>
</beans>

可以看到,配置中指定了三个sql脚本。

  • H2_TYPE.sql是指定H2的模式,这里设置为兼容mysql模式

    1
    SET MODE MySQL;
  • INIT_TABLE.sql初始化表结构,这里给个简单示例

    1
    2
    3
    4
    CREATE TABLE `tt` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • INIT_DATA.sql插入数据到数据库,这里也是简单示例

    1
    INSERT INTO `tt` (id) VALUES (1);

四. FAQ

1. 配置成功后总是提示sql语法错误,但是sql在mysql下都能正常执行。

A:H2模拟mysql暂时无法支持所有mysql语法,两者有所区别,部分语法H2不支持,会导致报错。i.e.在H2的1.3.176版本前,不支持create table后面的Default Charset=utf8

2. 标识符报错:Syntax error in SQL statement "CREATE TABLE ""TT"" ( "; expected "identifier";

A:通过jdbc:script标签配置的sql脚本默认使用分号作为分隔符,如果没有找到就会使用\n做分隔符,因此这个报错很有可能是在sql末尾忘记了分号。还有一种可能就是表名或者列名使用了单引号'tableName',正确方式:tableName或者tableName也即要么不加,要么就是加```。

3. 索引报错:Index "status" already exists

A:H2的key或者unique key是数据库级别而非表级别,因此两张表内存在同名key会遇到这个报错,需要保证数据库内key的命名唯一。

4. 常见mysql兼容问题_

  • 不支持表级别的Comment
  • 插入语句单引号中的\'转义不支持
  • H2 UNIQUE KEY是数据库级别的
  • 无法执行多个Update语句
  • 列别名无法用于子查询

五. 相关链接

H2官方语法介绍