通常我们的后端项目中都离不开数据库的使用,然而在编写测试用例的时候却不是很好处理,这个时候,我们可以利用TestContainer来进行数据库的测试支持,这里使用我熟悉的MySQL。
一、引入依赖
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.12.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<version>1.12.0</version>
<scope>test</scope>
</dependency>
其中mybatis是我这里用到的一个持久层框架,大家根据自己的需要配置
二、使用testcontainer启动mysql
使用testcontainer启动mysql有两种方式:
- 使用注解@ClassRule / @Rule
- 使用特殊的url连接
首先,我们写出公用的代码,如下:
User类:
package com.firewolf.utils.testcontainer.mysql;
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private Integer age;
}
UserMapper接口:
package com.firewolf.utils.testcontainer.mysql;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("select * from user")
List<User> findUser();
}
(一)使用注解的方式
一般情况下,@Rule用处不是很大,因为这个只能在测试用例里面单独的使用,而不能注入到Spring容器中,所以我这里只记录@ClassRule用法
- 在类加载前启动容器
这里引入了初始化脚本(如果你有初始的数据库脚本),脚本所在目录为resources下面@ClassRule public static MySQLContainer mysql = (MySQLContainer) new MySQLContainer("mysql:5.7") .withInitScript("db/init.sql");
init.sql内容如下:CREATE TABLE `user` ( `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, `id` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user` (`name`,`age`) values ('lisi',30); insert into `user` (`name`,`age`) values ('wangwu',30); insert into `user` (`name`,`age`) values ('赵六',15);
- 为Spring容器注入连接属性
这样的话,Spring在初始化数据源的时候,就能够连接上数据库@BeforeClass public static void init() { System.setProperty("spring.datasource.url", mysql.getJdbcUrl()); System.setProperty("spring.datasource.driver-class-name", mysql.getDriverClassName()); System.setProperty("spring.datasource.username", mysql.getUsername()); System.setProperty("spring.datasource.password", mysql.getPassword()); }
- 注入UserMapper并使用
@Autowired private UserMapper userMapper; @Test public void testUserMapper() { List<User> userList = userMapper.findUser(); assert userList.size() == 3; System.out.println("查询到的用户列表如下:"); System.out.println(userList); }
- 完整代码为
package com.firewolf.utils.testcontainer.mysql; import com.firewolf.utils.testcontainer.TestcontainerApplication; import java.util.List; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.testcontainers.containers.MySQLContainer; @SpringBootTest(classes = TestcontainerApplication.class) @RunWith(SpringRunner.class) public class MySQLDriver2 { @Autowired private UserMapper userMapper; @ClassRule public static MySQLContainer mysql = (MySQLContainer) new MySQLContainer("mysql:5.7") .withInitScript("db/init.sql"); /** * 设置数据库连接属性 */ @BeforeClass public static void init() { System.setProperty("spring.datasource.url", mysql.getJdbcUrl()); System.setProperty("spring.datasource.driver-class-name", mysql.getDriverClassName()); System.setProperty("spring.datasource.username", mysql.getUsername()); System.setProperty("spring.datasource.password", mysql.getPassword()); } @Test public void testUserMapper() { List<User> userList = userMapper.findUser(); assert userList.size() == 3; System.out.println("查询到的用户列表如下:"); System.out.println(userList); } }
这种用法的话,在Spring配置文件中不需要配置数据库连接的四个关键属性,其他的可以自行配置;
如果只是在某个测试用例里面用到数据库,我们可以直接在测试用例里面启动mysql容器即可,如:
public void testSimple() {
MySQLContainer mysql = (MySQLContainer) new MySQLContainer("mysql:5.7")
.withInitScript("db/init.sql");
mysql.start();
System.out.println(mysql.getJdbcUrl());
System.out.println(mysql.getDriverClassName());
System.out.println(mysql.getUsername());
System.out.println(mysql.getPassword());
}
(二)使用特殊的URL
这种方式的测试用例类非常简答,没有任何容器相关内容,先贴出来:
package com.firewolf.utils.testcontainer.mysql;
import com.firewolf.utils.testcontainer.TestcontainerApplication;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest(classes = TestcontainerApplication.class)
@RunWith(SpringRunner.class)
public class MySQLDriver {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
List<User> userList = userMapper.findUser();
assert userList.size() == 3;
System.out.println("查询到的用户列表如下:");
System.out.println(userList);
}
}
那么怎么引入testcontainer呢,其实很简答,就在我们的配置文件中对url进行处理,如下:
#mysql
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.url=jdbc:tc:mysql:5.7://localhost:3306/testdb?TC_INITSCRIPT=db/init.sql
spring.datasource.username=root
spring.datasource.password=111111
#mybatis下划线转驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true
我们可以看到,和正常配置URL不同的是,这里在jdbc后面添加了tc
同时我们还看到,可以通过TC_INITSCRIPT来指定初始化脚本的位置;
对于URL这种方式,我们还可以使用类来进行数据库初始化,方式如下:
- 改变URL为:
这里使用TC_INITFUNCTION来指定了初始化的类和方法spring.datasource.url=jdbc:tc:mysql:5.7://localhost:3306/testdb?TC_INITFUNCTION=com.firewolf.utils.testcontainer.mysql.DataSourceInit::initFunction
- 编写初始化方法
package com.firewolf.utils.testcontainer.mysql; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; public class DataSourceInit { public static void initFunction(Connection connection) throws Exception { Statement statement = connection.createStatement(); String[] sqls = new String[]{ "CREATE TABLE `user` (\n" + " `name` varchar(255) DEFAULT NULL,\n" + " `age` int(11) DEFAULT NULL,\n" + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + " PRIMARY KEY (`id`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8", "insert into `user` (`name`,`age`) values ('bajie',30)", "insert into `user` (`name`,`age`) values ('wukong',1000)", "insert into `user` (`name`,`age`) values ('唐僧',600)", }; Arrays.stream(sqls).forEach(x -> { try { statement.execute(x); } catch (SQLException e) { e.printStackTrace(); } }); } }