수많은 통합 테스트들을 작성하다 보면 여러 테스트 메서드들마다 테스트 데이터베이스에 다양한 데이터들을 저장하게 된다. 테스트 메서드들마다 어떤 데이터들을 저장하고 삭제했는지 신경쓰면서 구현할 수도 있지만 어떤 테스트가 다른 테스트에 영향을 받는 것은 좋지 않고, 무엇보다 테스트간 순서가 보장이 안 돼서 오히려 더 어렵다. 그래서 간편하게 @DirtiestContext를 사용하여 다음과 같은 방식을 사용한다.
@ActiveProfiles("test")
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IntegrationTest {
@LocalServerPort
private int port;
@BeforeEach
void setUp() {
RestAssured.port = port;
}
}
하지만 @DirtestContext는 모든 테스트 메서드가 실행될 때마다 Application Context를 재구성하기 때문에 비용적으로 큰 부담이 될 수 있다. 테스트 코드가 별로 없다면 모르겠지만 수백, 수천 개가 된다면 @DirtestContext를 사용하는 건 그다지 좋지 않은 방식이다. 대신 다음과 같이 자동으로 database를 초기화하는 클래스를 작성해둠으로써 많은 시간과 비용을 절약할 수 있다.
@Component
@ActiveProfiles("test")
@Transactional
public class DatabaseCleaner implements InitializingBean {
// 프로젝트에서 생성한 table 이름을 상수로 저장
private static final List<String> generatedTableNames = List.of("MEMBER", "MEMBER_PROFILE",
"MEMBER_PROFILE_IMAGE", "ROADMAP", "ROADMAP_CATEGORY", "ROADMAP_CONTENT", "ROADMAP_NODE",
"ROADMAP_NODE_IMAGE");
@PersistenceContext
private EntityManager entityManager;
private List<String> tableNames;
@Override
public void afterPropertiesSet() {
final Session session = entityManager.unwrap(Session.class);
session.doWork(this::extractTableNames);
}
private void extractTableNames(final Connection conn) throws SQLException {
final List<String> tableNames = new ArrayList<>();
final ResultSet tables = conn.getMetaData()
.getTables(conn.getCatalog(), null, "%", new String[]{"TABLE"});
while (tables.next()) {
tableNames.add(tables.getString("table_name"));
}
// 기존 코드 : this.taleNames = tableNames;
this.tableNames = generatedTableNames;
}
public void execute() {
final Session session = entityManager.unwrap(Session.class);
session.doWork(this::cleanUpDatabase);
}
private void cleanUpDatabase(final Connection conn) throws SQLException {
final Statement statement = conn.createStatement();
statement.executeUpdate("SET REFERENTIAL_INTEGRITY FALSE");
for (final String tableName : tableNames) {
statement.executeUpdate("TRUNCATE TABLE " + tableName);
statement.executeUpdate("ALTER TABLE " + tableName + " ALTER COLUMN id RESTART WITH 1");
}
statement.executeUpdate("SET REFERENTIAL_INTEGRITY TRUE");
}
}
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestConstructor(autowireMode = AutowireMode.ALL)
public class IntegrationTest {
@LocalServerPort
protected int port;
@Autowired
protected ObjectMapper objectMapper;
@Autowired
private DatabaseCleaner databaseCleaner;
@BeforeEach
void setUp() {
RestAssured.port = port;
}
@AfterEach
void tearDown() {
databaseCleaner.execute();
}
}
다음 글을 참조했지만 테스트에서 사용한 데이터베이스인 h2에 default 테이블값들이 등록되어있어서 제대로 실행이 되지 않았다. 그래서 추가적으로 generatedTableNames라는 리스트에 직접 만든 테이블 이름들을 따로 저장해두는 방식으로 구현하니 잘 작동하였다. 매번 저 리스트에 내용을 추가하는 것이 귀찮다면 사용하는 데이터베이스의 default 테이블 이름들을 따로 저장해둔 뒤 그 이름들을 제외하는 식으로 해도 좋을 것 같다. (우테코 크루 져니의 아이디어)
'Spring' 카테고리의 다른 글
[Spring 공식문서] Spring Core 1.5 (0) | 2023.05.07 |
---|---|
[Spring 공식문서] Spring Core 1.4 (0) | 2023.04.30 |
[Spring] DAO는 어떤 값을 주고 받아야 할까?(DAO, DTO, Entity) (2) | 2023.04.20 |
[Spring 공식문서] Spring Core 1.1 ~ 1.3 (0) | 2023.04.16 |