티스토리 뷰

Why?

Service 를 테스트 하면서, Service에서 사용하는 Repository를 Mock 하기보다는 인 메모리(H2)로 사용하고 싶어졌다.

물론, Best Practice는 Repository도 Mocking 을 해서 Service 클래스에 주입하는 것이지만, 되는지 궁금하기도 하고 

어떻게 되는지 알아보려고한다. 물론, 이 글에서 다루는 내용은 위에 말한 것 자체를 다루지는 않는다. 

위 처럼 테스트를 하기 위해서 적절한 Test annotation을 검색해보았는데, 익숙한 @DataJpaTest, @RunWith가 있다. 

그런데, 어떤 글에서 @DataJpaTest 는 JPA에 필요한 객체들만 로딩해서 가볍다고 하는데, @RunWith(SpringRunner.class)를 같이 쓰고 있는 것이다.

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserServiceTest {
	....
} 

그런데, @RunWith(SpringRunner.class) 는 @SpringBootTest 보다 조금 더 가볍다고 듣긴 했는데

어쨋든 대부분의 Bean에 대해 DI를 해주는 등, 꽤나 무거운 작업이고 실제로 경험상 @SpringBootTest랑 크게 

속도 차이가 나지 않음을 경험했다. 

 

What

1. @RunWith와 더불어 @RunWith(SpringRunner.class)가 도대체 뭘 하는지?

2. @RunWith와 @SpringBootTest랑 어떻게 다른가?

3. 언제 뭘 써야 할까?

4. @RunWith와 @DataJpaTest를 같이 쓰는것은 Reasonable 한가?

 

Content

JUnit 클래스나, 부모 클래스가 @RunWith로 어노테이트 되면, 해당 클래스를 default runner가 아닌 test runner를 이용해 JUnit Framework를 호출한다.

출처 : [1] https://www.logicbig.com/tutorials/unit-testing/junit/runner.html

@RunWith는 위와 같은 코드로 되어 있으며, value는 반드시 abstract org.junit.runner.Runner의 서브클래스여야 한다.

Runner 클래스는 JUnit test 를 동작시킬 책임이 있고 일반적으로 reflection으로 이 작업을 수행한다.

그래서 정리하자면, @RunWith는 JUnit 의 기본 러너 대신에 사용할 러너를 지정해주는 것이고, 지정된 러너는

JUnit의 Life cycle method 등(@BeforeClass, @AfterClass ..)을 호출하는 책임도 갖고 있다. 

그냥 넘어가고 싶지만, "Runner(러너)" 라는 키워드가 계속 신경쓰인다. 감만 있고 제대로 모르고 있다.

 

JUnit Runner

참고: dzone.com/articles/understanding-junits-runner

JUnit Runner는 JUnit의 추상 Runner class를 상속한 클래스이고. 테스트 클래스들을 실행하는 역할을 한다.

그래서, 실제로 테스트 할 클래스는 제공받은 Runner에게 던져져서, 러너에서 실행시키게 되는 것이다.

여기서 꽤 재밌는 사실이 있는데, 대부분의 IDE는 JUnit Test 결과를 표출해주는데 JUnit Runner에서 구현하는

Descriabable의 getDescription() 메소드에서 내보내는(export) 정보를 이용해서 테스트 결과를 보여준다고 한다.

츨처 : https://dzone.com/articles/understanding-junits-runner

 

 

[1] www.logicbig.com/tutorials/unit-testing/junit/runner.html

이제, Runner는 너무 generous 하기 때문에, 조금 더 구체적인 클래스인 ParentRunner를 살펴보면

BlockJUnit4ClassRunner가 ParentRunner를 상속받는 것을 볼 수 있다. 참고로 BlockJUnit4ClassRunner는

@RunWith로 Runner를 제공해주지 않으면 사용되는 기본 Runner이다.

 

BlockJUnit4ClassRunner

1. 내부의 getChildren() 메소드를 사용해서 @Test로 어노테이트 된 메소드를 리플렉션을 사용해 몽땅 스캔한다.

  최종적으로, runChild() 를 호출해서 테스트 메소드를 실행시킨다.

 

결론적으로, Runner의 역할은 테스트 실행 프로세스를 계획하고 실행시키는 것이다.

 

@RunWith(SpringRunner.class)

참고 : 

stackoverflow.com/questions/25317009/what-does-this-do-runwithspringjunit4classrunner-class

 

zetcode.com/spring/springrunner/

 

SpringRunner는 SpringJUnit4ClassRunner의 단축어이다. 

SpringRunenr는 유닛 테스트를 하기전에 Spring container를 시작하는 작업을 포함하기 때문에 오버헤드가 크다

따라서 최대한 절약하며(sparingly) 사용해야 한다.

일반적으로 아래와 같은 상황에 사용한다

1. 컴포넌트가 주입되어야 할 때

2. 설정 데이터가 주입되어야 할 때

그리고 아래와 같은 상황에 사용하는 것을 삼가야 한다.

1. Spring과 관련 없는 기능들을 테스트할 때(SpringRunner는 overhead가 크기 때문에)

testContextManager를 생성하는 코드를 볼 수 있는데, 이 TestContextManager 클래스에 대해서 보면

빨간 밑줄 친 곳을 보면, Spring 의 아주 핵심적인 부분인 ApplicationContext를 로딩하고 접근하는 것을 지원한다고 되어있다.

단, @SpringBootTest를 사용하면 모든 application context를 다 로딩하게 되는데 

@RunWith(SpringRunner.class)를 사용한다면, @Autowire, @MockBean에 해당되는 것들에만 application context를

로딩하게 된다.

 

 @RunWith와 @DataJpaTest를 같이 쓰는것은 Reasonable 한가?

일단 @DataJpaTest 를 도대체 왜 써야할까?'

stackoverflow.com/questions/56523416/datajpatest-annotation-usagespring-boot

의 답변에 따르면

@DataJpaTest는 인 메모리 데이터베이스(일반적으로 H2)로 설정하고, @Entity 가 붙은 클래스들을 모조리 스캔한다

그리고, Spring Data JPA repository 도 설정해준다. 또한, Transactional하며 테스트가 끝날 때 rollback 해준다(!)

@DataJpaTest의 코드는 아래와 같다. 

 

쭉 알아보면서 여기까지 도달했지만, 아직 확신이 가지는 않는다.

하지만, 긴 여정이 무색하게 답은 이미 공식 문서에 나와있었다..(ㄴㅇㄱ)

docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.html

 

DataJpaTest (Spring Boot 2.4.0 API)

Annotation for a JPA test that focuses only on JPA components. Using this annotation will disable full auto-configuration and instead apply only configuration relevant to JPA tests. By default, tests annotated with @DataJpaTest are transactional and roll b

docs.spring.io

출처 : https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.html

빨간 박스를 해석해보면

JUnit4 를 사용한다면 이 어노테이션은 반드시 @RunWith(SpringRunner.class)와 사용되어야 합니다.

..Reasonable 한지 따질 필요 없이 반드시 같이 써주어야 했군요..

 

결론

 

1. @RunWith와 더불어 @RunWith(SpringRunner.class)가 도대체 뭘 하는지?

=> @RunWith JUnit 테스트의 lifecycle 및 테스트가 어떻게 실행시킬 것인지 정의

=> @RunWith(SpringRunner.class)는 @Mock 과 @Autowired등의 기능을 JUnit에서 사용할 수 있도록 해주는 브릿지

 

2. @RunWith와 @SpringBootTest랑 어떻게 다른가?

=> @RunWith(SpringRunner.class)는 가벼움(일부 Spring 기능만을 사용 및 주입)

=> @SpringBootTest는 ApplicationContext를 모두 적재하기에 시간이 오래걸림(당연히 유닛 테스트에 사용하면 안 됨)

 

3. 언제 뭘 써야 할까?

JUnit4 라면 @RunWith(SpringRunner.class)와 @DataJpaTest를 같이 써주어야함

 

4. @RunWith와 @DataJpaTest를 같이 쓰는것은 Reasonable 한가?

JUnit4 라면 @RunWith(SpringRunner.class)와 @DataJpaTest를 같이 써주어야함

그냥 강제로 같이 써줘야 했던 것 임..

 

이제부터 모든 어노테이션 조합을 시도해가면서 테스트가 성공하길 바라지 않을 수 있을 것 같습니다.

항상..

댓글