一、TestContainer是什么?
在我们开发的过程中,经常会用到一些第三方的产品或者中间件,如:redis,mq,db等等,然而,因为这些产品的引入,测试过程变得复杂起来,因为中间件的缺失(可能本地有,但是换了环境就没有了),导致测试用例编写很麻烦。
TestContainer可以通过和docker结合,让我们在编写测试用例的时候,很方便的启动docker容器(容器内有我们需要的第三方产品),这样,我们就可以更好的完善测试用例。
二、官方网站
https://www.testcontainers.org/
三、基本使用步骤
- 引入testcontainer依赖
<dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.12.0</version> </dependency> - 创建并启动容器,同时暴露端口
实际上,@ClassRule public static GenericContainer redis = new GenericContainer("redis:5.0.5") .withExposedPorts(6379);new GenericContainer("redis:5.0.5")就会创建并启动容器,而withExposedPorts(6379)是用来暴露容器的端口号的,这些是对容器的一些配置,这种配置会有很多,后面再做介绍。 - 连接容器
我们知道,在使用第三方产品的时候,会有一些配置项来连接这些产品,比如:redis需要指定主机和端口号,我们需要通过容器暴露出来的主机名和端口号进行连接。
这可以通过以下两个方法:
- 获取容器ip:
redis.getContainerIpAddress() - 获取容器暴露的端口:
redis.getMappedPort(6379)
- 编写测试代码
有了连接的主机地址和端口,我们就可以连接redis进行redis操作了,这里redis创建连接及操作代码就省略了(因为后面我会给出完整的讲解,比如:在springboot环境下使用RedisTemplate的玩儿法)。 - 运行测试
四、常用操作
(一)端口暴露与获取
随机端口号
类似于启动命令的-P选项
暴露:
在创建容器的时候通过new GenericContainer(...).withExposedPorts(6379)方法可以将容器内使用的6379端口号暴露出来;
这种方式暴露出来的端口,在主机会有一个随机的端口号进行映射,
获取:
由于是随机的,所以我们无法在测试代码中提前写死要连接的端口号,这需要我们通过以下的方法来获取主机上与容器内对应的端口号,这有两种方法:
container.getFirstMappedPort():这会获取到第一个端口映射出来的端口,如果我们的容器中只暴露出来了一个端口(如redis的6379),这种方式比较好用。container.getMappedPort(int port):找到容器内port端口映射出来的端口,当有多个端口的时候,使用这种方式。
使用技巧:
在Spring项目中,我们通常通过配置文件来指定这些参数,如:spring.redis.port。可是,在项目启动之前,由于容器没有启动,我们并不知道这个端口是多少,那么就没法在配置文件中配置端口了,我们如下设置:
@BeforeClass
public static void init() {
System.setProperty("spring.redis.host", redis.getContainerIpAddress());
System.setProperty("spring.redis.port", redis.getFirstMappedPort().toString());
}
注意这里使用的注解是@BeforeClass,这样,我们就可以在Spring容器中通过@Value("${spring.redis.port}")的方式获取到随机生成的端口了
指定端口号
类似启动命令的-p选项。
这种方式在暴露的同时进行了映射绑定。
new GenericContainer(...).withCreateContainerCmdModifier(
cmd -> {
//对主机端口和docker中的端口进行绑定,前面的是主机端口,后面的是docker中的端口
cmd.withPortBindings(new PortBinding(Ports.Binding.bindPort(8000), new ExposedPort(6379)));
})
这样的话,我们就可以在代码中明确的使用8000端口来访问redis了
(二)获取容器ip
container.getContainerIpAddress()方法即可,默认可以直接使用localhost
(三)暴露本机端口号给容器
`Testcontainers.exposeHostPorts(localServerPort)` ,这种用法可能不多
(四)运行命令
- 指定启动命令
new GenericContainer(...).withCommand("redis-server --port 7777") - 在容器内部运行命令
container.execInContainer("touch", "/somefile.txt"); - 运行命令并获取结果
Container.ExecResult lsResult = container.execInContainer("ls", "-al", "/"); String stdout = lsResult.getStdout(); int exitCode = lsResult.getExitCode(); assertTrue(stdout.contains("somefile.txt")); assertTrue(exitCode == 0);
(五)指定环境变量
new GenericContainer(...).withEnv("API_TOKEN", "foo")
(六)映射数据卷和文件
new GenericContainer(...)
.withClasspathResourceMapping("redis.conf",
"/etc/redis.conf",
BindMode.READ_ONLY)
(七)获取日志
有如下两种方式获取容器内的运行日志:
一次性获取
使用container.getLogs()方法会获取容器从启动到当前的日志字符串
动态输出
ToStringConsumer toStringConsumer = new ToStringConsumer();
container.followOutput(toStringConsumer, OutputType.STDOUT);
这里有一堆的Consumer,如:ToStringConsumer、Slf4jLogConsumer等等