일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- jpa
- Lock
- 데이터 타입
- reflection
- CAS
- 가비지 컬렉션
- Varchar
- iterable
- iterator
- MySQL
- Locking Read
- 동시성
- Synchronized
- foreach
- 자바
- 스프링
- gc
- 동시성 문제
- db
- Di
- MVCC
- 백엔드
- text
- Atomic Type
- java
- 가비지 컬렉터
- Today
- Total
과정을 즐기자
테스트 코드 작성시 @Transactional 사용하지 않기 본문
테스트 코드에서 @Transactional은 데이터베이스도 롤백이 되기 때문에 @BeforeEach에서 일일이
repository를 deleteAll 해주지 않고 외래키 문제로 고민할 필요도 없기 때문에 편리합니다.
그래서 자주 사용을 했었는데 한가지 문제점이 있습니다.
바로 원래 코드에서 @Transactional이 없더라도 테스트 코드에서 @Transactional을 사용한다면
하나의 트랜잭션으로 묶여 원래 코드와 다른 결과를 나타낼 수 있다는 점입니다.
이 문제점은 예전 글에서 작성한 적이 있습니다.
'위 테스트가 실패를 했다. 이유는 테스트 코드에도 @Transactional을 적용해줘야 하기 때문이다.
@Transaction을 적용해주니 테스트가 통과하였다. 하지만 이때 Service의 @Transaction을 지워도
테스트에 @Transaction이 있어서 테스트가 통과하였다. 애플리케이션의 작동 여부를 체크하기 위한
테스트 코드인데 뭔가 찝찝한 느낌이다... 혹시 다른 방법이 있으려나... 찾으면 다시 글을 작성하기로..'
위 글에서 이러한 문장을 작성한 적이 있는데 더 좋은 방법을 찾아 이번 글에서 정리해보려고 합니다.
먼저 참고한 자료입니다
위 글에서 말하는 것은 '모든 테스트를 서로 독립적으로 실행하고 격리시키기 위해 테이블 명세만
남기고 모든 데이터를 제거하는 TRUNCATE 명령어를 모든 테이블에 수행하자' 라는 것 입니다.
위 글에서 정말 정말 자세히 설명이 되어있기 때문에 저는 간단히 사용법만 정리하겠습니다.
AcceptanceTestExecutionListener
public class AcceptanceTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void afterTestMethod(final TestContext testContext) {
final JdbcTemplate jdbcTemplate = getJdbcTemplate(testContext);
final List<String> truncateQueries = getTruncateQueries(jdbcTemplate);
truncateTables(jdbcTemplate, truncateQueries);
}
private JdbcTemplate getJdbcTemplate(final TestContext testContext) {
return testContext.getApplicationContext().getBean(JdbcTemplate.class);
}
private List<String> getTruncateQueries(final JdbcTemplate jdbcTemplate) {
return jdbcTemplate.queryForList("SELECT Concat('TRUNCATE TABLE ', TABLE_NAME,
';') " + "AS q FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA
= 'PUBLIC'", String.class);
}
private void truncateTables(final JdbcTemplate jdbcTemplate,
final List<String> truncateQueries) {
execute(jdbcTemplate, "SET REFERENTIAL_INTEGRITY FALSE");
truncateQueries.forEach(v -> execute(jdbcTemplate, v));
execute(jdbcTemplate, "SET REFERENTIAL_INTEGRITY TRUE");
}
private void execute(final JdbcTemplate jdbcTemplate, final String query) {
jdbcTemplate.execute(query);
}
}
H2 인메모리 DB를 사용할 때 위와 같이 쿼리문을 작성한 메소드를 만듭니다.
AcceptanceTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Retention(RetentionPolicy.RUNTIME)
@TestExecutionListeners(value = {AcceptanceTestExecutionListener.class,},
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public @interface AcceptanceTest {
}
여러 메타 에노테이션을 섞은 합성 에노테이션을 만듭니다.
@AcceptanceTest
public class ServiceTest {
...
}
테스트를 작성할 때 AcceptanceTest 에노테이션을 사용하면 됩니다.
이렇게하면 @Transactional을 사용할 때의 문제점도 해결하고 @BeforeEach를 사용하여
직접 deleteAll 할 때 외래키문제로 오류가 발생하는 문제도 해결하여 독립적인 테스트를
작성할 수 있습니다!!
추가
테스트 코드에서 @Trasactional 사용에 대한 논쟁에 대해 정리하고 제가 사용하는 방법을 정리해보았습니다.
'테스트 코드' 카테고리의 다른 글
제어할 수 없는 영역을 테스트 하는 방법 (0) | 2024.06.30 |
---|---|
저희 팀에서는 이렇게 테스트 코드를 작성해요 (0) | 2024.05.11 |
테스트 코드에서 @Transactional 사용에 대한 끊임 없는 논쟁 (0) | 2024.03.01 |