Arthur's Blog
JUnit 테스트는 Thread를 재사용한다. 본문
JUnit 테스트는 당연히 스레드를 재사용해야 합니다. 테스트 케이스 하나하나를 실행할 때마다 스레드를 재할당하면 테스트가 하루종일 걸릴 수 있기 때문입니다.
최근 GitHub에서 진행한 프로젝트 leeseojune53/LearnBoot는 Spring Boot의 주요 기능을 학습하기 위해 보안 및 데이터베이스 접근 기능을 직접 구현해보는 실습용 프레임워크입니다.
이 프레임워크를 통해 DB Access의 테스트 케이스(TC)를 작성하고 로직을 구현하여 실행하는 중, 아래와 같은 오류가 발생했습니다:
java.lang.RuntimeException: java.sql.SQLNonTransientConnectionException: No operations allowed after connection closed.
즉, 이미 닫힌 연결(connection)은 조작할 수 없다는 의미입니다.
원인 분석
DB Access 시 사용하는 SessionManager에서 트랜잭션 관리를 ThreadLocal에 하였기에, 테스트 케이스를 돌리는 동안 동일한 Connection 객체를 사용하려 했습니다. 이는 테스트 진행 시 각각의 스레드가 동일한 Connection 객체를 재사용하여 생긴 문제입니다.
해결 방안
처음에는 Connection을 다시 연결하는 방안을 고려했지만, Java의 Connection 객체는 상태를 유지하지 않는(stateless) 특성을 가진다. 따라서 새로운 Connection 객체를 생성해야 합니다.
그래서 SessionManager를 클린업하여 새로운 Connection을 가져올 수 있도록 변경했습니다. 특정 테스트 케이스에서는 SessionManager가 초기화될 수 있게 호출해주었습니다.
// SessionManager의 클린업 예제 코드
public class SessionManager {
private static final ThreadLocal<SessionManager> session = new ThreadLocal<>();
private final Transaction transaction;
... 생략
public static Transaction getTransaction() {
return getInstance().transaction;
}
/**
* ThreadLocal 변수 제거
* 테스트 시 동일 Thread 사용하여 이미 close된 Connection 오류가 발생하여 cleanup 함수 추가.
*/
public static void clean() {
session.remove();
}
}
------------------
@Test
void testWithTransaction() {
// Test 실행 전 SessionManager를 정리한다.
SessionManager.clean();
var applicationContext = new ApplicationContext();
applicationContext.loadBean();
var testClass = applicationContext.get(TransactionService.class);
testClass.transactionAndDoSomething();
}
이제 SessionManager 클린업 매커니즘을 사용하면 트랜잭션이 제대로 관리되어, 각 테스트 케이스에서 새로운 Connection 객체를 생성하여 사용하게 됩니다. 이를 통해 위의 오류를 해결할 수 있었습니다.
이와 같은 방법으로 프로젝트 진행 시 발생하는 문제들을 하나씩 해결해나갈 수 있습니다. 잘 관리된 트랜잭션과 스레드 재사용을 통해 테스트 효율성을 높일 수 있습니다.
'Language > Java' 카테고리의 다른 글
Random과 SecureRandom 차이 (0) | 2023.06.29 |
---|---|
동시성 문제를 제어하는 방법 (0) | 2023.06.28 |
상속에서 @Builder 사용하기 (0) | 2023.06.26 |