Fast(빠르게) : 일반적으로 작성한 테스트 케이스는 빨라야 한다는 의미입니다.
Independent(독립적으로) : 각각의 테스트 케이스는 독립적이어야 한다는 의미입니다.
예를 들어, A라는 테스트 케이스를 먼저 실행시킨 후에 다음으로 B라는 테스트 케이스를 실행시켰더니 테스트에 실패하게 된다면 테스트 케이스끼리 독립적이지 않은 것입니다.
Repeatable(반복 가능하도록) : 테스트 케이스는 어떤 환경에서도 반복해서 실행이 가능해야 된다는 의미입니다. 로컬 환경이나 서버 환경에서 실행하든 반복해서 같은 결과를 확인할 수 있어야 합니다.
Self-validating(셀프 검증이 되도록) : 단위 테스트는 성공 또는 실패라는 자체 검증 결과를 보여주어야 한다는 의미입니다.
Timely(시기적절하게) : 단위 테스트는 테스트하려는 기능 구현을 하기 직전에 작성해야 한다는 의미입니다.
테스트 케이스의 가독성을 높이기 위해 given - when - then 표현 방법을 사용하는 것은 테스트 케이스를 작성하는데 유용한 방법입니다.
Given
: 테스트를 위한 준비 과정을 명시할 수 있습니다.
: 테스트에 필요한 전제 조건들이 포함된다고 보면 됩니다.
: 테스트 대상에 전달되는 입력 값(테스트 데이터) 역시 Given에 포함됩니다.
When
: 테스트할 동작(대상)을 지정합니다.
: 단위 테스트에서는 일반적으로 메서드 호출을 통해 테스트를 진행하므로 한두 줄 정도로 작성이 끝나는 부분입니다.
Then
: 테스트의 결과를 검증하는 영역입니다.
: 일반적으로 예상하는 값(expected)과 테스트 대상 메서드의 동작 수행 결과(actual) 값을 비교해서 기대한 대로 동작을 수행하는지 검증(Assertion)하는 코드들이 포함됩니다.
JUnit은 Java 언어로 만들어진 애플리케이션을 테스트하기 위한 오픈 소스 테스트 프레임워크로서 사실상 Java의 표준 테스트 프레임워크라고 봐도 무방합니다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HelloJUnitTest {
@DisplayName("Hello JUnit Test")
@Test
public void assertionTest() {
String expected = "Hello, JUnit";
String actual = "Hello, JUnit";
assertEquals(expected, actual);
}
}
import com.codestates.CryptoCurrency;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class AssertionNotNullTest {
@DisplayName("AssertionNull() Test")
@Test
public void assertNotNullTest() {
String currencyName = getCryptoCurrency("ETH");
assertNotNull(currencyName, "should be not null");
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit);
}
}
assertNotNull() 메서드의 첫 번째 파라미터는 테스트 대상 객체이고, 두 번째 파라미터는 테스트에 실패했을 때, 표시할 메시지입니다.
import com.codestates.CryptoCurrency;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class AssertionExceptionTest {
@DisplayName("throws NullPointerException when map.get()")
@Test
public void assertionThrowExceptionTest() {
assertThrows(NullPointerException.class, () -> getCryptoCurrency("XRP"));
}
private String getCryptoCurrency(String unit) {
return CryptoCurrency.map.get(unit).toUpperCase();
}
}
getCryptoCurrency() 메서드를 호출했을 때, NullPointerException이 발생하는지 테스트하고 있습니다.
assertThrows()의 첫 번째 파라미터에는 발생이 기대되는 예외 클래스를 입력하고, 두 번째 파라미터인 람다 표현식에서는 테스트 대상 메서드를 호출하면 됩니다.
assertDoesNotThrow(() -> getCryptoCurrency("XRP"));
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class BeforeEach1Test {
@BeforeEach
public void init() {
System.out.println("Pre-processing before each test case");
}
@DisplayName("@BeforeEach Test1")
@Test
public void beforeEachTest() {
}
@DisplayName("@BeforeEach Test2")
@Test
public void beforeEachTest2() {
}
}
@BeforeEach 애너테이션을 추가한 메서드는 테스트 케이스가 각각 실행될 때마다 테스트 케이스 실행 직전에 먼저 실행되어 초기화 작업 등을 진행할 수 있습니다.
@BeforeAll 애너테이션을 추가한 메서드는 정적 메서드(static method)여야 합니다.
@AfterEach, @AfterAll로 앞서 @BeforeEach, @BeforeAll과 동작 방식이 같습니다.
예를들어
public class AssumptionTest {
@DisplayName("Assumption Test")
@Test
public void assumptionTest() {
assumeTrue(System.getProperty("os.name").startsWith("Windows"));
System.out.println("execute?");
assertTrue(processOnlyWindowsTask());
}
private boolean processOnlyWindowsTask() {
return true;
}
}
위 코드에서 assumetrue() 값이 true이면 아래 로직을 실행하고 아니라면 실행되지 않습니다.
assumeTrue()는 특정 OS 환경 등의 특정 조건에서 선택적인 테스트가 필요하다면 유용하게 사용할 수 있는 JUnit 5의 API입니다.