Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- Azure
- bind
- builder
- button
- c++
- c++ builder
- c03
Archives
- Today
- Total
기록
[테스트전략] Test Fixture 독립성 보장 본문
시작하면서
테스트는 독립적으로 실행되어야 하지만, 공유 자원을 사용할 경우 문제가 발생할 수 있습니다. 예를 들어, @BeforeAll, @BeforeEach로 테스트 환경을 설정하고 모든 테스트에서 같은 데이터를 사용하면, 한 테스트에서 변경된 데이터가 다른 테스트에 영향을 줄 수 있습니다.
Test Fixture의 개념
- Test Fixture란 테스트를 위해 필요한 상태로 고정된 일련의 객체나 데이터를 말합니다.
- Given 절에서 필요한 객체를 생성하는 과정이 반복되다 보면 중복 코드가 늘어날 수 있습니다.
- 하지만 중복을 제거하려는 목적으로 모든 데이터를 공통화하면 공유 자원 문제로 인해 테스트 독립성이 깨질 위험이 있습니다.
Test Fixture 구성과 클렌징 전략
Test Fixture 구성 시 주의점
- @BeforeEach, @BeforeAll 사용 시 주의점
- 각 테스트 입장에서 봤을 때, Fixture의 내용을 알지 못해도 테스트를 이해하는 데 문제가 없어야 합니다.
- 수정해도 모든 테스트에 영향을 주지 않도록 설계해야 합니다.
- Data.sql 사용 금지
- 데이터와 테스트 로직이 분리되면서 관리가 어려워지고, 테스트를 이해하기 힘들어집니다.
- 테스트 코드에서 필요한 데이터를 명시적으로 작성하여 테스트가 문서로서 역할을 할 수 있도록 해야 합니다.
클렌징 전략: @AfterEach와 deleteAllInBatch 사용
테스트 실행 후 생성된 데이터를 삭제하여 상태를 초기화합니다.
@AfterEach
void tearDown() {
orderProductRepository.deleteAllInBatch();
orderRepository.deleteAllInBatch();
productRepository.deleteAllInBatch();
mailSendHistoryRepository.deleteAllInBatch();
}
deleteAll과 deleteAllInBatch의 차이
- deleteAllInBatch:
- DELETE FROM table 형태로 전체 데이터를 삭제합니다.
- 테이블 전체를 한 번에 삭제하기 때문에 성능이 뛰어나며, 외래키 제약 조건에 유의해야 합니다.
- deleteAll:
- SELECT * FROM table; DELETE FROM table WHERE id = ? 형태로 데이터를 조회한 후, 하나씩 삭제합니다.
- 모델 간 관계를 고려하여 중간 테이블도 자동으로 삭제하지만, 속도가 느립니다.
그런데 왜 Test Fixture는 공유하면 안 되나?
테스트 간에 자원을 공유하면 예상치 못한 결과를 초래할 수 있습니다. 공유 자원이란, 테스트 간에 같은 객체나 데이터를 사용하는 경우를 말합니다. 예를 들어, @BeforeAll, @BeforeEach로 테스트 환경을 설정하고 모든 테스트에서 같은 데이터를 사용하면, 한 테스트에서 변경된 데이터가 다른 테스트에 영향을 줄 수 있습니다.
잘못된 테스트 코드: 공유 자원 사용
다음은 @BeforeAll로 도서관의 책 데이터를 미리 설정하고, 테스트에서 이 데이터를 사용하는 코드입니다. 이 코드는 각 테스트가 동일한 데이터를 공유하며, 테스트 실행 순서에 따라 결과가 달라질 수 있습니다.
static BookRepository bookRepository;
static StockRepository stockRepository;
@BeforeAll
static void setup() {
// 모든 테스트에서 공유할 데이터 생성
bookRepository = new BookRepository();
stockRepository = new StockRepository();
Book book1 = new Book("001", "The Catcher in the Rye");
Book book2 = new Book("002", "1984");
bookRepository.saveAll(List.of(book1, book2));
Stock stock1 = new Stock("001", 2); // 초기 재고 2개
Stock stock2 = new Stock("002", 1); // 초기 재고 1개
stockRepository.saveAll(List.of(stock1, stock2));
}
@DisplayName("책 대여 시 재고를 줄인다.")
@Test
void borrowBookReducesStock() {
// given
BorrowRequest request = BorrowRequest.builder()
.bookIds(List.of("001"))
.build();
// when
borrowService.borrowBooks(request);
// then
Stock stock = stockRepository.findByBookId("001");
assertThat(stock.getQuantity()).isEqualTo(1); // 재고 2 -> 1
}
@DisplayName("책 대여 시 재고가 부족하면 예외가 발생한다.")
@Test
void borrowBookThrowsExceptionWhenNoStock() {
// given
BorrowRequest request = BorrowRequest.builder()
.bookIds(List.of("002", "002")) // 재고 초과 요청
.build();
// when / then
assertThatThrownBy(() -> borrowService.borrowBooks(request))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("대여 가능한 책이 부족합니다.");
}
문제점
- @BeforeAll로 데이터를 설정: setup() 메서드에서 모든 테스트가 공유하는 데이터를 초기화했습니다. 이는 테스트 간에 동일한 객체(책과 재고)를 사용하게 만듭니다.
- 테스트 순서 의존성: borrowBookReducesStock()에서 001의 재고를 1로 줄인 후, 다른 테스트가 실행되면 001의 재고 상태가 이미 변경된 상태에서 테스트가 실행됩니다. 이는 테스트 결과가 실행 순서에 따라 달라질 수 있음을 의미합니다.
- 독립성 결여: 각 테스트가 공유 데이터를 사용하기 때문에, 특정 테스트가 데이터를 변경하면 다른 테스트에 영향을 미칩니다.
독립성을 보장하는 테스트 코드
테스트의 독립성을 보장하려면, 각 테스트에서 필요한 데이터를 새로 생성해야 합니다.
@DisplayName("책 대여 시 재고를 줄인다.")
@Test
void borrowBookReducesStock() {
// given
bookRepository = new BookRepository();
stockRepository = new StockRepository();
Book book1 = new Book("001", "The Catcher in the Rye");
Book book2 = new Book("002", "1984");
bookRepository.saveAll(List.of(book1, book2));
Stock stock1 = new Stock("001", 2); // 초기 재고 2개
Stock stock2 = new Stock("002", 1); // 초기 재고 1개
stockRepository.saveAll(List.of(stock1, stock2));
BorrowRequest request = BorrowRequest.builder()
.bookIds(List.of("001"))
.build();
// when
borrowService.borrowBooks(request);
// then
Stock stock = stockRepository.findByBookId("001");
assertThat(stock.getQuantity()).isEqualTo(1); // 재고 2 -> 1
}
개선된 점
- 테스트 간 독립성 보장: 각 테스트는 독립적으로 실행되며, 다른 테스트의 실행 결과에 영향을 받지 않습니다.
- 테스트 결과 일관성: 어떤 순서로 테스트를 실행하더라도 동일한 결과를 보장합니다.
결론
테스트 간에 자원을 공유하면 예상치 못한 결과를 초래할 수 있습니다.
1. 각 테스트는 독립적으로 실행되며, 새로운 객체와 데이터를 생성해야 합니다.
2. 테스트 실행 순서에 관계없이 동일한 결과를 보장해야 합니다.
3. 클렌징 전략을 사용하면 테스트 환경을 항상 초기 상태로 유지할 수 있습니다.
'교육 > 강의' 카테고리의 다른 글
[테스트 전략] 현재시간, Private 메서드, 테스트 전용 메서드 (0) | 2025.02.11 |
---|---|
[테스트전략] 테스트 수행 시간을 줄이기 위한 환경 통합 (0) | 2025.02.11 |
[테스트전략] 각 테스트는 하나의 목적만 가진다 (0) | 2025.02.09 |
[Mock] 스프링 환경에서 외부 시스템 테스트하기 (0) | 2025.02.06 |
[JpaTest] CQRS와 서비스 레이어에서의 트랜잭션 관리 (0) | 2025.02.05 |
Comments