Dev/TDD

Junit In Action - TDD를 위한 테스트 원칙, 도구 및 활용 Review - Junit4와 Junit5 비교

린네의 2024. 6. 26. 22:19

 

Junit4에서 Junit5로 전환하기

Junit5는 새로운 아키텍처를 적용한 새로운 패러다임으로  Junit Vintage 테스트 엔진을 활용하여 Junit4에서 Junit5로 전환할 수 있다.

Junit Jupiter와 관련한 모든 클래스와 애노테이션은 org.junit.jupiter패키지에서 확인할 수 있고, org.junit 패키지에 Junit4와 관련한 모든 클래스와 애노테이션이 있으므로 클래스 패스에 Junit 5 Jupiter와 Junit4가 모두 존재하더라도 충돌이 발생하지 않는다. ( 예를 들면, Junit4의 @Test 애노테이션은 org.junit.Test에 속해 있고 Junit5의 @Test 애노테이션은 org.junit.jupiter.api.Test에 속해 있어 충돌이 발생하지 않고 각자 불러올 수 있다. ) 

 

Junit4에서 Junit5로의 전환은 다음과 같은 단계를 거친다.  전환 시 Junit4는 java5 이상을, Junit5는 java8 이상을 요구하니 설치된 자바 버전을 확인하는 것이 좋다.

 

  • 의존성 교체

Junit4를 Junit5으로 전환시 junit-vintage-engine 의존성을 추가해야 한다. 의존성 추가 방법은 다음과 같다. ( 보다 자세한 설정은 해당 링크 참조 ) 해당 의존성을 추가하면 Junit4에 대한 의존성 파일을 추가하지 않아도 정상적으로 Junit4가 포함된 테스트 코드를 실행시킬 수 있다.

 

dependencies {
	testImplementation("junit:junit:4.13.2")
	testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.10.2")
}

 

 

  • Junit4 애노테이션을 Junit5로 애노테이션으로 교체
  • 테스트 클래스와 메서드를 교체

 

Jinit5를 사용하여 테스트를 작성하기 위해 junit-jupiter 의존성을 추가 해줘야 한다. jupiter에는 애노테이션, 클래스, 메서드가 포함된 junit-jupiter-api와 테스트 엔진을 실행하기 위한 junit-jupiter-engine가 포함되어 있다. 이 외에도 파라미터를 사용한 테스트 작성이 필요하다면 junit-jupiter-params가 추가적으로 필요할 수 있다.

 

dependencies {
	testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
}

 

Junit4와 Junit5에서 유사하게 사용되는 애노테이션,클래스,메서드는 다음과 같다.

 

  • 애노테이션
Junit4 Junit5
@BeforeClass, @AfterClass @BeforeAll, @AfterAll
@Before, @After @BeforeEach, @AfterEach
@Ignore @Disabled
@Category @Tag

 

@BeforeClass, @AfterClass는 static이어야 하고 public이어야 했던 점이 Junit5에서 static이 유지되는 것은 똑같지만 접근제어 수준이 default로 완화되었다.  또한 @TestInstance(Lifecyle.PER_CLASS)을 사용하여 static이 아니라 비정적(none-static)으로 선언할 수 있게 되었다.

 

@Category가 아닌 @Tag를 사용하면 코드 수준에서 테스트 수준을 묶을 수 있다. 간단한 예시를 보자. 아래 코드를 작성하여 각각 IndividualTests와 RepositoryTest를 정의할 수 있다.

 

// @Category로 묶을 interface 선언 
public interface IndividualTests {
}

public interface RepositoryTests {
}

 

 

public class JUnit4CustomerTest {
	private String CUSTOMER_NAME = "John Smith";
    
    @Category(IndividualTests.class)  // 테스트를 각각 특정 카테고리에 매핑시킴
    @Test 
    public void testCustomer() {
    	// test code
  
  	}
    
    @Category({IndividualTests.class, RepositoryTests.class}) // 테스트를 각각 특정 카테고리에 매핑시킴
    @Test 
    public void testCustomer() {
    	// test code
    }
}



@Category({IndividualTests.class, RepositoryTests.class}) // 클래스 수준에서 애노테이션을 추가하여 클래스 안에 있는 테스트 메서드 모두를 특정 카테고리에 포함되게 만들 수 있음
public class JUnit4CustomerTest {
	private String CUSTOMER_NAME = "John Smith";
    
   
    @Test 
    public void testCustomer() {
    	// test code
  
  	}
    
    
    @Test 
    public void testCustomer() {
    	// test code
    }
}

 

 

/**
  *Junit4CustomerTest.class, Junit5CustomerRepositoryTest.class 클래스에서 
  *IndividualTest 애노테이션이 포함된 테스트만 실행 시키도록함
 **/

@RunWith(Categories.calss)
@Categories.IncludeCategory(IndividualTests.class) // @Categories.ExcludeCategory(x.class)를 사용하면 제외할 목록을 지정할 수 있음
@Suite.SuiteClass({Junit4CustomerTest.class, Junit4CustomerRepositoryTest.class}) // 테스트 하고자 하는 클래스들 나열
public class Junit4IndividualTestsSuite {


}

 

 

Junit4의 @Category는 테스트 묶음을 위한 인터페이스를 만들어야 하고, @Category의 파라미터에 들어갈 마커 인터페이스를 전부 따로 작성해야 하는 번거로움을 가진다. Junit5에서는 이러한 번거로움이 사라졌다. 단순히 @Tag("구분할 명칭")을 클래스나 테스트 단위로 지정하고, IDE를 사용하여 실행할 태그를 선택하면 끝이다.

 

 

  • 단언문
Junit4 Junit5
Assert 클래스를 사용한다 Assertions 클래스를 사용한다
단언문 메시지는 첫 번째 파라미터에 적는다 단언문 메시지는 마지막 파라미터에 적는다
assertThat 메서드를 사용할 수 있다. assertThat 메서드를 지원하지 않는다. assertAll과 assertThrows
메서드가 추가 되었다.

 

    단언문에서 assertThat의 지원이 종료되고, Hamcrest패키지로 옮겨가게 되었다. import 하는 패키지만 달라졌을 뿐이지 문법은 동일하다. 예를들면 이런식이다.

 

// Junit4 - org.junit.Assert.assertThat
assertThat(values, hasItem(anyOf(equalTo("Oliver"), eqaulTo("Jack"), eqaulTo("Harry"))));


//Junit5 - org.hamcrest.Matcher.Assert.assertThat
assertThat(values, hasItem(anyOf(equalTo("Oliver"), equalTo("Jack"), equalTo("Harry"))));



  • 가정문
Junit4 Junit5
Assume 클래스를 사용한다 Assumptions 클래스를 사용한다
assumeNotNull, assumeNoException 메서드를 사용할 수 있다 assumeNotNull, assumeNoException 메서드를 사용할 수 없다

 

  • Junit4 rule과 runner를 Junit5의 확장모델로 교체

 

cf. Junit4 rule

더보기

* Junit4 rule 이란?

메서드가 실행될 때 호출을 가로채고 메서드 실행 전후에 추가 작업을 수행할 수 있는 Junit4 컴포넌트다. ExpectedException, TemporaryFolder 혹은 CustomRule을 직접 작성하여 사용할 수 있다.

 

Junit4 rule에서 ExpectedException은 Junit5에서 assertThrows 메서드로 쉽게 대체할 수 있다. 간단한 예제를 보자.

 

// Junit4 
public class JUnit5RuleExceptionTester {
    @Rule
    public ExpectedException expectedException = ExpectedException.none();
    
    @Test
    public void expectIllegalArgumentException() {
    	expectedException.expect(IllegalArgumentException.class);
        expectedException.expectMessage("blarblar...");
        
        //에러 예상 코드 작성
    }
}


// Junit5
public class JUnit5RuleExceptionTester {
    
    @Test
    public void expectIllegalArgumentException() {
		Throwable throwable = assertThorws(IllegalArgumentException.class,
        		() -> caculator.sqrt(-1));  // 에러 예상 코드 작성 
		assertEquals("blarblar...", throwable.getMessage());        

    }
 }

 

 

이 외에도, Junit4에서 @Rule을 사용해서 TemporaryFolder 타입을 삽입하는 대신 Junit5의 @TempDir을 사용하여 간편하게 사용하는 것도 가능하다. 테스트 실행 전후로 비슷한 작업이 필요한 경우네는 사용자 정의 rule을 사용할 수 있다.

 

다음은 TestRule을 implements 하여 CustomRule을 작성하는 예제이다.

 

//Junit4 에서 TestRule을 참조하는 CustomRule 작성 예제 
public class CustomRule implements TestRule {
    private Statement base;
    private Descrption description;
 
    @Override
    public Statement apply(Statement base, Description description) {
    	this.base = base;
        this.description = description;
        return new CustomStatement(base, descrption);

    }
   
}

 

혹은 Satement를 impements 하는 CustomRule 클래스를 생성할 수도 있다.  생성된 CustomRule을 실행시키기 위해서 테스트 클래스 객체를 별도로 만들어 실행시킬 수 있다.

 

public class Junit4CustomRuleTest {

   @Rule
    public CustomRule myRule = new CustomRule();
    
    @Test
    public void myCustomRuleTest() {
    	System.out.printlnt("call of a test method");
    }	
}

 

 

Junit5에서는 사용자 정의 extension으로 테스트 클래스와 테스트 메서드의 동작을 확장하고 개발자들이 Junit4 rule과 유사한 효과를 누릴 수 있도록 했다. Junit5의 사용자 정의 extension을 사용하게 되면 코드가 짧아지고, 애노테이션을 사용할 수 있어 소스 코드가 선언적으로 바뀐다.

 

//Junit5에서 Junit4의 @Rule을 대체하여 @ExtendWith을 사용하는 예제
public class CustomExtension implements AfterEachCallback, BeforeEachCallback {
	
    // AfterEachCallback, BeforeEachCallback 인터페이스를 구현하여  @BeforeEach와 @AfterEach 처럼 동작한다
    
    @Override
    public void beforeEach(ExtensionContext extensionContext) throws Exception {
    	System.out.println(this.getClass().getSimpleName() + extensionContext.getDisplayName() );
    }

    @Override
    public void afterEach(ExtensionContext extensionContext) throws Exception {
    	System.out.printlnt(this.getClass().getSimpleName() + extensionContext.getDisplayName());
    
    }
}



@ExtendWith(CustomExtension.class)
public class Junit5CustomExtensionTester {
	
    @Test
    public void myCustomRuleTest() {
    	System.out.println("Call of a test method");
    }

}

 

 

Junit5 Extension을 사용하여 Junit4의 runner을 점차적으로 대체할 수도 있다.  가령,  Mockito 테스트 전환을 위해서는 @RunWith(MockitoJunitRunner.class) 애노테이션을 @ExtendWith(MockitoExtension.class)로 바꾸면 된다. 스프링  테스트는  @RunWith(SpringJUnit4 ClassRunner.class) 애노테이션을 @ExtendWith(SpringExtension.class)로 대체할 수 있다.