0. 이 글을 쓰는 이유
강의에서는 SpringBoot 2.x버전과 junit4를 붙여서 테스트를 진행하는데 내 환경은 SpringBoot 3.x버전이라 @RunWith가 먹히지 않았고 @SpringBootTest 어노테이션 하나로 퉁칠 수 있다고 한다. 도대체 왜 그게 가능한지 간단하게 정리를 해보기 위해 이 글을 작성한다.
1. Junit4와 Junit5 그리고 SpringBoot
우선 Spring Boot 2.2.x 버전을 기점으로 이후 버전은 Junit5를, 이전 버전은 Junit4를 기본 테스트 모듈로 제공한다.
Junit4는 모노리식 아키텍처로 되어있고 Junit5는 모듈화 된 아키텍처로 되어있다.
Junit5의 주요 모듈은 크게 3가지로 Junit Platform, Junit Jupiter, Junit Vintage가 있다.
Junit Platform : Junit 테스트를 위한 기본 엔진, Junit Jupiter와 Junit Vintage도 이 플랫폼 엔진 위에서 동작한다.
Junit Jupiter : 중첩 테스트, 동적 테스트 및 확장 등을 위한 Junit5의 주요 테스트 프레임워크이자 가장 일반적으로 사용되는 테스트 엔진
Junit Vintage : 이전 Junit 테스트와 호환을 이루기 위한 모듈, Junit5에서 Junit4에 대한 호환성을 유지하기 위해 사용한 엔진이 Vintage 엔진이다.
순수하게 호환성을 제외하고 사용하는 경우 Junit4에서는 @RunWith, Junit5에서는 @ExtendWith 어노테이션을 제공한다.
내부로 들어가는 클래스 또한 달라진다.
@RunWith에는 SpringRunner.class가, @ExtendWith에는 SpringExtension.class가 파라미터로 들어간다.
2. @Runwith, 그리고 SpringRunner.class
우선 Junit의 공식 문서를 읽어보자.
https://junit.org/junit4/javadoc/4.13/org/junit/runner/RunWith.html
When a class is annotated with @RunWith or extends a class annotated with @RunWith, JUnit will invoke the class it references to run the tests in that class instead of the runner built into JUnit. We added this feature late in development. While it seems powerful we expect the runner API to change as we learn how people really use it. Some of the classes that are currently internal will likely be refined and become public. For example, suites in JUnit 4 are built using RunWith, and a custom runner named Suite:
Junit4에 있는 API로 해당 어노테이션을 이용하면 Junit의 기본 테스트 러너 대신 외부 러너 참조 클래스를 사용할 수 있다고 한다. 그럼 이 안에 들어가던 SpringRunner.class를 살펴보자.
/**
* {@code SpringRunner} is an <em>alias</em> for the {@link SpringJUnit4ClassRunner}.
*
* <p>To use this class, annotate a JUnit 4 based test class with
* {@code @RunWith(SpringRunner.class)}.
*
* <p>If you would like to use the Spring TestContext Framework with a runner other than
* this one, use {@link org.springframework.test.context.junit4.rules.SpringClassRule}
* and {@link org.springframework.test.context.junit4.rules.SpringMethodRule}.
*
* <p><strong>NOTE:</strong> This class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @since 4.3
* @see SpringJUnit4ClassRunner
* @see org.springframework.test.context.junit4.rules.SpringClassRule
* @see org.springframework.test.context.junit4.rules.SpringMethodRule
*/
public final class SpringRunner extends SpringJUnit4ClassRunner {
/**
* Construct a new {@code SpringRunner} and initialize a
* {@link org.springframework.test.context.TestContextManager TestContextManager}
* to provide Spring testing functionality to standard JUnit 4 tests.
* @param clazz the test class to be run
* @see #createTestContextManager(Class)
*/
public SpringRunner(Class<?> clazz) throws InitializationError {
super(clazz);
}
}
SpringJunit4ClassRunner에 대한 alias 클래스라고 한다. 벌써 이름부터 Junit5와 호환성이 없으면 사용 못할 것 같은 느낌적인 느낌이 온다. 그러면 SpringJunit4ClassRunner를 살펴보자.
/**
* {@code SpringJUnit4ClassRunner} is a custom extension of JUnit's
* {@link BlockJUnit4ClassRunner} which provides functionality of the
* <em>Spring TestContext Framework</em> to standard JUnit tests by means of the
* {@link TestContextManager} and associated support classes and annotations.
*
* <p>To use this class, annotate a JUnit 4 based test class with
* {@code @RunWith(SpringJUnit4ClassRunner.class)} or {@code @RunWith(SpringRunner.class)}.
*
* <p>The following list constitutes all annotations currently supported directly
* or indirectly by {@code SpringJUnit4ClassRunner}. <em>(Note that additional
* annotations may be supported by various
* {@link org.springframework.test.context.TestExecutionListener TestExecutionListener}
* or {@link org.springframework.test.context.TestContextBootstrapper TestContextBootstrapper}
* implementations.)</em>
*
* <ul>
* <li>{@link Test#expected() @Test(expected=...)}</li>
* <li>{@link Test#timeout() @Test(timeout=...)}</li>
* <li>{@link org.springframework.test.annotation.Timed @Timed}</li>
* <li>{@link org.springframework.test.annotation.Repeat @Repeat}</li>
* <li>{@link Ignore @Ignore}</li>
* <li>{@link org.springframework.test.annotation.ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
* <li>{@link org.springframework.test.annotation.IfProfileValue @IfProfileValue}</li>
* </ul>
*
* <p>If you would like to use the Spring TestContext Framework with a runner
* other than this one, use {@link SpringClassRule} and {@link SpringMethodRule}.
*
* <p><strong>NOTE:</strong> This class requires JUnit 4.12 or higher.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see SpringRunner
* @see TestContextManager
* @see AbstractJUnit4SpringContextTests
* @see AbstractTransactionalJUnit4SpringContextTests
* @see org.springframework.test.context.junit4.rules.SpringClassRule
* @see org.springframework.test.context.junit4.rules.SpringMethodRule
*/
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
다행히 Junit을 개발하신 분들은 주석을 정말 잘 달아주신다. 그래서 시그니처만 가져왔다.
요약해보면 Spring framework에 대해 표준 Junit test를 할 수 있게 해주는 Class라고 한다.
3. 하지만 우리는 @SpringBootTest 어노테이션 하나로 모든 걸 퉁칠 수 있다.
코드만 봐도 된다.
/**
* Annotation that can be specified on a test class that runs Spring Boot based tests.
* Provides the following features over and above the regular <em>Spring TestContext
* Framework</em>:
* <ul>
* <li>Uses {@link SpringBootContextLoader} as the default {@link ContextLoader} when no
* specific {@link ContextConfiguration#loader() @ContextConfiguration(loader=...)} is
* defined.</li>
* <li>Automatically searches for a
* {@link SpringBootConfiguration @SpringBootConfiguration} when nested
* {@code @Configuration} is not used, and no explicit {@link #classes() classes} are
* specified.</li>
* <li>Allows custom {@link Environment} properties to be defined using the
* {@link #properties() properties attribute}.</li>
* <li>Allows application arguments to be defined using the {@link #args() args
* attribute}.</li>
* <li>Provides support for different {@link #webEnvironment() webEnvironment} modes,
* including the ability to start a fully running web server listening on a
* {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT
* random} port.</li>
* <li>Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate
* TestRestTemplate} and/or
* {@link org.springframework.test.web.reactive.server.WebTestClient WebTestClient} bean
* for use in web tests that are using a fully running web server.</li>
* </ul>
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.4.0
* @see ContextConfiguration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith(SpringExtension.class)
public @interface SpringBootTest {
@SpringBootTest 어노테이션에서 @ExtendWith 어노테이션을 가지고 있다. 이 덕분에 앞서 RunWith에서 ExtendWith로 변화한 Junit5를 사용하더라도 이 @SpringBootTest 어노테이션 하나로 사용이 가능한 것이다.
4. (여기까지만 쓰기 좀 그래서 하나 더 추가한) Exception 테스트에 대한 차이
Junit4에서는 @Test 어노테이션에 expected라는 인자를 넘길 수 있었다. 이 역할은 테스트 중 throw를 통해 문제가 발생하는 것을 기대하는 코드이다. 하지만 Junit5에서는 @Test 어노테이션은 정확히 테스트의 기능만을 가지고 있다.
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@Testable
public @interface Test {
}
그렇다면 Junit5에서 예외에 대한 테스트는 어떻게 진행할까?
조금 귀찮긴 하지만 문제가 생겨야 하는(throw가 발생해야하는)부분에서 직접 Assertions.assertThrows를 호출해서 사용해야 한다.
예시
@Test
public void 중복_회원_예외() throws Exception {
//given
Member member1 = new Member();
member1.setUserName("kim1");
Member member2 = new Member();
member2.setUserName("kim1");
//when
memberService.join(member1);
// memberService.join(member2);
//then
assertThrows(IllegalStateException.class, () -> {
memberService.join(member2);
});
}
'JAVA > spring' 카테고리의 다른 글
Spring + discord webhook 연동하기 (2) | 2023.07.13 |
---|---|
spring + slack webhook 연동하기 (0) | 2023.07.12 |
스프링 데이터 jpa 공식 문서 읽으면서 끄적끄적 (0) | 2023.04.23 |
spring method 요청에 대해 핸들링하기 (0) | 2023.03.13 |
spring boot 최소한으로 logging 설정하기(logback을 곁들인) (0) | 2023.03.12 |