基于testcontainers的redis单元测试实践

在我们将redis整合并应用到业务模块后,单元测试这块我们希望对redis服务器也进行隔离,思路和之前内存模式启动的h2一样,我们将引入testcontainers测试框架。

当开始单元测试后,会临时从目标的docker服务器启动一个用于redis单元测试的docker容器来实现测试数据与数据运行数据的隔离。当单元测试完成后,该docker容器将停止并随后被删除。我们发现针对一些中间件采用testcontainers进行单元测试会非常优雅。

不多说,直接开干!

准备工作

首先testcontainers所要推送image并启动容器的docker服务器可以在本地也可以是远程的,这里我们采用的是后者。因此需要做服务器的客户端连接配置,参考:jenkins基础CI实践 - 远程docker服务设置

同时在本机环境变量中新增DOCKER_HOST,值为:tcp://192.168.1.113:2375,也就是docker服务器的ip和端口。

引入依赖

build.gradle

groovy 复制代码
plugins {
    id 'org.springframework.boot' version '2.2.6.RELEASE'
}

...

dependencies {
    ...

    testImplementation 'org.testcontainers:junit-jupiter:1.17.6'
    testImplementation 'com.redis.testcontainers:testcontainers-redis-junit-jupiter:1.4.6'
}

...

注意,这里我们将采用spring中提供的动态注册属性值的方式,即@DynamicPropertySource注解,这在spring的5.2.5.RELEASE版本中才支持,因此我们对spring boot从2.2.5.RELEASE升级到2.2.6.RELEASE

而在测试依赖中我们引入了与junit5junit-jupiter框架相匹配的testcontainerstestcontainers-redis依赖,用于通过testcontainers测试工具来基于image启动一个独立的中间件docker容器服务以便连接测试。

单元测试调整

对之前的RedistTest测试类做相关调整:

java 复制代码
package com.xiaojuan.boot.redis;

import ...

@Slf4j
@Testcontainers
public class RedisTest extends TestBase {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @BeforeEach
    public void initDb() {
        flushdb();
    }

    @Container
    private static final RedisContainer REDIS_CONTAINER = new RedisContainer(
            DockerImageName.parse("redis:6.2.6-alpine")).withExposedPorts(6379);

    @DynamicPropertySource
    private static void registerRedisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.redis.host", REDIS_CONTAINER::getHost);
        registry.add("spring.redis.port", () -> REDIS_CONTAINER.getMappedPort(6379).toString());
    }

    // 测试用例省略
    ...

    private void flushdb() {
        redisTemplate.execute((RedisCallback<Object>) connection -> {
            connection.flushDb();
            return "ok";
        });
    }

    ...

}

调整说明

redis的单元测试依然我们从继承的TestBase中类头部修饰的@SpringBootTest启动,额外我们再加一个@Testcontainers用于管理启动的docker容器的生命周期,这样确保在容器启动后才进行客户端的连接设置并发起单元测试,最后在单元测试结束后再停止和销毁容器。

然后我们通过@Container注解修饰一个静态成员变量的方式来定义redis容器对象,包括了再启动容器时使用的image版本,这里我们采用的是轻量级的alpine版本,并指定了内部端口。

然后通过@DynamicPropertySource注解修饰的静态方法来实现redis连接属性的动态添加,这里的端口设置为redis容器对外映射出来的端口号。

这样我们单元测试在运行时所采用的redis库从原先真实的redis库切换到临时启动的一个干净的docker容器库进行测试,而application.yml中的redis配置不用做任何调整。

为了实现单元测试直接完全的数据隔离,我们依然保留@BeforeEach修饰的initDb方法,在每个单元测试启动前先清除redis库中的数据,因为对于每个测试类,testcontainers只会启动一次容器,测试类运行结束才停止销毁容器。

问题修复

在接下来运行单元测试时,报了一个问题:

原因是我们在application.yml中对redis连接设置了password属性,而在testcontainers框架中完全可以跳过认证这一块,因此我们只需要在application-test.yml中覆盖设置该值为空即可:

yaml 复制代码
spring:
  ...
  redis:
    password:

最后运行单元测试,ok!

从运行的日志中可以看到:

好了,商品分类模块的接口开发就到此结束了,下一节开始我们将进入商品模块的开发,大家加油!

相关推荐
@yanyu66630 分钟前
springboot实现查询学生
java·spring boot·后端
酷爱码1 小时前
Spring Boot项目中JSON解析库的深度解析与应用实践
spring boot·后端·json
java干货2 小时前
虚拟线程与消息队列:Spring Boot 3.5 中异步架构的演进与选择
spring boot·后端·架构
武昌库里写JAVA5 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
小白杨树树5 小时前
【WebSocket】SpringBoot项目中使用WebSocket
spring boot·websocket·网络协议
clk660711 小时前
Spring Boot
java·spring boot·后端
爱敲代码的TOM12 小时前
基于JWT+SpringSecurity整合一个单点认证授权机制
spring boot
loser.loser12 小时前
QQ邮箱发送验证码(Springboot)
java·spring boot·mybatis