基于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!

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

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

相关推荐
终将老去的穷苦程序员13 小时前
基于SpringBoot的餐饮管理系统
java·spring boot·后端
心之伊始13 小时前
Spring AI Tool Calling 实战:让 Java Agent 调用本地 Bean 工具方法
java·spring boot·agent·spring ai·tool calling
熠熠仔13 小时前
Spring Boot 与 MyBatis-Plus 空间几何数据集成指南
spring boot·后端·mybatis
天青色等烟雨..14 小时前
智慧农林核心遥感技术99个案例实践
运维·人工智能·spring boot·后端·自动化
IT策士14 小时前
Redis 从入门到精通:事务与 Lua 脚本
redis·junit·lua
北极星日淘14 小时前
日淘平台优惠券系统的设计:从规则引擎到防超领
junit
慧都小妮子14 小时前
不想频繁改 PLC?用 DeviceXPlorer Lua 脚本把产线业务逻辑放到 OPC Server 层
java·junit·lua·takebishi·dxpserver·设备数据采集软件·opc server
雪宫街道16 小时前
SpringBoot 向 IOC 容器注册组件的两种姿势:@Configuration 与 @Import
java·spring boot·后端·spring
接着奏乐接着舞16 小时前
springboot mp mybatis plaus
windows·spring boot·mybatis
金融支付架构实战指南17 小时前
Milvus 向量检索服务 + SpringBoot 实战:电商商品语义检索与相似商品推荐
spring boot·后端·milvus·向量检索