背景
在日常软件开发过程中,集成测试是保障模块功能在真实环境中正常运行的关键一环,然而,由于外部组件依赖的引入,例如数据库、消息队列等,测试过程会变得复杂,环境中组件的缺失会导致无法为测试用例提供该有的基本条件。因此,管理外部依赖,常常是个令人头疼的问题,为了解决这个问题,游戏公司playtika提供了一款在测试中简便使用容器来引入组件的框架TestContainers,使得开发人员可以轻松启动和使用包括MySQL、Redis、ElasticSearch、InFluxDB、Kafka等各种组件容器,为测试环境提供了简单而灵活的解决方案。
简介
TestContainers 是一个用于简化集成测试的库,最初是为 Java 编写的。然而,随着时间的推移,它也演变为支持其他编程语言,包括 Kotlin、Scala、Groovy 和 Python。该库的核心思想是在测试期间启动和管理 Docker 容器,以便测试可以在真实的环境中运行,而不是在模拟的环境中。
使用案例
需求
在测试过程中操作MySQL和Redis,为MySQL准备好初始化表结构的sql脚本,并有必要制作一个带有数据的MySQL镜像,方便其他测试案例使用时不需要再重复初始化sql。
步骤
引入依赖
以maven构建的springboot工程为例
引入TestContainers核心库
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.1</version>
<scope>test</scope>
</dependency>
或者通过管理多个测试容器依赖项版本的方式,使用BOM
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.19.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然后使用容器依赖时可不指定版本
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
引入MySQL、Redis以及其他工具包的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
初始化容器
提供一个初始化和关闭MySQL Redis容器的类:
/**
* mysql and redis container
*/
@Slf4j
public class MRContainer {
private static final ReentrantLock reentrantLock = new ReentrantLock();
private static String withDataMysqlImageName = null;
public MRContainer(){
}
public MRContainer(boolean initRedisContainer, boolean initMysqlContainer){
// super(initRedisContainer);
// ENV
// DOCKER_HOST tcp://{IP}:2375
// TESTCONTAINERS_HOST_OVERRIDE {IP}
// UUID.randomUUID().toString()
// String tempPath = JoinerUtil.joiner("/",true,"/data/","test", TimeUtil.getTime().getYMD());
// File f = new File(tempPath);
// boolean persistentVolumn = true;
}
private MySQLContainer creatMysqlContainer(String fullImageName){
return (MySQLContainer) new MySQLContainer(DockerImageName.parse(fullImageName).asCompatibleSubstituteFor("mysql"))
.withExposedPorts(3306)
.withEnv("MYSQL_ROOT_PASSWORD","test")
.withCommand("--character-set-server=utf8 --collation-server=utf8_unicode_ci");
}
public MySQLContainer mysql ;
public GenericContainer<?> redis ;
public void setup0(boolean initRedisContainer,boolean initMysqlContainer){
List<CompletableFuture<Void>> cfs = Lists.newArrayList();
if(initRedisContainer) {
log.info("redis container starting...");
cfs.add(CompletableFuture.runAsync(()->{
redis= new GenericContainer<>(DockerImageName.parse("redis:7.2.0")).withExposedPorts(6379);
redis.start();
log.info("redis container started,id:{}",redis.getContainerId());
}));
}
if(initMysqlContainer) {
log.info("mysql container starting...");
boolean mysqlCustomImageLock = Boolean.valueOf(System.getProperty("testcontainers.mysql.customimage.lock", "true"));
CompletableFuture<Void> mysqlCF = CompletableFuture.runAsync(() -> {
if (mysqlCustomImageLock && reentrantLock.tryLock() && withDataMysqlImageName == null) {
try {
//制作包含基础数据的mysql镜像
GenericContainer container = new GenericContainer(new ImageFromDockerfile()
.withFileFromClasspath("Dockerfile", "Dockerfile")
.withFileFromClasspath("mysqld.cnf", "mysqld.cnf")
.withFileFromClasspath("struct.sql", "struct.sql"));
container.start();
// 执行docker commit 命令制作一个含有数据且不需要初始化sql的镜像
CommitCmd commitCmd = container.getDockerClient()
.commitCmd(container.getContainerName())
.withRepository("mysql")
.withTag("test-5.7.34");
String withDataMysqlImageId = commitCmd.exec();//得到含有数据且不需要初始化sql的镜像的id
withDataMysqlImageName = commitCmd.getRepository() + ":" + commitCmd.getTag();
container.stop();
log.info("MRContainer.Create WithDataMysqlImage success|ImageId:{}|ImageName:{}", withDataMysqlImageId, withDataMysqlImageName);
} catch (Exception e) {
log.error("MRContiner.Create WithDataMysqlImage fail", e);
} finally {
reentrantLock.unlock();
}
}
int timeoutSec = Integer.parseInt(System.getProperty("testcontainers.mysql.customimage.timeout", "30"));
LocalDateTime startWait = LocalDateTime.now();
LocalDateTime now;
log.info("Start wait CreateWithDataMysqlImage");
while (true) {
if(!mysqlCustomImageLock){
break;
}
if (withDataMysqlImageName != null) {
// 使用这个新镜像
mysql = creatMysqlContainer(withDataMysqlImageName);
log.info("creatMysqlContainer withDataMysqlImage success");
break;
} else {
now = LocalDateTime.now();
if (Duration.between(startWait, now).getSeconds() > timeoutSec) {
break;
}
}
try {
Thread.sleep(200);
} catch (Exception e) {
log.error("MRContiner.Thread.sleep error", e);
}
}
if(mysql==null){
//如果超时时间到了还没制作好基础镜像则使用加载初始化sql方式跑 mysql 镜像
mysql = creatMysqlContainer("mysql:5.7.34");
mysql.withInitScript("struct.sql");
log.info("timeout.CreateWithDataMysqlImage");
}
log.info("end wait CreateWithDataMysqlImage");
}).thenAccept(__ -> {
mysql.start();
log.info("mysql container started,id:{}", mysql.getContainerId());
});
cfs.add(mysqlCF);
}
cfs.forEach(CompletableFuture::join);
}
public void close0(boolean initRedisContainer,boolean initMysqlContainer){
List<CompletableFuture<Void>> cfs = Lists.newArrayList();
if(initRedisContainer) {
cfs.add(CompletableFuture.runAsync(()->{
redis.close();
}));
}
if(initMysqlContainer) {
cfs.add(CompletableFuture.runAsync(()->{
mysql.close();
}));
}
cfs.forEach(CompletableFuture::join);
}
}
由于初始化MySQL需要制作一个包含数据(例如表结构)的镜像,
因此需要准备好sql脚本
struct.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_config
-- ----------------------------
DROP TABLE IF EXISTS `t_config`;
CREATE TABLE `t_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`conf_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`conf_value` varchar(640) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`conf_remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '配置项备注',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uniq_key`(`conf_key`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
用于制作MySQL镜像的Dockerfile
FROM mysql:5.7.34
RUN mkdir -p /workdir/mysql/
ENV MYSQL_ROOT_PASSWORD root
# 拷贝初始化sql脚本
COPY ./mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf
COPY ./struct.sql /docker-entrypoint-initdb.d/
#CMD ["sh", "/mysql/setup.sh"]
MySQL配置文件
[mysqld]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
datadir = /workdir/mysql/
#log-error = /var/log/mysql/error.log
# By default we only accept connections from localhost
#bind-address = 127.0.0.1
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
创建BaseTestEnv类继承自MRContainer,在构造函数中执行逻辑,该类主要用来获取是否开启mysql redis容器初始化环境配置,并走MRContainer的setup0初始化容器逻辑,初始化好容器后,设置mysql redis连接的环境变量,便于后续使用自动装配的mysql redis引擎。
@Slf4j
public class BaseTestEnv extends MRContainer {
// @Getter
// private String redisHost;
// @Getter
// private Integer redisPort;
//
// @Getter
// private String mysqlUrl;
boolean runRedisContainer = false;
boolean runMySqlContainer = false;
public BaseTestEnv() {
boolean runWithContainer = Boolean.valueOf(System.getProperty("testcontainers.container", "true"));
runRedisContainer = Boolean.valueOf(System.getProperty("testcontainers.redis", "false"));
runMySqlContainer = Boolean.valueOf(System.getProperty("testcontainers.mysql", "false"));
if(runWithContainer){
runRedisContainer = true;
runMySqlContainer = true;
}
log.info("===========================initing BaseTestEnv========================");
setup0(runRedisContainer,runMySqlContainer);
if(runRedisContainer){
log.info("redis.contaer started.IP and Port:{}|{}",redis.getHost(),redis.getFirstMappedPort().toString());
System.setProperty("spring.redis.host",redis.getHost());
System.setProperty("spring.redis.port",redis.getFirstMappedPort().toString());
}
if(runMySqlContainer){
log.info("mysql.container:{}|{}",mysql.getHost(),mysql.getFirstMappedPort());
String mysqlUrl = "jdbc:mysql://"+mysql.getHost()+":"+mysql.getFirstMappedPort()+"/test?useSSL=false";//Testcontainers库会自动创建一个默认数据库test
System.setProperty("spring.datasource.druid.url",mysqlUrl);
System.setProperty("spring.datasource.druid.username","root");
System.setProperty("spring.datasource.druid.password","test");
}
}
public BaseTestEnv(boolean initRedisContainer, boolean initMysqlContainer){
super(initRedisContainer,initMysqlContainer);
}
}
启动类继承自BaseTestEnv,使得测试容器在Spring自动配置入口之前就初始化完毕:
@SpringBootApplication
@MapperScan("com.example.containertest.mapper")
@ServletComponentScan("com.example.containertest")
@ComponentScan("com.example.containertest")
public class ContainertestApplicationEnv extends BaseTestEnv{
public static void main(String[] args) {
SpringApplication.run(ContainertestApplicationEnv.class, args);
}
}
配置组件
配置好Redis模板引擎和用于MySQL操作的Mybatis相关Mapper
@Configuration
@Slf4j
public class RestTemplateConfig {
@Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
StringRedisTemplate redisTemplate = new StringRedisTemplate();
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
public interface ConfigMapper extends Mapper<Config> {
}
测试
简单写一个测试类来验证是否可以正常启动和操作组件。
@SpringBootTest(classes = ContainertestApplicationEnv.class)
@Slf4j
class ContainertestApplicationTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ConfigMapper configMapper;
@Test
void contextLoads() {
stringRedisTemplate.opsForValue().set("1","1");
log.info("ContainertestApplicationTests.contextLoads.redis.get|{}",stringRedisTemplate.opsForValue().get("1"));
Config config = new Config();
config.setConfKey("key");
config.setConfValue("value");
config.setCreateTime(new Date());
config.setUpdateTime(new Date());
config.setConfRemark("remark");
configMapper.insert(config);
List<Config> configs = configMapper.selectAll();
log.info("ContainertestApplicationTests.contextLoads.mysql.get|{}", JSONUtils.beanToJson(configs));
}
}
执行测试
配置执行指定测试类,配置开启容器初始化,配置使用制作带数据的MySQL镜像
另外,TestContainers需要连接到有Docker环境的主机上来执行初始化容器操作,
根据官网描述
我们使用非TLS的TCP连接来指定Docker守护进程主机:DOCKER_HOST=tcp://192.168.237.130:2375,ip修改为你自己的Docker主机地址。
mvn test -D test=ContainertestApplicationTests#contextLoads -D testcontainers.container=true -D testcontainers.mysql.customimage.lock=true -D DOCKER_HOST=tcp://192.168.237.130:2375
效果
日志会显示连接的Docker host IP、拉取的镜像、创建的容器、容器启动的ip端口、容器ID等信息,
通过我们测试方法打印的日志显示成功对我们通过TestContainer启动的MySQL、Redis容器进行插入和查询出插入结果的操作。
2023-10-12 13:05:04.188 INFO 10692 --- [ main] c.e.c.ContainertestApplicationTests : Starting ContainertestApplicationTests using Java 17.0.5 on DESKTOP-QMITG9N with PID 10692 (started by 12633 in E:\IntelliJ IDEA Workspace\containertest)
2023-10-12 13:05:04.193 INFO 10692 --- [ main] c.e.c.ContainertestApplicationTests : No active profile set, falling back to default profiles: default
2023-10-12 13:05:04.922 INFO 10692 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2023-10-12 13:05:04.925 INFO 10692 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2023-10-12 13:05:04.983 INFO 10692 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 25 ms. Found 0 Redis repository interfaces.
2023-10-12 13:05:05.395 INFO 10692 --- [ main] com.example.containertest.BaseTestEnv : ===========================initing BaseTestEnv========================
2023-10-12 13:05:05.396 INFO 10692 --- [ main] com.example.containertest.MRContainer : redis container starting...
2023-10-12 13:05:05.404 INFO 10692 --- [ main] com.example.containertest.MRContainer : mysql container starting...
2023-10-12 13:05:05.662 INFO 10692 --- [onPool-worker-1] org.testcontainers.images.PullPolicy : Image pull policy will be performed by: DefaultPullPolicy()
2023-10-12 13:05:05.664 INFO 10692 --- [onPool-worker-1] o.t.utility.ImageNameSubstitutor : Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstituto
r')
2023-10-12 13:05:06.049 INFO 10692 --- [onPool-worker-2] o.t.d.DockerClientProviderStrategy : Found Docker environment with Environment variables, system properties and defaults. Resolved dockerHost=tcp://192.168.237.130:2375
2023-10-12 13:05:06.049 INFO 10692 --- [onPool-worker-2] org.testcontainers.DockerClientFactory : Docker host IP address is 192.168.237.130
2023-10-12 13:05:06.066 INFO 10692 --- [onPool-worker-2] org.testcontainers.DockerClientFactory : Connected to docker:
Server Version: 20.10.17
API Version: 1.41
Operating System: CentOS Linux 7 (Core)
Total Memory: 972 MB
2023-10-12 13:05:06.103 INFO 10692 --- [onPool-worker-2] tc.testcontainers/ryuk:0.5.1 : Creating container for image: testcontainers/ryuk:0.5.1
2023-10-12 13:05:06.109 INFO 10692 --- [onPool-worker-2] o.t.utility.RegistryAuthLocator : Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: testcontainers/ryu
k:0.5.1, configFile: C:\Users\12633\.docker\config.json, configEnv: DOCKER_AUTH_CONFIG). Falling back to docker-java default behaviour. Exception message: Status 404: No config supplied. Checked in order: C:\Users\12633\.docker\config.json (file not found), DOCK
ER_AUTH_CONFIG (not set)
2023-10-12 13:05:06.213 INFO 10692 --- [onPool-worker-2] tc.testcontainers/ryuk:0.5.1 : Container testcontainers/ryuk:0.5.1 is starting: e16299836a73d47b1d1785db64f7a1ab9e02323b3472fabaaee0ee892836f2bf
2023-10-12 13:05:06.624 INFO 10692 --- [onPool-worker-2] tc.testcontainers/ryuk:0.5.1 : Container testcontainers/ryuk:0.5.1 started in PT0.5214793S
2023-10-12 13:05:06.628 INFO 10692 --- [onPool-worker-2] o.t.utility.RyukResourceReaper : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2023-10-12 13:05:06.629 INFO 10692 --- [onPool-worker-2] org.testcontainers.DockerClientFactory : Checking the system...
2023-10-12 13:05:06.629 INFO 10692 --- [onPool-worker-2] org.testcontainers.DockerClientFactory : ?? Docker server version should be at least 1.6.0
2023-10-12 13:05:06.630 INFO 10692 --- [onPool-worker-1] tc.redis:7.2.0 : Creating container for image: redis:7.2.0
2023-10-12 13:05:06.650 INFO 10692 --- [onPool-worker-1] tc.redis:7.2.0 : Container redis:7.2.0 is starting: 444d8a258be933cf5c9b5109dc84d1a35ab5cf589bce170e30a9025eddc5f54d
2023-10-12 13:05:06.674 INFO 10692 --- [onPool-worker-2] o.t.images.builder.ImageFromDockerfile : Transferred 2 KB to Docker daemon
2023-10-12 13:05:07.248 INFO 10692 --- [onPool-worker-1] tc.redis:7.2.0 : Container redis:7.2.0 started in PT0.618163S
2023-10-12 13:05:07.248 INFO 10692 --- [onPool-worker-1] com.example.containertest.MRContainer : redis container started,id:444d8a258be933cf5c9b5109dc84d1a35ab5cf589bce170e30a9025eddc5f54d
2023-10-12 13:05:07.899 INFO 10692 --- [onPool-worker-2] t/testcontainers/wozqmoobekncvtap:latest : Creating container for image: localhost/testcontainers/wozqmoobekncvtap:latest
2023-10-12 13:05:07.900 INFO 10692 --- [onPool-worker-2] o.t.utility.RegistryAuthLocator : Failure when attempting to lookup auth config. Please ignore if you don't have images in an authenticated registry. Details: (dockerImageName: localhost/testcont
ainers/wozqmoobekncvtap:latest, configFile: C:\Users\12633\.docker\config.json, configEnv: DOCKER_AUTH_CONFIG). Falling back to docker-java default behaviour. Exception message: Status 404: No config supplied. Checked in order: C:\Users\12633\.docker\config.json
(file not found), DOCKER_AUTH_CONFIG (not set)
2023-10-12 13:05:08.352 INFO 10692 --- [onPool-worker-2] com.example.containertest.MRContainer : end wait CreateWithDataMysqlImage
2023-10-12 13:05:08.356 INFO 10692 --- [onPool-worker-2] tc.mysql:test-5.7.34 : Creating container for image: mysql:test-5.7.34
2023-10-12 13:05:08.441 INFO 10692 --- [onPool-worker-2] tc.mysql:test-5.7.34 : Container mysql:test-5.7.34 is starting: 9d3a83599d68042447b3534e2e96b0c51f39c16b36ce43e499861aea05ce4a2d
2023-10-12 13:05:08.691 INFO 10692 --- [onPool-worker-2] tc.mysql:test-5.7.34 : Waiting for database connection to become available at jdbc:mysql://192.168.237.130:49206/test using query 'SELECT 1'
2023-10-12 13:05:17.219 INFO 10692 --- [onPool-worker-2] tc.mysql:test-5.7.34 : Container mysql:test-5.7.34 started in PT8.8625031S
2023-10-12 13:05:17.219 INFO 10692 --- [onPool-worker-2] tc.mysql:test-5.7.34 : Container is started (JDBC URL: jdbc:mysql://192.168.237.130:49206/test)
2023-10-12 13:05:17.219 INFO 10692 --- [onPool-worker-2] com.example.containertest.MRContainer : mysql container started,id:9d3a83599d68042447b3534e2e96b0c51f39c16b36ce43e499861aea05ce4a2d
2023-10-12 13:05:17.219 INFO 10692 --- [ main] com.example.containertest.BaseTestEnv : redis.contaer started.IP and Port:192.168.237.130|49205
2023-10-12 13:05:17.219 INFO 10692 --- [ main] com.example.containertest.BaseTestEnv : mysql.container:192.168.237.130|49206
2023-10-12 13:05:17.556 INFO 10692 --- [ main] c.a.d.s.b.a.DruidDataSourceAutoConfigure : Init DruidDataSource
2023-10-12 13:05:17.714 INFO 10692 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2023-10-12 13:05:18.858 INFO 10692 --- [ main] t.m.m.autoconfigure.MapperCacheDisabler : Clear tk.mybatis.mapper.util.MsUtil CLASS_CACHE cache.
2023-10-12 13:05:18.859 INFO 10692 --- [ main] t.m.m.autoconfigure.MapperCacheDisabler : Clear tk.mybatis.mapper.genid.GenIdUtil CACHE cache.
2023-10-12 13:05:18.859 INFO 10692 --- [ main] t.m.m.autoconfigure.MapperCacheDisabler : Clear tk.mybatis.mapper.version.VersionUtil CACHE cache.
2023-10-12 13:05:18.859 INFO 10692 --- [ main] t.m.m.autoconfigure.MapperCacheDisabler : Clear EntityHelper entityTableMap cache.
2023-10-12 13:05:18.882 INFO 10692 --- [ main] c.e.c.ContainertestApplicationTests : Started ContainertestApplicationTests in 15.023 seconds (JVM running for 15.988)
2023-10-12 13:05:20.182 INFO 10692 --- [ main] c.e.c.ContainertestApplicationTests : ContainertestApplicationTests.contextLoads.redis.get|1
2023-10-12 13:05:20.338 INFO 10692 --- [ main] c.e.c.ContainertestApplicationTests : ContainertestApplicationTests.contextLoads.mysql.get|[{"confKey":"key","confRemark":"remark","confValue":"value","createTime":1697087120000,"id":1,"updateTime":1697087120000}]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 16.872 s - in com.example.containertest.ContainertestApplicationTests
2023-10-12 13:05:20.381 INFO 10692 --- [extShutdownHook] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closing ...
2023-10-12 13:05:20.383 INFO 10692 --- [extShutdownHook] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closed
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 21.425 s
[INFO] Finished at: 2023-10-12T13:05:20+08:00
[INFO] ------------------------------------------------------------------------
附:本文章涉及的项目代码:https://github.com/ljm-cloud/containertest
结语
在软件开发的世界中,集成测试是确保软件质量的关键一环。TestContainers 提供了一个强大而灵活的工具,使得在集成测试中使用真实环境变得更加容易。通过简化容器的启动和配置,TestContainers为开发者提供了一个更好的方式来确保他们的应用在不同环境中正常运行。如果你还没有尝试过 TestContainers,不妨在下一个项目中给它一个机会,看看它是如何改进你的集成测试流程的。