spring boot如何不依赖外部redis、mysql等中间件也不mock实现集成测试
2025-01-24 09:10 阅读(68)

https://www.zuocode.com

背景

之前聊过很多测试方法,但主要是单测

如果是集成测试依赖的中间件也是外部服务上的中间件。

比如服务器上部署的redis、mysql等

今天我们要讨论的就是如何脱离外部服务器上的中间件,本地基于docker进行容器化集成测试。

实际很多开源项目的集成测试都是这么干的

Testcontainers是啥

Testcontainers是一个Java库,用于在JVM测试中管理Docker容器。

如果我们想要进行不依赖外部服务器的集成测试,Testcontainers必不可少

接下来我们就演示一个最简单spring boot + redis的集成测试案例帮助大家更好的学习如何在spring boot中不依赖外部中间件进行容器化集成测试


测试

引入依赖

首先我们需要引入测试相关的依赖

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>1.17.6</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <version>1.20.4</version>
            <scope>test</scope>
        </dependency>
    
        
    </dependencies>

核心依赖的测试依赖主要是如下几个


spring-boot-starter-test:spring boot测试依赖

junit-jupiter:junit5测试依赖

testcontainers:testcontainers测试依赖


编写集成测试

这里先给出所有代码,下面再逐步分析核心代码

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
@Testcontainers
class TestControllerTest {

    @Container
    public static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:6.2.6"))
        .withExposedPorts(6379);

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

    private final static Long SLEEP_TIME = 3L;

    @Autowired
    private FluxCacheProperties cacheProperties;

    @Autowired
    private TestController testController;

    @Autowired
    private DefaultFluxCacheManager cacheManager;

    @Test
    public void testFirstCacheByCaffeine() {
        List<StudentVO> vos = testController.firstCacheByCaffeine("aaa");
        StudentVO vo = vos.get(0);
        int age = vo.getAge();
        List<StudentVO> vos1 = testController.firstCacheByCaffeine("aaa");
        StudentVO vo1 = vos1.get(0);
        int age1 = vo1.getAge();
        assertEquals(age, age1);
        List<StudentVO> vos2 = testController.firstCacheByCaffeine("bb");
        StudentVO vo2 = vos2.get(0);
        assertNotEquals(age, vo2.getAge());
    }
}

添加@Testcontainers注解,表示使用Testcontainers 来管理 Docker 容器

使用@Container注解管理redis容器

    @Container
    public static GenericContainer<?> redis = new GenericContainer<>(DockerImageName.parse("redis:6.2.6"))
        .withExposedPorts(6379);

表示启动一个redis docker容器,版本为6.2.6,并且映射到主机的6379端口


使用DynamicPropertySource进行动态注入redis的host和port

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
        System.out.println(redis.getHost());
        System.out.println(redis.getMappedPort(6379).toString());
        registry.add("redis.host", redis::getHost);
        registry.add("redis.port", () -> redis.getMappedPort(6379).toString());
    }

这里因为我的配置类中读取的配置key为redis.host和redis.port,所以这里动态注入这两个值


比如我的redission配置类如下

@Configuration
public class RedissonConfig {

    @Value("${redis.host}")
    private String redisLoginHost;
    @Value("${redis.port}")
    private Integer redisLoginPort;
    @Value("${redis.password}")
    private String redisLoginPassword;

    @Bean
    public RedissonClient redissonClient() {
        return createRedis(redisLoginHost, redisLoginPort, redisLoginPassword);
    }

    private RedissonClient createRedis(String redisHost, Integer redisPort, String redisPassword) {
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setAddress("redis://" + redisHost + ":" + redisPort + "");
/*        if (Objects.nonNull(redisPassword)) {
            singleServerConfig.setPassword(redisPassword);
        }*/
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        config.setCodec(new JsonJacksonCodec(objectMapper));
        return Redisson.create(config);
    }

}

如果使用的是其他key,这里动态注入你设置的key即可

这样配置完成后我们就可以直接运行我们的集成测试代码了,比如我这里是testFirstCacheByCaffeine

测试

直接运行测试类,如果是初次运行我们观察log会发现有下载镜像的log,然后就是一些正常的docker启动log

然后可以看到我们的测试正常启动,无需要依赖外部的redis,我们在启动集成测试的时候testcontainers会自动帮我们启动相应的docker容器

总结

Testcontainers是一个非常好用的库,可以帮助我们在集成测试的时候脱离外部服务器的依赖,本地基于docker进行容器化集成测试

这样我们就可以更好的保证我们的集成测试的独立性,同时也可以更好的保证我们的测试的稳定性

一些常用的开源框架的集成测试也是基于Testcontainers来进行的

Testcontainers + ci/cd可以在每次代码修改后进行自动化测试,保证代码的质量


作者:小奏技术

链接:https://juejin.cn