0. 들어가기 전에
Redis
를 도입하면서 테스트 방법이 필요했는데 이에 대해 고민한 글입니다.
1. Redis
테스트는 어떻게 해야할까?
Reids를 도입하면서 테스트를 어떻게 해야할지에 대한 고민이 생겼다. 기존에는 MySQL 대신에 embeded h2를 사용해서 테스트를 했다. 하지만 redis를 도입하면서 이 방법은 사용할 수 없게 되었다. 찾아보니 레디스를 로컬에서 테스트하는 방법은 두 가지 방법이 있었다.
1.1 Embedded Redis
//spring-data-redis
compile('org.springframework.boot:spring-boot-starter-data-redis')
//embedded-redis
compile group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'
build.gradle에 embedded redis
의 의존성을 추가해서 테스트를 하는 방법이다. 추가적인 설정이 필요하지만 h2 처럼 사용할 수 있다.
1.2 Test Container
testImplementation 'org.testcontainers:testcontainers:1.17.2'
2. 선택한 방법
결론적으로 Test Container를 사용하는 방법을 사용하기로 했다. 처음엔 Embedded Redis 사용해서 테스트를 하려고 했다. Embedded redis가 설정이 좀 더 쉽고, Test Container 보다 속도가 더 빠르기 때문이다. 하지만 MAC M1에서 이슈가 있었다. 해결방법이 있었지만 나중에 h2도 Test Container로 바꿀예정이기 때문에 두 번 변경하지 않게 Test Container를 선택했다.
3. 구현 방법
- TestRedisContainerConfig
public class TestRedisContainerConfig implements BeforeAllCallback {
private static final String REDIS_IMAGE = "redis:7.0.8-alpine";
private static final int REDIS_PORT = 6379;
private GenericContainer redisContainer;
@Override
public void beforeAll(ExtensionContext context) {
redisContainer = new GenericContainer(DockerImageName.parse(REDIS_IMAGE))
.withExposedPorts(REDIS_PORT);
redisContainer.start();
System.setProperty("spring.redis.host", redisContainer.getHost());
System.setProperty("spring.redis.port", redisContainer.getMappedPort(6379).toString());
}
}
테스트 속도를 위해 apline 이미지를 사용했다. BeforeAllCallBack
의 beforeAll 메서드를 구현해서 모든 테스트가 실행되기 전에 테스트 컨테이너를 띄우도록 설정했다. 또한 사용할 클래스에 @ExtendedWith(TestRedisContainerConfig.class)
로 선언해줘야한다.
- TestRedisConfig
@Profile("test")
@Configuration
public class TestRedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
@Bean
public RedisTemplate<String, String> redisTemplate() {
final RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
Redis에서 사용가능한 Serializer는 3종류가 있다.
- Jackson2JsonRedisSerializer
새로운 객체를 사용할 때마다 bean으로 등록해주어야 한다. 그렇기 때문에 RedisTemplate<String, T>
로 사용할 수 있어 type casting
이 없이 사용할 수 있다.
- StringRedisSerializer
ObjectMapper
로 직렬화와 역직렬화를 해서 사용해야한다.
- GenericJackson2JsonRedisSerializer
RedisTemplate<String, Object>
형식으로 쓰이며 redis value를 조회할 때 type casting
을 해줘야한다. 클래스의 패키지를 같이 저장하기 때문에 패키지가 변경되면 오류가 난다. MSA와 같은 환경에서 쓸 수 없다는 단점이 있다.
RedisTemplate을 이용해서 토큰 만료 기능(expire
)만을 테스트할 예정이기 때문에, 간단하게 StringRedisSerializer
를 사용했다.
- TestRefreshTokenRepository
@Repository
public class TestRefreshTokenRepository {
private final RedisTemplate<String, String> redisTemplate;
public TestRefreshTokenRepository(final RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void save(final RefreshToken refreshToken) {
redisTemplate.opsForHash().put(String.format("token:refresh:%s", refreshToken.getSocialId()), refreshToken.getToken().getValue(), Duration.ofSeconds(30));
}
public void expireRefreshToken(final RefreshToken refreshToken) {
redisTemplate.expire(String.format("token:refresh:%s", refreshToken.getSocialId()), Duration.ZERO);
}
}
※ 참고
'개발 > [우테코]' 카테고리의 다른 글
락을 사용한 동시성 테스트하기 (1) | 2024.02.27 |
---|---|
확장을 고려해서 TaskScheduler로 자동 리뷰 완료 기능 구현하기 (0) | 2024.02.20 |
Redis를 활용해서 RefreshToken 최적화하기 (0) | 2024.02.08 |
바톤의 DB Replication (0) | 2023.10.14 |
build 할 때 Rest Docs 파일이 생성이 안되는 문제 트러블 슈팅 (0) | 2023.08.20 |