참고
이동욱님 [Redis] SpringBoot Data Redis 로컬/통합 테스트 환경 구축하기
redis를 붙이며 가장 큰 문제가 생긴건 build 할 경우 해당 서버에 무조건 redis가 켜져있어야한다는 것이다. 물론 테스트 서버와 jenkins 서버에 모두 redis가 설치되어 있어야하며 만약에 우리 회사에 새로운 개발자 분이 오셨을 때 코드가 잘 실행되는지 테스트를 실행했을 때 redis를 설정해두지 않으면 테스트 조차 실행되지 않을 것이다.
또한 이동욱님의 글을 참고하면 최소한 테스트 환경은 아무런 설정없이 실행될 수 있도록 설정해두는 것을 지향한다고 하셨다. 그래서 나도 redis를 테스트 환경에 맞게 돌아갈 수 있게 진행해보려 한다.
server:
port: 0
spring:
profiles:
active: local #테스트 환경 구축후에는 test로 결과 보면서 테스트 진행
application:
name: login-service
eureka:
instance:
instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: ${server.eureka.url:http://localhost:8761}/eureka
management:
endpoints:
web:
exposure:
include: refresh, health, beans, httptrace
token:
access:
expiration_time: 15 #15분
refresh:
expiration_time: 1440 #24시간
secret: test_secret
--- #local
spring:
config:
activate:
on-profile: local
h2:
console:
enabled: true
settings:
web-allow-others: true
path: /h2-console
jpa:
open-in-view: false #OSIV(Open Session In View) false일 경우 영속성 컨텍스트가 서비스까지만 존재
hibernate:
ddl-auto: create #절대 수정 금지
show-sql: true
datasource:
hikari:
driver-class-name: org.h2.Driver
jdbc-url: jdbc:h2:mem:testdb #실제 서버에서는 내장 h2 db 서버로 연결해야함! ex) jdbc:h2:mem:test-db
username: sa
password:
redis:
host: localhost
port: 6379
--- #test
spring:
config:
activate:
on-profile: test
h2:
console:
enabled: true
settings:
web-allow-others: true
path: /h2-console
jpa:
open-in-view: false #OSIV(Open Session In View) false일 경우 영속성 컨텍스트가 서비스까지만 존재
hibernate:
ddl-auto: create #테스트 방법에 맞게 수정해서 사용
show-sql: true
datasource:
hikari:
driver-class-name: org.h2.Driver
jdbc-url: jdbc:h2:tcp://localhost/~/testdb #실제 서버에서는 내장 h2 db 서버로 연결해야함! ex) jdbc:h2:mem:test-db
username: sa
password:
redis:
host: localhost
port: 6379
/test/resources/application.yml 파일에 다음과 같이 작성했다. local은 h2와 redis를 모두 내장으로 돌리려는 profile이고 test는 혹시나 실제 데이터가 잘 들어가고 redis도 실제 데이터를 확인하고 싶다면 실행하도록 test라는 profile로 빼두었다. 실제 서버를 운영할 때도 test라는 profile을 추가해서 로컬에서 내장 테스트와 서버를 연결해서 하는 테스트로 나눠서 진행하려고 한다.
<!-- test 내장 redis 추가 -->
<dependency>
<groupId>it.ozimov</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.7.3</version>
<exclusions>
<!-- slf4j simple이 기존 boot 설정과 충돌로 인해 제외 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
embedded redis의 경우 slf4j simple 설정이 추가로 잡혀 있어서 충돌 에러 문구가 나오게 된다. exclusion을 통해 해당 설정 부분을 제외하고 의존성을 추가하면 해당 에러를 해결할 수 있다.
@Configuration
@RequiredArgsConstructor
@Profile(value = "local")
public class EmbeddedRedisConfig {
private RedisServer redisServer;
@Value("${spring.redis.port}")
private int redisPort;
@PostConstruct
public void redisServer() throws IOException {
int port = isRedisRunning()? findAvailablePort() : redisPort;
redisServer = new RedisServer(port);
redisServer.start();
}
@PreDestroy
public void stopRedis(){
if(redisServer != null){
redisServer.stop();
}
}
/**
* Embedded Redis가 현재 실행중인지 확인
*/
private boolean isRedisRunning() throws IOException {
return isRunning(executeGrepProcessCommand(redisPort));
}
/**
* 현재 PC/서버에서 사용가능한 포트 조회
*/
public int findAvailablePort() throws IOException {
for (int port = 10000; port <= 65535; port++) {
Process process = executeGrepProcessCommand(port);
if (!isRunning(process)) {
return port;
}
}
throw new IllegalArgumentException("Not Found Available port: 10000 ~ 65535");
}
/**
* 해당 port를 사용중인 프로세스 확인하는 sh 실행
*/
private Process executeGrepProcessCommand(int port) throws IOException {
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
if(os.contains("win")){
String command = String.format("netstat -nao | find \"LISTEN\" | find \"%d\"", port);
String[] shell = {"cmd.exe", "/y", "/c", command};
return Runtime.getRuntime().exec(shell);
}else if(os.contains("linux")) {
String command = String.format("netstat -nat | grep LISTEN|grep %d", port);
String[] shell = {"/bin/sh", "-c", command};
return Runtime.getRuntime().exec(shell);
}else if(os.contains("mac")){
}else{
throw new RuntimeException("해결되지 않은 OS 입니다. executeGrepProcessCommand() os 명령어 코드를 추가해주세요.");
}
return null;
}
/**
* 해당 Process가 현재 실행중인지 확인
*/
private boolean isRunning(Process process) {
String line;
StringBuilder pidInfo = new StringBuilder();
try (BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
while ((line = input.readLine()) != null) {
pidInfo.append(line);
}
} catch (Exception e) {
}
return !StringUtils.isEmpty(pidInfo.toString());
}
}
가장 애를 먹었던 부분인데. 우리 회사는 개발은 window에서 진행하고 실제 서버는 linux 서버로 진행된다. mac을 통한 해결 방법은 많이 나와있었지만 window에 대한 해결방법을 못 찾고 있던 중 이동욱님 블로그 글 댓글에
해당 분의 댓글에 해결방법이 적혀져 있었다ㅜㅜ widnow 명령어로 변경해서 사용하면 되는데 그 부분을 몰라서 애를 먹다가 해당 분 덕분에 바로 해결할 수 있었다. 그래서 내 코드를 보면
/**
* 해당 port를 사용중인 프로세스 확인하는 sh 실행
*/
private Process executeGrepProcessCommand(int port) throws IOException {
String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
if(os.contains("win")){
String command = String.format("netstat -nao | find \"LISTEN\" | find \"%d\"", port);
String[] shell = {"cmd.exe", "/y", "/c", command};
return Runtime.getRuntime().exec(shell);
}else if(os.contains("linux")) {
String command = String.format("netstat -nat | grep LISTEN|grep %d", port);
String[] shell = {"/bin/sh", "-c", command};
return Runtime.getRuntime().exec(shell);
}else if(os.contains("mac")){
}else{
throw new RuntimeException("해결되지 않은 OS 입니다. executeGrepProcessCommand() os 명령어 코드를 추가해주세요.");
}
return null;
}
해당 메서드 부분만 수정해서 사용했다. window 경우 해당 포트가 실행중인지 확인하는 sh 부분을 다르게 작성해야하기 때문에 다음과 같이 분기처리를 해두었다. 회사에 mac 사용하시는 분들도 있는데... mac은 또 intell용과 m1용이 나눠서 설정해야하더라... 나중에 꼭 작성해야겠다ㅜㅜ
그리고 나서 테스트를 진행해보면
local로 package를 돌리게 되면
따로 h2 서버나 redis 서버를 실행시키지 않더라도 테스트가 잘 진행된다. 만약 redis에 내용을 보고 싶다면 redis 서버를 포트번호를 맞게 설정해서 실행시키면 된다. 위 부분은 테스트를 진행할 때 여러 redis가 동시에 올락면서 충돌하는 문제를 해결하기 위한 코드였다. 다른 더 쉬운 설정이 있었다면 좋았겠지만 인터넷 상에는 이게 최선인거 같다!