테스트 원칙
- 테스트에는 두 가지 중요한 원칙이 있다
- 테스트는 다른 테스트와 격리해야 한다
- 테스트는 반복해서 실행할 수 있어야 한다
- 데이터 접근 계층 테스트 시 데이터 롤백을 시켜주지 않으면 테스트 데이터가 계속해서 누적되고, 서로 다른 테스트 간에 의존성을 가지게 되면서 위 두 가지 원칙을 위반하게 된다
- 테스트가 끝날 때마다 추가된 데이터를 DELETE 해줄 수 있지만, 이 방법 또한 궁극적인 해결책이 아니다
- 만약 테스트 과정에서 데이터를 이미 추가했는데, 테스트가 실행되는 도중에 예외가 발생하거나 애플리케이션이 종료되어버려서 테스트 종료 시점에 DELETE가 이루어지지 않으면 테스트 데이터가 그대로 남아있게 된다
트랜잭션과 롤백 전략
- 트랜잭션을 사용하여 이런 문제를 해결할 수 있다
- 다음 순서와 같이 각각의 테스트 실행 직전에 트랜잭션을 시작하고, 각각의 테스트 실행 직후에 트랜잭션을 롤백해야 테스트 간에 데이터로 영향을 끼치지 않는다
1. 트랜잭션 시작
2. 테스트 A 실행
3. 트랜잭션 롤백
4. 트랜잭션 시작
5. 테스트 B 실행
6. 트랜잭션 롤백
테스트에 직접 트랜잭션 추가
@SpringBootTest
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository;
@Autowired
PlatformTransactionManager transactionManager;
TransactionStatus status;
@BeforeEach
void beforeEach() {
//트랜잭션 시작
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
@AfterEach
void afterEach() {
//트랜잭션 롤백
transactionManager.rollback(status);
}
@Test
void findItems() {
// ...생략
}
}
- 다음과 같이 트랜잭션 매니저를 주입하여 트랜잭션을 활성화시킬 수 있다
- @Transactional을 활용하면 더욱 깔끔하게 코드를 작성할 수 있다
@Transactional을 활용한 트랜잭션 추가
import org.springframework.transaction.annotation.Transactional;
@Transactional
@SpringBootTest
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository;
@Test
void findItems() {
// ...생략
}
}
- 다음과 같이 테스트 클래스에 @Transactional을 붙여주면 별도의 코드 없이 트랜잭션을 활성화시킬 수 있다
- @Transactional이 테스트에 있으면 스프링은 테스트를 트랜잭션 안에서 실행하고, 테스트가 끝나면 트랜잭션을 자동으로 롤백시켜 버린다
@Transactional이 적용된 테스트 동작 방식
@Test
void findItems() {
Item item1 = new Item("itemA-1", 10000, 10);
Item item2 = new Item("itemA-2", 20000, 20);
Item item3 = new Item("itemB-1", 30000, 30);
itemRepository.save(item1);
itemRepository.save(item2);
itemRepository.save(item3);
//..검증코드(SELECT SQL) 생략
}
- @Transactional이 테스트 메서드나 클래스에 있으면 먼저 트랜잭션을 시작한다.
- 테스트 로직을 수행한다. 이때, 테스트가 끝날 때까지 모든 로직은 트랜잭션 안에서 수행된다.
- 테스트 실행 중에 INSERT SQL을 사용해서 item1, item2, item3를 데이터베이스에 저장한다.
- 검증을 위해서 SELECT SQL로 데이터를 조회한다. 여기서는 앞서 저장한 item1, item2, item3이 조회되었다.
- @Transactional이 테스트에 있으면 테스트가 끝날 때 트랜잭션을 강제로 롤백한다.
- 롤백에 의해 앞서 데이터베이스에 저장한 item1, item2, item3의 데이터가 제거된다.
기타
- 클래스가 아닌 메서드에 @Transactionl을 붙이면 메서드 레벨에서만 트랜잭션이 동작한다
- @Commit이나 @Rollback(value = false)를 붙이면 테스트 종료 후 롤백 대신 커밋이 호출된다
메모리 DB (임베디드 모드 DB)
임베디드 모드
- 실제 운영 DB가 아닌 메모리DB를 사용해서 데이터 접근 계층에 대한 테스트를 수행할 수 있다.
- H2 DB는 자바로 개발되어 있고, JVM 안에서 메모리 모드로 동작하는 특별한 기능을 제공하기 때문에 앱을 실행할 때 H2 DB도 해당 JVM 메모리에 포함해서 함께 실행할 수 있다
- DB를 애플리케이션에 내장해서 함께 실행한다고 해서 임베디드 모드(Embedded mode)라고 한다
- 애플리케이션이 종료되면 임베디드 모드로 동작하는 H2 DB도 함께 종료되고, 데이터도 모두 사라진다
- 스프링 부트는 데이터베이스에 대한 별다른 설정이 없으면 메모리 DB를 사용한다
- 스프링 부트에서 메모리 DB를 설정하는 자세한 방법은 https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.sql.datasource.embedded을 참고한다
SQL 스크립트로 데이터베이스 초기화하기
#src/test/resources/schema.sql
drop table if exists item CASCADE;
create table item
(
id bigint generated by default as identity,
item_name varchar(10),
price integer,
quantity integer,
primary key (id)
);
- 메모리 DB는 애플리케이션이 종료될 때 함께 사라지기 때문에 애플리케이션 실행 시점에 DB 테이블도 새로 만들어주어야 한다
- 스프링 부트는 SQL 스크립트를 실행해서 애플리케이션 로딩 시점에 데이터베이스를 초기화하는 기능을 제공한다
- 이를 위해선 SQL 스크립트를 특정한 경로에 정해진 파일명으로 넣어줘야 한다
- src/test/resources/schema.sql
- 자세한 내용은 https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-%20initialization.using-basic-sql-scripts을 참고한다
참고
'Spring > Spring' 카테고리의 다른 글
[Spring] 트랜잭션 AOP 사용 시 주의점 (ft. Spring AOP self-invocation) (0) | 2022.09.01 |
---|---|
[Spring] 테스트 코드를 작성할 때 @Transactional을 주의하라 (4) | 2022.08.17 |
[Spring] 트랜잭션 동기화 정리 (0) | 2022.08.13 |
[Spring] 트랜잭션 추상화 정리 (0) | 2022.08.08 |
[Spring] 스프링 DataSource 간단하게 정리 (0) | 2022.08.08 |