TDD(Test Driven Development) - JUnit 5 (2)
아래 내용은 ‘테스트 주도 개발: TDD 실천법과 도구’(https://repo.yona.io/doortts/blog/issue/1)를 보고 주요 내용을 개인적으로 정리하여 작성했습니다.
JUnit ?
Java 에서 사용되는 Testing 프레임워크이다. 단위 테스트를 할 때 사용되는 도구이며 테스트 결과와 예상값을 확인시켜주는 Assert 와 테스트 실행을 도와주는 Test Runner 등으로 구성되어 있다.
xUnit 이라고 해서 자바 외에 다른 언어에서 사용되는 테스트 도구가 있다. 예를들어 C 의 경우 CUnit C++ 의 경우 CppUnit 등이 있다.
JUnit, Hamcrest 설정
JUnit 4 버전에서는 단일 jar 만 사용되었었는데 JUnit 5 로 넘어오면서 여러개의 jar 로 분리되었다고 한다.
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
- JUnit Platform : JVM 위에서 실행되는 테스트 프레임워크 이다. JUnit 과 Client Build Tool 사이에서 강력한 인터페이스를 제공한다. 또한 JUnit 플랫폼 위에서 실행되는 테스트 프레임워크를 개발하기 위한 TestEngine API 를 제공한다. 이를 사용해서 써드파티 테스트 라이브러리를 JUnit 에 직접 플러그인 할 수 있다.
- JUnit Jupiter : @BeforeEach, @AfterEach, 등의 5버전에서 새로이 추가된 프로그래밍 모델과 확장 모델들을 포함하고 있다. JUnit 4 에서 추가되거나 변경된 Annotation 은 다음과 같다.
- @TestFactory : 동적 테스트를 위한 테스트 팩토리 메소드를 가리킨다.
- @DisplayName : 테스트 클래스 또는 메소드의 사용자 정의 이름을 표시한다.
- @Nested : 해당 Annotation 이 표시된 클래스가 중첩된 비 정적인 테스트 클래스임을 나타낸다.
- @Tag : 필터링 테스트를 위한 태그를 선언
- @ExtendWith : 사용자 정의 확장명을 등록하는 데 사용
- @BeforeEach : 테스트 메소드 이전에 실행되는 메소드(@Before)
- @AfterEach : 테스트 메소드 후에 실행되는 메소드(@After)
- @BeforeAll : 현재 클래스의 모든 메소드 이전에 실행되는 메소드(@BeforeClass)
- @AfterAll : 현재 클래스의 모든 메소드 이후에 실행되는 메소드(@AfterClass)
- @Disable : 테스트 클래스 또는 메소드를 테스트에서 제외 또는 비활성화 하는데 사용(@Ignore)
-
JUnit Vintage : TestEngine 에서 JUnit 3 & JUnit 4 버전의 테스트 코드를 실행해준다.
- JUnit 5 Features :
- JUnit Jupiter 에서 테스트 코드를 작성
- JUnit 4 버전과 Jupiter 통합
- 테스트 실행
- JUnit Jupiter 를 위한 확장 모델 제공
- JUnit Platform 실행 API, JUnit Platform Test Kit 제공
JUnit 5 의 대략적인 변경점과 특징을 알아보았다. 이제 Maven 또는 Gradle 에서 Dependency 를 추가해보자.
Maven
<dependencies>
<!--
https://mvnrepository.com/artifact/org.junit.
jupiter/junit-jupiter
-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Gradle
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.6.2'
JUnit 5 예제
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.logging.Logger;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
private static final Logger log = Logger.getLogger(DemoApplicationTests.class.getName());
// BeforeAll 은 현재 테스트 클래스 또는 전체 테스트 메소드 실행이전에 실행되야 하므로 static 으로 작성한다.
@BeforeAll
static void setup() {
log.info("@BeforeAll - executes once before all test methods in this class");
}
@BeforeEach
void init() {
log.info("@BeforeEach - executes before each test method in this class");
}
// assertEquals 사용
@Test
@DisplayName("1 + 1 = 2")
void addTwoNumbers () {
Calculator calculator = new Calculator();
assertEquals(2, calculator.add(1, 1), "1 + 1 should equal 2");
}
// 테스트 항목 이름 설정
@DisplayName("Single test successful")
@Test
void testSingleSuccessTest() {
log.info("Success");
}
// JUnit4 버전의 @Ignore 와 동일한 @Disabled 사용
@Test
@Disabled("Not implemented yet")
void testShowSomething() {
}
// Java 8 이상의 버전에서는 Lambda 식을 사용할 수 있다.
@Test
void lambdaExpressions() {
List<Integer> numbers = Arrays.asList(1, 2, 3);
assertTrue(numbers
.stream()
.mapToInt(i -> i)
.sum() > 5, () -> "Sum should be greater than 5");
}
// 위 Lambda 식을 사용하면 lazy 로딩을 이용하여 좋은 퍼포먼스를 보일 수 있다.
// 하지만 아래처럼 사용할 경우 보다 더 정확한 위치의 오류를 확인할 수 있다.
@Test
void groupAssertions() {
int[] numbers = {0, 1, 2, 3, 4};
assertAll("numbers",
() -> assertEquals(numbers[0], 1),
() -> assertEquals(numbers[3], 3),
() -> assertEquals(numbers[4], 1)
);
}
@Test
void trueAssumption() {
// True 경우를 가정하는 테스트 문
assumeTrue(5 > 1);
assertEquals(5 + 2, 7);
}
@Test
void falseAssumption() {
// False 일 경우를 가정하는 테스트 문
assumeFalse(5 < 1);
assertEquals(5 + 2, 7);
}
@Test
void assumptionThat() {
String someString = "Just a string";
// assumingThat 의 경우 첫번째 인자인 가정문이 맞지 않을 경우 실행되지 않는다.
// 만약 첫번째 인자의 가정문이 맞다고 하면 뒤에 테스트 문이 실행된다.
assumingThat(someString.equals("Just a string"), () -> assertEquals(2 + 1, 4));
}
// 예외에 대한 테스트
@Test
void shouldThrowException() {
// 예외의 세부사항에 대한 테스트
Throwable exception = assertThrows(UnsupportedOperationException.class, () -> {
throw new UnsupportedOperationException("Not supported");
});
assertEquals(exception.getMessage(), "Not supported");
}
@Test
void assertThrowsException() {
String str = null;
// 예외의 유형에 대한 테스트
assertThrows(IllegalArgumentException.class, () -> {
Integer.valueOf(str);
});
}
// 아래 두 예제는 Test suite 예제인데, @SelectPackage 같은 경우에는 테스트 패키지를 테스트하는 것이고
// @SelectClasses 의 경우에는 선택된 클래스만 테스트하는 것이다.
// @RunWith(JUnitPlatform.class)
// @SelectPackages("com.baeldung")
// public class AllUnitTest {}
// @RunWith(JUnitPlatform.class)
// @SelectClasses({AssertionTest.class, AssumptionTest.class, ExceptionTest.class})
// public class AllUnitTest {}
// 아래 예제는 동적인 테스트에 관한 예제이다.
// 일반적으로 위에서 보았던 테스트의 경우 정해진 정적인 값을 비교하는 테스트들이었다.
// 하지만 아래는 고정된 값이 아닌 배열을 조회하며 각각의 값을 테스트한다.
private List<String> in = new ArrayList<>(Arrays.asList("A", "B", "C"));
private List<String> out = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc"));
@TestFactory
public Stream<DynamicTest> translateDynamicTestsFromStream() {
return in.stream().map(word -> DynamicTest.dynamicTest("Test translate " + word, () -> {
int id = in.indexOf(word);
assertEquals(out.get(id), translate(word));
}));
}
private String translate(String word) {
if ("A".equalsIgnoreCase(word)) {
return "aaa";
} else if ("B".equalsIgnoreCase(word)) {
return "bbb";
} else if ("C".equalsIgnoreCase(word)) {
return "ccc";
}
return "Error";
}
@AfterEach
void tearDown() {
log.info("@AfterEach - executed after each test method.");
}
@AfterAll
static void done() {
log.info("@AfterAll - executed after all test methods.");
}
}
위에 코드를 보면 @Test 로 표시된 메소드를 테스트한다. 테스트 항목의 이름은 “1 + 1 = 2” 로 선언하였고 Equals 를 이용해 add(1, 1) 의 결과가 2 가 맞는지 확인한다.
해당 예제는 JUnit5 공식 사이트에 있다.
간단하게 JUnit 5의 특징과 예제를 알아보았다. 다음에는 Jupiter 의 여러 Annotation 과 테스트를 좀더 유연하게 사용하도록 도와줄 Hamcrest 를 알아보자.
댓글남기기