멀티 스레드 테스트에서 발생하는 @Transactional가 주는 문제
@SpringBootTest
class OrderConcurrentTest {
// 의존성 주입 ...
private User user;
private Product product;
private List<OrderInfo> savedOrderList;
@Transactional // 독립적인 테스트를 위해 테스트 메서드 실행 후 롤백되도록 @Transactional 어노테이션 추가
@CsvSource({
"300, 300, 300, 0, 0",
"300, 299, 299, 0, 1",
"300, 301, 300, 1, 0",
"300, 350, 300, 50, 0",
})
@ParameterizedTest
@DisplayName("동시에 승인 요청을 보내면 재고만큼 승인되고 나머지는 실패한다.")
void approveOrderWithMultipleRequests(
int stock,
int orderCount,
int expectedSuccess,
int expectedFail,
int expectedStock
) {
// 테스트 수행 전 데이터 저장
product = productRepository.save(
generateProductWithPriceAndStock(BigDecimal.valueOf(1000), stock)
);
user = userRepository.save(generateUser());
savedOrderList = getSavedOrderList(orderCount); // orderCount만큼 주문 데이터 저장 및 리스트에 저장
AtomicInteger successCount = new AtomicInteger();
AtomicInteger failCount = new AtomicInteger();
// 테스트 수행
executeConcurrentActions(orderIndex -> {
try {
// ...
// 리스트에 저장된 orderIndex번째 주문 승인 요청
OrderConfirmRequest orderConfirmRequest = generateOrderConfirmRequest(
savedOrderList.get(orderIndex)
);
orderController.confirmOrder(orderConfirmRequest); // 실제 테스트 대상 메서드
successCount.incrementAndGet();
} catch (Exception e) {
failCount.incrementAndGet();
}
}, orderCount, 32);
// 테스트 결과 검증
Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
assertThat(updatedProduct.getStock()).isEqualTo(expectedStock);
assertThat(successCount.get()).isEqualTo(expectedSuccess);
assertThat(failCount.get()).isEqualTo(expectedFail);
}
// ...
private void executeConcurrentActions(
Consumer<Integer> action,
int repeatCount,
int threadSize
) {
AtomicInteger atomicInteger = new AtomicInteger();
CountDownLatch countDownLatch = new CountDownLatch(repeatCount);
ExecutorService executorService = Executors.newFixedThreadPool(threadSize);
for (int i = 1; i <= repeatCount; i++) {
executorService.execute(() -> {
int index = atomicInteger.incrementAndGet() - 1;
action.accept(index);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}발생 원인
해결 방법
결론
Last updated