Post

[Book - JUnit IN ACTION 3판] 4. JUnit 4에서 JUnit 5로 전환하기

JUnit 4에서 JUnit 5로의 전환 과정

JUnit 5는 새로운 아키텍처를 적용한 새로운 패러다임이다.
JUnit 5에는 새로운 패키지, 애노테이션, 메서드, 클래스가 추가되었다.

JUnit은 JUnit Vintage 테스트 엔진을 활용하여 테스트를 JUnit 4에서 JUnit 5로 전환하는 로드맵을 제시한다.

주요 단계설명
의존성을 교체한다.JUnit 4에서는 하나의 의존성만 있으면 된다.
하지만 JUnit 5에는 목적에 따라 의존성이 필요할 수 있다.
JUnit 4 애노테이션을 JUnit 5 애노테이션으로 교체한다.JUnit 5는 JUnit 4의 애노테이션 중의 일부를 가지고 있다.
그리고 JUnit 5에서 새로 추가된 애노테이션은 신규 기능을 적용하고 더 나은 테스트를 작성하는 데 도움이 된다.
테스트 클래스와 메서드를 교체한다.JUnit 5에서 사용하는 단언문이나 가정문은 다른 패키지의 다른 클래스로 옮겨졌다.
JUnit 4 rule과 runner를 JUnit 5의 확장 모델로 교체한다.JUnit 4 rule과 runner를 JUnit 5 확장 모델로 바꾸는 작업에는 일반적으로 다른 단계보다 더 많은 노력이 필요하다.
하지만 JUnit 4와 JUnit 5는 장기간 공존할 수 있으므로 코드에 남아 있어도 상관없고 나중에 바꿔도 괜찮다.

JUnit 4는 자바 5 이상을 요구한다.
JUnit 5는 자바 8 이상을 요구한다.


JUnit 4에서 JUnit 5로 전환하는 데 필요한 의존성

제품을 위한 테스트가 더 많이 필요하고 테스트 작성에 더 많은 유연성과 명확성이 필요하기 때문에 JUnit 5를 사용한다.
JUnit 5를 사용한다는 것은 곧 디스플레이명을 사용한 중첩 테스트나 동적 테스트를 수행할 수 있다는 말과 같다.

JUnit 5의 주요 의존성 중 하나인 JUnit Vintage는 JUnit 4의 의존성을 대체할 수 있다.

JUnit 5를 사용하여 테스트를 작성하기 위해서는 junit-jupiter-api와 junit-jupiter-engine 의존성을 추가해야 한다.

junit-jupiter-api : 애노테이션, 클래스, 메서드를 포함하여 JUnit Jupiter로 테스트를 작성하기 위한 API를 제공하는 아티팩트
junit-jupiter-engine : 테스트 엔진을 실행하기 위한 JUnit Jupiter의 핵심적인 아티팩트


JUnit 5 애노테이션, 클래스, 메서드

JUnit 4와 JUnit 5에서 비슷하게 사용하는 애노테이션, 클래스, 메서드

| JUnit 4 | JUnit 5 | |—————————|————————-| | @BeforeClass, @AfterClass | @BeforeAll, @AfterAll | | @Before, @After | @BeforeEach, @AfterEach | | @Ignore | @Disabled | | @Category | @Tag |

JUnit 4JUnit 5
Assert 클래스Assertions 클래스
단언문 메시지는 첫 번째 파라미터에 적는다.단언문 메시지는 마지막 파라미터에 적는다.
assertThat 메서드assertThat 메서드 지원 X, assertAll과 assertThrows 메서드가 추가되었다.
JUnit 4JUnit 5
Assume 클래스Assumptions 클래스
assumeNotNull, assumeNoException 메서드assumeNotNull, assumeNoException 메서드 지원 X

JUnit 5로 넘어오면서 테스트 메서드에 대한 기본 접근 제어 수준이 public에서 디폴트(package-private)로 완화되었다.
사실 테스트 메서드는 테스트 클래스가 속한 패키지 내에서만 접근하면 되므로 굳이 public으로 선언할 필요가 없다.

JUnit 4의 rule과 JUnit 5의 확장 모델

JUnit 4의 rule을 사용하면 ExpectedException.none()이 deprecated 되었기도 하고,
JUnit 5의 assertThrows 메서드로 쉽게 대체할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.example.junitinaction3.chapter04.Calculator;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class JUnit5ExceptionTest {
    private final Calculator calculator = new Calculator();

    @Test
    public void expectIllegalArgumentException() {
        Throwable throwable = assertThrows(IllegalStateException.class,
                () -> calculator.sqrt(-1));
        assertEquals("음수의 제곱근을 구할 수 없다", throwable.getMessage());
    }

    @Test
    public void expectArithmeticException() {
        Throwable throwable = assertThrows(ArithmeticException.class,
                () -> calculator.divide(1, 0));
        assertEquals("0으로 나눌 수 없다", throwable.getMessage());
    }
}
  • assertThrow 메서드를 사용해 calculator.sqrt(-1) 문장에서 IllegalArgumentException을 던진다고 단언하고, 오류 메시지를 검증한다.
  • calculator.divide(1, 0) 문장에서 ArithmeticException을 던진다고 단언하고, 오류 메시지를 검증한다.

    이전 챕터에서 Junit 4로 테스트했을 때도 divide 테스트가 그냥 통과가 되었는데 JUnit 5도 이유를 알 수 없다..

사용자 정의 rule을 extension으로 전환하기

JUnit 4 사용자 정의 rule 대신 JUnit 5의 사용자 정의 extension을 사용해서 짧은 코드와 애노테이션으로 선언적이게 된 코드를 생성해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class CustomExtension implements AfterEachCallback, BeforeEachCallback {

    @Override
    public void beforeEach(ExtensionContext extensionContext) throws Exception {
        System.out.println(this.getClass().getSimpleName() + " "
                + extensionContext.getDisplayName() + " has started");
    }

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
        System.out.println(this.getClass().getSimpleName() + " "
                + extensionContext.getDisplayName() + " has finished");
    }
}
1
2
3
4
5
6
7
8
9
10
11
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(CustomExtension.class)
public class JUnit5CustomExtensionTest {

    @Test
    void myCustomRuleTest() {
        System.out.println("Call of a test method");
    }
}
  • JUnit5CustomExtensionTest 클래스를 확장하기 위해 CustomExtension 클래스를 사용했다.
  • 테스트 클래스는 CustomExtension으로 확장되므로 CustomExtension에서 정의한 beforeEach와 afterEach 메서드는 각각의 테스트 메서드 전후에 실행된다.

JUnit 5 extension을 적용하면 각 테스트 메서드 전후에 실행될 코드가 명확한 이름으 가진 전용 메서드로 분리되는 것을 볼 수 있다.

추가로, JUnit 5 extension을 사용하여 JUnit 4의 runner를 점차적으로 대체할 수도 있다.

  • Mockito 테스트를 전환하려면 단순히 테스트 클래스의 @RunWith(MockitoJUnitRunner.class) 애노테이션을 @ExtendWith(MockitoExtension.class)로 대체한다.
  • 스프링 테스트를 전환하려면 테스트 클래스의 @RunWith(SpringJUnit4ClassRunner.class) 애노테이션을 @ExtendWith(SpringExtension.class)로 대체한다.

정리

JUnit 4 대신 JUnit 5를 사용하면 코드가 줄고, 테스트 메서드 자체가 더 명확해지는 것 같다.
또한, JUnit 4의 단일 의존성 대신 JUnit 5의 다중 의존성을 사용하면 필요한 의존성만 사용할 수도 있다.

JUnit 4의 rule과 runnner가 좋다는 말도 있었는데, 대신에 JUnit 5 extension을 사용하면 될 것 같다.

역시 JUnit 4 보다 JUnit 5를 사용하는 것이 좋을 것 같다.

This post is licensed under CC BY 4.0 by the author.

© Yn3. Some rights reserved.