(SpringBoot 2.6.6 버전을 기준으로 작성되었습니다.)
우아한테크코스 레벨 1에서 작성했던 체스 미션을 레벨 2에서 스프링 어플리케이션으로 바꾸는 과정을 진행하고 있다. JdbcTemplate를 사용하는 DAO를 만들고, 컨트롤러에서 해당 DAO를 사용하는 방식으로 어플리케이션이 동작하게 되는데, DAO와 컨트롤러에 대한 테스트를 진행하는데 문제에 봉착했다.
메인 어플리케이션 자체는 도커에 올린 mysql과 연결되어 작동한다. 문제는 테스트 코드가 돌아갈 때 실제 DB에 접근하게 되면 다음의 문제가 발생할 수 있다.
따라서 메인에서 쓰는 데이터베이스와 테스트에서 쓰이는 데이터베이스를 분리할 필요를 느꼈다. 기존 레벨 1에서는 이를 위해 데이터베이스가 아니라 HashMap을 저장소로 쓰는 FakeDao 객체를 만들어서 사용했었다.
FakeGameDao.java
public class FakeGameDao implements GameDao {
private final Map<Long, GameState> games;
public FakeGameDao() {
this.games = new HashMap<>();
}
@Override
public void save(long id) {
games.put(id, GameState.READY);
}
@Override
public Optional<GameState> load(long id) {
if (games.containsKey(id)) {
return Optional.of(games.get(id));
}
return Optional.empty();
}
@Override
public void updateState(long id, GameState gameState) {
games.put(id, gameState);
}
@Override
public void delete(long id) {
games.remove(id);
}
}
ChessServiceTest.java
class ChessServiceTest {
private final ChessService chessService = new ChessService(new FakeGameDao(), new FakePieceDao());
...
}
하지만 스프링으로 체스를 이식하려다 보니 문제가 생겼는데, 기존에는 FakeDao 객체들을 생성해서 서비스 객체에 주입해서 ServiceTest를 진행했고, 별도의 ControllerTest가 존재하지 않았기 때문에 크게 문제가 되지 않았다. 하지만 스프링 테스트를 진행하려고 하니 첫째로 저 FakeDao들을 다 스프링 빈으로 등록해서 서비스에 주입시켜줄 필요가 있었고, 컨트롤러를 테스트 할 때 원래 쓰는 서비스가 아닌 FakeDao를 사용하는 서비스를 컨트롤러에 주입시켜줄 필요가 있었다. 이 과정에서 가짜 객체들을 주입시켜주는 방법을 찾아보면서 적용해보려고 노력했지만 실패했다.
그래서, 다른 방법을 찾았다. 아예 다른 데이터베이스를 테스트에 붙여주기로 한 것이다. 이를 위해서 application.properties
및 application.yml
에 대해서 이해할 필요가 있었다.
스프링부트는 어플리케이션에서 사용하는 여러 설정 값들을 properties 파일 또는 yaml 파일에 작성하여 어플리케이션에 적용해줄 수 있다. 기본적으로 main/resources 디렉토리 아래에 application.properties
파일 또는 application.yml
파일을 작성하게 되면 스프링부트가 자동으로 스캔해서 설정을 불러오게 된다. 그래서 데이터베이스 등의 설정을 할 때, 굳이 내부에 클래스로 설정 관련된 빈을 만들어서 설정을 입력해줄 필요 없이, properties 파일 또는 yaml 파일에 원하는 설정을 작동해주기만 하면 된다.
/main/resources/application.yml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:13306/chess?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC;
username: root
password: root
application.yml 파일에 데이터베이스 설정을 작성해주고, DAO를 JdbcTemplate를 사용하도록 변경했더니 정상적으로 작동되었다. JdbcTemplate를 사용하려면 기존에 데이터베이스 커넥션 정보를 담고 있던 DBConnection 객체를 DataSource 빈으로 등록해주어야 하는데, 그 정보를 그냥 yaml 파일에 담는 것 만으로 스프링부트가 자동으로 DataSource 빈으로 데이터베이스 정보를 등록해주어 사용할 수 있게 되는 것이다.
하지만 이렇게 하더라도 궁극적인 목표인 테스트에서의 데이터베이스 분리까지는 아직 한 단계 남았다. application.yml로 분리해주었지만 메인 어플리케이션 및 테스트 코드가 모두 application.yml에 작성한 DataSource대로 작동하게 된다. 이게 무슨 소리냐면, 테스트 코드에서 JdbcTemplate에 DataSource를 자동 주입할 때 메인에서 사용하려고 하는 mysql의 커넥션이 주입되고, 해당 JdbcTemplate를 쓰는 Dao, 서비스를 자동으로 주입할 때도 mysql 커넥션을 사용하는 Dao, 서비스가 주입되게 된다.
그렇다면 테스트 시에 다른 데이터베이스 정보를 주입해주려면 어떻게 해야 할까? 사실 간단하다. test 디렉토리에도 application.yml
파일을 작성해주면 된다.
/test/resources/application.yml
spring:
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb;MODE=MySQL;
username: sa
password:
테스트 데이터베이스로는 간편하게 인메모리로 테스트를 할 수 있도록 h2 데이터베이스를 사용하기로 결정했다. 위와 같이 설정을 해 주면 테스트 코드가 실행될 때 설정한 데이터베이스가 메모리에 올라가고, 테스트가 종료되면 데이터베이스가 메모리에서 내려가게 된다.
하지만 테스트 코드가 실패한다. 테스트 데이터베이스에는 메인 데이터베이스의 스키마가 생성되지 않았기 때문이다. 스키마를 작성하려면 schema.sql
파일을 작성해서 resources 아래에 넣어주기만 하면 된다.
CREATE TABLE game
(
game_id int NOT NULL PRIMARY KEY,
state varchar(20) NOT NULL
);
CREATE TABLE piece
(
piece_id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
game_id INT NOT NULL,
position VARCHAR(10) NOT NULL,
piece_type VARCHAR(10) NOT NULL,
color VARCHAR(10) NOT NULL,
FOREIGN KEY (game_id)
REFERENCES game (game_id) ON DELETE CASCADE
);
테스트 디렉토리에 schema.sql 파일이 있기 때문에, SpringBootTest가 실행되면 자동으로 schema.sql 파일이 스캔되어 데이터베이스가 생성된다.
이제 메인 데이터베이스를 건드리지 않고도 테스트를 실행할 수 있게 되었다!