ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JUnit] JUnit4에서 JUnit5로 마이그레이션
    DEV/JUnit 2024. 6. 30. 01:38
    왜 JUnit4에서 JUnit5로 전환해야하며, JUnit5가 등장한 이유가 무엇일까?
    JUnit in action에서 자세히 다루고 있어 가볍게 정리하고 넘어가고자한다.

    JUnit4 아키텍처, JUnit5 아키텍처 비교

    JUnit4는 단순한 모놀리식 아키텍처를 가지고 있다.

    JUnit4의 모든 기능은 jar파일(junit.jar) 한개 안에 들어 있다.

    이는 개발자가 JUnit4를 사용하고자한다면 클래스패스에 단일 jar만 추가하면 된다는 뜻이다.

     

    단일 jar파일로도 동작이 가능하지만, JUnit4의 기능을 조금 더 확장해서 사용하는 것이 일반적이다.

    JUnit4 runner, rule을 활용하여 테스트를 확장 할 수 있으나 runner의 리플렉션 사용은 캡슐화를 저해한다.

    이는 JUnit5가 만들어진 이유 중 하나가 되었다.

     

    외에도 JUnit4가 IDE나 빌드 도구와 지나치게 결합도가 높아지고, 제공하는 API가 유연하지 못한 단점이 있었다.

    이 때문에 더 작고 모듈화된 JUnit에 대한 요구가 생겨나고 이 요구에 따라 JUnit5가 등장하게 되었다.

     

    JUnit5의 모듈성

    : 다양한 빌드 도구와 IDE를 사용하는 클라이언트와 상호작용할 수 있도록 아래와 같은 요구사항이 있었다.

     

    - 개발자가 주로 사용하는 테스트를 작성하기 위한 API

    - 테스트를 발견하고 실행하는 데 사용하는 메커니즘

    - IDE나 빌드 도구와 쉽게 상호작용하고 테스트를 구동할 수 있는 API

     

    그 결과 JUnit5 아키텍처는 세 가지 모듈로 나누어졌다.

     

    JUnit5 내부 아키텍처 구성도, 출처 : https://velog.io/@shlee7131/JAVA-%EC%8B%AC%ED%99%94-JUnit

     

    - JUnit Platform : JVM위에서 테스트 프레임워크를 구동하기 위한 기반이 되는 플랫폼. 또한 콘솔, IDE, 빌드 도구에서 테스트를 구동할 수 있는 API도 제공한다.

     

    - JUnit Jupiter : JUnit5에서 테스트와 extension을 만들 수 있도록 프로그래밍 모델과 확장 모델을 결합한것. 일반적인 애노테이션, 클래스, 메서드를 비롯하여 JUnit5 테스트를 작성하기 위한 것은 여기에 있다.

     

    - JUnit Vintage : JUnit Platform에서 JUnit3이나 JUnit4 기반의 테스트를 실행하기 위한 테스트 엔진. 하위 호환성을 보장한다.

     

    JUnit4에서 JUnit5로의 전환 (마이그레이션) 과정

    JUnit5는 JUnit4의 모놀리식 아키텍처를 개선하여 모듈성 아키텍처를 적용한 프레임워크다.

    JUnit5에는 새로운 패키지, 애노테이션, 메서드, 클래스가 추가되었다.

     

    일부 JUnit5 기능은 JUnit4와 유사하지만 JUnit5에 추가된 기능들은 보통 기존에 없던 신규 기능이다.

    그러나 JUnit5에서는 기존 테스트와의 호환성을 위해 JUnit Vintage를 제공하고 있으므로 당장 바꿀 필요는 없다.

     

    JUnit은 JUnit Vintage를 활용하여 JUnit4에서 JUnit5로 전환하는 로드맵을 제시한다.

     

    1. 의존성을 교체한다.

    JUnit4에서는 하나의 의존성만 있으면 된다. 하지만 JUnit5에서는 목적에 따라 다양한 의존성이 필요할 수 있다.

     

    2. JUnit4 애노테이션을 JUnit5 애노테이션으로 교체한다.

    JUnit5는 JUnit4의 애노테이션 중의 일부를 가지고 있다. 그리고 JUnit5에서 새로 추가된 애노테이션은 신규 기능을 적용하고 더 나은 테스트를 작성하는 데 도움이 된다.

     

    3. 테스트 클래스와 메서드를 교체한다.

    JUnit5에서 사용하는 단언문이나 가정문은 다른 패키지의 다른 클래스로 옮겨졌다.

     

    4. JUnit4 rule과 runner를 JUnit5의 확장 모델로 교체한다.

    다른 단계보다 더 많은 노력이 필요하겠지만 JUnit4와 JUnit5는 장기간 공존할 수 잇으므로 rule과 runner는 코드에 계속 남아 있어도 상관없고 나중에 바꿔도 괜찮다.

     

     

    의존성

    gradle은 default로 JUnit5 platform를 제공하고 있다.

    test {
        useJUnitPlatform()
    }

     

     

    JUnit4를 대체하기 위한 JUnit5 vintage 의존성과 JUnit5 애노테이션으로 교체하기 위한 JUnit5 jupiter 의존성을 추가한다.

    dependencies {
        testImplementation("junit:junit:4.13.2") // 기존 사용 JUnit4
        testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.3")
        testImplementation("org.junit.jupiter:junit-jupiter:5.10.3")
    }

     

    애노테이션

    JUnit4 JUnit5
    @BeforClass, @AfterClass @BeforeAll, @AfterAll
    @Before, @After @BeforeEach, @AfterEach
    @Ignore @Disabled
    @Category @Tag

     

    단언문

    JUnit4 JUnit5
    Assert 클래스 Assertions 클래스
    단언문 메시지는 첫 번째 파라미터에 적는다. 단언문 메시지는 마지막 파라미터에 적는다.
    assertThat 사용 가능 assertThat 메서드 지원 X,
    assertAll과 assertThrows 메서드 추가

     

    가정문

    JUnit4 JUnit5
    Assume 클래스 Assumptions 클래스
    assumeNotNull, assumeNoException 메서드 사용 가능 assumeNotNull, assumeNoException 메서드 지원 X

     

    JUnit4 rule과 JUnit5 확장 모델

    rule은 메서드가 실행될 때 호출을 가로채고 메서드 실행 전후에 추가 작업을 수행할 수 있는 JUnit4 컴포넌트다.

     

    예외를 던지는 예제를 통해 JUnit4의 ExpectedException과 JUnit5의 assertThrows를 비교해보자.

     

    Calculator 클래스

    public class Calculator {
        public double add(double number1, double number2) {
            return number1 + number2;
        }
    
        public double sqrt(double x) {
            if(x < 0) {
                throw new IllegalArgumentException("음수의 제곱근을 구할 수 없다.");
            }
            return Math.sqrt(x);
        }
    
        public double divide(double x, double y) {
            if(y == 0) {
                throw new ArithmeticException("0으로 나눌 수 없다.");
            }
            return x / y;
        }
    }

     

    JUnit4RuleExceptionTester 클래스

    import com.study.junit.ch01.Calculator;
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.rules.ExpectedException;
    
    public class JUnit4RuleExceptionTester {
        @Rule
        public ExpectedException expectedException =  ExpectedException.none();
    
        private Calculator calculator = new Calculator();
    
        @Test
        public void expectIllegalArgumentException() {
            expectedException.expect(IllegalArgumentException.class);
            expectedException.expectMessage("음수의 제곱근을 구할 수 없다.");
            calculator.sqrt(-1);
        }
    
        @Test
        public void expectArithmeticException() {
            expectedException.expect(ArithmeticException.class);
            expectedException.expectMessage("0으로 나눌 수 없다.");
            calculator.divide(1,0);
        }
    }

     

    JUnit5ExceptionTester 클래스

    import com.study.junit.ch01.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 JUnit5ExceptionTester {
        private Calculator calculator = new Calculator();
    
        @Test
        public void expectIllegalArgumentException() {
            Throwable throwable = assertThrows(IllegalArgumentException.class,
                    () -> calculator.sqrt(-1));
            assertEquals("음수의 제곱근을 구할 수 없다.", throwable.getMessage());
        }
    
        @Test
        public void expectArithmeticException() {
            Throwable throwable = assertThrows(ArithmeticException.class,
                    () -> calculator.divide(1,0));
            assertEquals("0으로 나눌 수 없다.", throwable.getMessage());
        }
    }

     

    JUnit5로 작성한 테스트 코드가 훨씬 간결하고 rule을 따로 초기화하고 관리할 필요도 없어진다.

    댓글

Designed by Tistory.