JUnit5 테스트 실행 방식 및 주의점
몇몇 테스트를 통해 JUnit 5 의 테스트가 어떻게 돌아가는지 근본적으로 파악해보자.
1. static 영역의 인스턴스.
먼저, 본인이 예전에 작성했다가 지웠던 코드를 간략하게 가져왔다.
@Repository
public class AccountRepository {
private static final long START_ID = 0;
private static AtomicLong ID_GENERATOR = new AtomicLong(START_ID);
public long genId() {
return ID_GENERATOR.incrementAndGet();
}
public void initGenerator() {
ID_GENERATOR = new AtomicLong(START_ID);
}
...
}
static으로 AtomicLong을 만들고 Auto increment 하면서 ID를 생산하고 있다.
다음은 테스트 코드이다.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Slf4j
class GroundTest {
AccountRepository accountRepository = new AccountRepository();
AtomicLong atomicLong = new AtomicLong(0);
@Test
@Order(1)
void _1Test() {
log.info("static-atomic_1: {}", accountRepository.genId());
log.info("junit-atomic_1: {}", atomicLong.incrementAndGet());
}
@Test
@Order(2)
void _2Test() {
log.info("static-atomic_2: {}", accountRepository.genId());
log.info("junit-atomic_2: {}", atomicLong.incrementAndGet());
}
@Test
@Order(3)
void _3Test() {
log.info("static-atomic_3: {}", accountRepository.genId());
log.info("junit-atomic_3: {}", atomicLong.incrementAndGet());
}
}
AccountRepository와 테스트 클래스 내에서 AtomicLong을 따로 만들어 값을 비교하고 있다.
본인은 두개의 경우 다같이 1, 1, 1 이 나오는 것을 기대하였지만 아니었다.

클래스 스코프 내에 존재하는 atomicLong은 매번 새로 할당되며 값이 초기화되는 한편, static 영역의 인스턴스는 테스트가 끝날떄까지 다시 할당되거나 하지 않는다.
2. non-final static 필드에 대한 해결책
만약 위처럼 매 테스트가 static 필드에 의존하고 있고, 그 값이 계속 변하여 다음 테스트에 영향을 준다면 제대로 테스트가 진행되지 못할 것이다.
만약 위와 같은 코드를 사용하고 있고, 이를 쉽게 바꾸지 못하는 상황이라면 해결책은 있다.
Reflection으로 매 테스트마다 값을 주입하는 것이다.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Slf4j
class GroundTest {
AccountRepository accountRepository = new AccountRepository();
AtomicLong atomicLong = new AtomicLong(0);
@BeforeEach
void injectIdGenerator() throws Exception{
// 기본적인 Java Reflection을 이용하는 방법
Field generator = AccountRepository.class.getDeclaredField("ID_GENERATOR");
generator.setAccessible(true);
generator.set(null, new AtomicLong(0));
// Spring framework에서 제공하는 ReflectionTestUtils를 사용하는 방법.
ReflectionTestUtils.setField(
AccountRepository.class, "ID_GENERATOR", new AtomicLong(0)
);
}
@Test
@Order(1)
void _1Test() {
log.info("static-atomic_1: {}", accountRepository.genId());
log.info("junit-atomic_1: {}", atomicLong.incrementAndGet());
}
@Test
@Order(2)
void _2Test() {
log.info("static-atomic_2: {}", accountRepository.genId());
log.info("junit-atomic_2: {}", atomicLong.incrementAndGet());
}
@Test
@Order(3)
void _3Test() {
log.info("static-atomic_3: {}", accountRepository.genId());
log.info("junit-atomic_3: {}", atomicLong.incrementAndGet());
}
}
결과에서 원하는 대로 테스트가 진행됨을 알 수 있다.
static-atomic_1: 1
junit-atomic_1: 1
static-atomic_2: 1
junit-atomic_2: 1
static-atomic_2: 1
junit-atomic_2: 1
3. final 필드의 경우에는?
final 필드를 리플렉션으로 변경하려 하는 경우 다음과 같은 예외를 마주할 수 있다.
java.lang.IllegalAccessException: Can not set static final java.util.concurrent.atomic.AtomicLong field com.flowerfulfort.testground.repository.AccountRepository.ID_GENERATOR to java.util.concurrent.atomic.AtomicLong
at java.base/jdk.internal.reflect.FieldAccessorImpl.throwFinalFieldIllegalAccessException(FieldAccessorImpl.java:137)
at java.base/jdk.internal.reflect.FieldAccessorImpl.throwFinalFieldIllegalAccessException(FieldAccessorImpl.java:141)
at java.base/jdk.internal.reflect.MethodHandleObjectFieldAccessorImpl.set(MethodHandleObjectFieldAccessorImpl.java:103)
at java.base/java.lang.reflect.Field.set(Field.java:836)
at com.flowerfulfort.testground.test.GroundTest.injectIdGenerator(GroundTest.java:36)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
static final 필드는 리플렉션으로 변경할 수 없다. JDK 8 까지는 다음과 같은 방법으로 Field 참조에서 final modifier 자체를 지워 값을 주입할 수 있었다.
Field generator = AccountRepository.class.getDeclaredField("ID_GENERATOR");
generator.setAccessible(true);
generator.setInt(generator, generator.getModifiers() & ~Modifier.FINAL);
generator.set(null, new AtomicLong(0));
그러나 레거시 프로젝트가 아닌 이상 적어도 JDK 12 이상을 사용할 것이기에 static final 필드를 리플렉션으로 변경하려는 시도는 가능하지 않을 것이다.