junit BeforeAll 적용기(적용 안함)

VVOO·2024년 8월 23일

배경

저의 원래 목적은 @BeforeAll로 미리 원하는 데이터에 대해 셋팅하고 나머지 테스트를 진행하는 것이였습니다.

그래서 @BeforeAll을 사용했습니다.

하지만 @BeforeAll은 반드시 static으로 선언되어야 한다고 합니다. 한가지 의문이 있었습니다. 왜 굳이 static으로 할까?

우선은 그냥 static을 사용하기로 했습니다.

문제 발생 1

그래서 static을 사용하니

위와 같이 의존성이 제대로 주입되지 않는 상황이 발생했습니다. 분명 @BeforeAll을 사용하기 전에는 문제가 없었는데 말이죠.

사실 왜 안되는 지는 간단합니다.

static은 class가 메모리에 할당됐을 때 메모리에 할당됩니다. 또한 의존성을 주입하기 위해선 인스턴스가 생성되어야 주입이 가능합니다.

즉 class가 메모리에 올라가 static 변수를 할당하고자 하는데 다른 Bean의 인스턴스가 생성되지 않는 상황이니 주입받지 못하는 상황인 것이죠.

그래서 static을 통한 @BeforeAll은 제가 원하는 방향으로는 사용 불가능 합니다.

그렇다면 BeforeAll을 static이 아닌 일반 메서드로 사용하고자 한다면 어떻게 하면 될까요?

해결 1

per-class를 사용해서 해결합니다.

junit의 테스트 실행 방법은 다음과 같습니다.

junit 공식문서 TestInstanse를 참고하면

https://junit.org/junit5/docs/5.0.0/user-guide/#writing-tests-annotations

junit은 per-method 방식으로 진행되는 것을 확인할 수 있습니다.

per-method는 테스트 메서드가 실행되기 전 인스턴스를 새롭게 만들어 테스트를 진행하는 방식입니다. 즉 테스트가 3개라면 테스트가 진행되는 동안 3개의 인스턴스가 만들어지는 것이죠.

이렇게 하면 얻는 이점은 안정성입니다. 변경 가능한 테스트 인스턴스의 영향으로 테스트가 제대로 진행되지 않는 것을 방지하고자 per-method 방식을 기본적으로 junit에서 사용합니다.

자 그럼 위에서 제가 언급한 굳이 @BeforeAll을 static으로 사용해야할까? 라는 의문이 해결됩니다.

테스트 마다 인스턴스가 생성되기에 이에 상관없이 @BeforeAll을 사용할려면 당연하게도 static으로 생성하는 것이 맞습니다.

이러한 per-method 방식은 per-class 방식으로 변경 가능합니다.

per-method가 테스트(method)가 실행되기 전 인스턴스를 생성한다면 per-class는 class가 생성되면 인스턴스를 만드는 방식입니다. 즉 테스트 진행 시 하나의 인스턴스만 만들어지는 방식입니다.

그렇다면 인스턴스가 하나만 만들어지니 굳이 static을 사용하지 않아도 됩니다.

그럼 이제 per-class 방식으로 바꾸고 실행해봅시다.

per-class는 TestInstance를 불러와 아래와 같이 설정해주면 됩니다.

@BeforeAll
    void beforeAll(){
        uid = UUID.randomUUID();
        category1 = Category.builder()
                .user_id(uid)
                .category_name("test category1")
                .build();

        category2 = Category.builder()
                .user_id(uid)
                .category_name("test category2")
                .build();

        method1 = Method.builder()
                .user_id(uid)
                .method_name("test method1")
                .build();
        method2 = Method.builder()
                .user_id(uid)
                .method_name("test method2")
                .build();

        Category c1  = categoryRepository.save(category1);
        cid1 = c1.getId();

        Category c2 = categoryRepository.save(category2);
        cid2 = c2.getId();

        Method m1 = methodRepository.save(method1);
        mid1 = m1.getId();

        Method m2 = methodRepository.save(method2);
        mid2 = m2.getId();
    }

그럼 실행해봅시다.

문제 발생 2

위와 같은 에러가 발생합니다.

Entity manager가 현재 스레드 상에서 사용할 수 없습니다.(?)

EntityManager는 영속성 컨텍스트에 접근하기 위해 사용합니다. 근데 사용할 수 없다고 합니다.

해결 2

No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

Transaction과 연결된 EntityManager가 현재 쓰레드 내에 없다고 합니다.

분명 DataJpaTest에 @Transational이 존재해서 Transaction이 적용되어 있다고 생각했지만 그렇지 않아 보입니다.

그래서 구글링하다가 마지막엔 공식문서를 뒤적여봤습니다.

https://docs.spring.io/spring-framework/reference/testing/testcontext-framework/tx.html

정확히 나와 있습니다... @BeforeAll은 테스트 관리 트랜잭션 내에서 실행 되지 않는다고 합니다.

즉 Transaction과 연결된 EntityManager가 필요한데 저는 EntityManager를 Transaction이 사용되지 않는 @BeforeAll에서 사용했기 때문에 위와 같은 문제가 발생한 것이죠.

그래서 결국 저는 @BeforeEach를 사용했습니다.

@BeforeEach
void beforeEach(){
	uid = UUID.randomUUID();
	category1 = Category.builder()
		.user_id(uid)
		.category_name("test category1")
		.build();

	category2 = Category.builder()
		.user_id(uid)
		.category_name("test category2")
		.build();

	method1 = Method.builder()
		.user_id(uid)
		.method_name("test method1")
		.build();
	method2 = Method.builder()
		.user_id(uid)
		.method_name("test method2")
		.build();

	Category c1  = categoryRepository.save(category1);
	cid1 = c1.getId();

	Category c2 = categoryRepository.save(category2);
	cid2 = c2.getId();

	Method m1 = methodRepository.save(method1);
	mid1 = m1.getId();

	Method m2 = methodRepository.save(method2);
	mid2 = m2.getId();

}

그렇다면 드디어! 아무런 문제없이 테스트가 진행됩니다...

후기

사실 BeforeAll에 집착했던 이유는 단 하나입니다. 말 그대로 모든 테스트 전에 모든 데이터에 대한 셋팅을 해주고 싶었습니다.

그러나 모든 블로그에서는 @BeforeAll이 아닌 @BeforeEach를 사용했고 굳이 이래야하나? 라는 생각에 깊게 매몰되었던 것 같습니다.

junit의 동작을 이해하고 보니 @BeforeEach를 사용하는 것도 이름의 의미 상 문제는 없다고 생각하고 나니 마음이 한결 편해졌습니다.

진작에 공부를 많이 하고 진행했으면 상관없었을텐데...

profile
Ctrl C V 뽑고 작성합니다.(그러고 싶습니다.)

0개의 댓글