Junit 5 설정부터 기초 내용까지 간단히 정리하자.
JUnit Platform : 테스팅 프레임워크를 구동하기 위한 런처와 테스트 엔진을 위한 API를 제공한다.
JUnit Jupiter : JUnit 5를 위한 테스트 API와 실행 엔진을 제공한다.
JUnit Vintage : JUnit3과 JUnit4로 작성된 테스트를 JUnit 5 플랫폼에서 실행하기 위한 모듈을 제공한다.
Gradle 인 경우 test 태스크에 JUnit 5 플랫폼을 사용하도록 설정해야 하고 testImplementation을 사용해 junit-jupiter 의존성을 추가하면 된다.
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
}
test {
useJUnitPlatform()
}
junit-jupiter 모듈은 JUnit 5.4 버전부터 제공한다.
junit-jupiter 모듈은 junit-jupiter-api 모듈, junit-jupiter-params 모듈, junit-jupiter-engine 모듈을 포함한다.
그렇기 때문에 이전 버전이라면 필요한 모듈을 각각 설정해주어야 한다.
- Gradle 4.6 버전 부터 Junit5 플랫폼을 지원한다.
@Test 어노테이션이 붙는 메서드는 테스트를 실행할 메서드이며 private이면 안되고 반환 값이 존재해도 안된다.
JUnit을 사용하게 되면 단언 메서드를 사용하기 위해 Assertions 클래스를 사용하게 된다.
다만 유사한 org.assertj.core.api가 있으니 잘 구별해서 사용해야 한다.
- assertEquals(expected, actual) : 기대값과 실제값이 같은지 확인합니다.
- assertTrue(condition) : 조건이 참인지 확인합니다.
- assertFalse(condition) : 조건이 거짓인지 확인합니다.
- assertNull(object) : 객체가 null 인지 확인합니다.
- assertNotNull(object) : 객체가 null이 아닌지 확인합니다.
- assertArrayEquals(expectedArray, actualArray) : 배열이 기댓값과 일치하는지 확인합니다.
- fail() : 테스트를 실패 처리한다.
- assertIterableEquals: 두 Iterable이 동일한지 확인합니다.
- assertLinesMatch: 두 줄이 동일한지 확인합니다.
- assertTimeout: 지정한 시간 내에 작업이 완료되는지 확인합니다.
주로 타입별로 assertEquals()메서드가 존재한다. 주의할 점은 첫 번째 인자가 기대하는 값이고 두 번째 인자가 검사하려는 값이라는 것이다. 나도 처음에 헷갈려서 잘 못 사용하긴 했다..
테스트를 실패하도록 만들어야 하는 경우가 있다. 그 경우 fail()를 사용할 수 있다.
public class MyTest {
@Test
public void testSomething() {
// some code here
if (!condition) {
fail("The condition was not met!");
}
// continue with the test
}
}
혹은 try/catch문을 예상했을 때 도 사용할 수 있다.
다만 익셉션 발생 유무가 검증 대상이면 fail()대신 다른 메서드를 추천한다.
- assertThrows 메서드는 예외가 발생하는지를 검사하고, 예외가 발생하면 그 예외를 반환합니다.
- assertDoesNotThrow : 예외가 발생하지 않는지 검사합니다.
- expectThrows 메서드는 예외가 발생하는지를 검사하고, 예외가 발생하면 그 예외를 반환합니다. 단, 다른 메서드와 달리 예외가 어떤 타입인지 확인하는 기능이 추가되어 있습니다.
@Test
void testException() {
Exception exception = assertThrows(Exception.class, () -> {
// 예외가 발생할 것으로 예상되는 코드
});
assertEquals("예외 메시지", exception.getMessage());
}
assert 메서드
assert 메서드는 실패 시 다음 코드를 실행하지 않고 바로 예외를 던지는데, 만약 여러 개의 단언 메서드가 있는 경우
일단 모든 검증을 실행하고 그 중 실패한 것을 확인하고 싶다면 assertAll()을 사용할 수 있다.
assertAll()은 테스트 코드 작성 시 사용되는 함수로, 전달된 모든 검증 조건이 true인지를 확인합니다.
만약 전달된 검증 조건 중 하나라도 false이면, AssertionError가 발생합니다.
이 함수는 검증 조건이 여러 개일 경우 각 검증 조건이 정상적으로 수행되었는지를 한 번에 확인할 수 있기 때문에 매우 유용합니다.
다음은 assertAll()을 사용한 예시입니다.
@Test
void testAssertAll() {
List<String> fruits = Arrays.asList("apple", "banana", "orange");
// assert that all elements in the list are not null
assertAll(() -> assertNotNull(fruits.get(0)),
() -> assertNotNull(fruits.get(1)),
() -> assertNotNull(fruits.get(2)));
// assert that all elements in the list are not empty
assertAll(() -> assertFalse(fruits.get(0).isEmpty()),
() -> assertFalse(fruits.get(1).isEmpty()),
() -> assertFalse(fruits.get(2).isEmpty()));
}
이는 Executable 목록을 가변 인자로 전달받아 각 Executable을 실행하는데, 실패하는 코드가 있으면 목록에 모아서 에러 메시지로 보여준다.
테스트 라이프 사이클
- 테스트 메서드를 포함한 객체 생성
- @BeforeEach 메서드 실행
- @Test 메서드 실행
- @AfterEach 메서드 실행
@BeforeEach에 대한 예시는 다음과 같습니다.
class TestExample {
private List<String> fruits;
@BeforeEach
void setUp() {
fruits = Arrays.asList("apple", "banana", "orange");
}
@Test
void testFruitsNotNull() {
// assert that all elements in the list are not null
assertAll(() -> assertNotNull(fruits.get(0)),
() -> assertNotNull(fruits.get(1)),
() -> assertNotNull(fruits.get(2)));
}
@Test
void testFruitsNotEmpty() {
// assert that all elements in the list are not empty
assertAll(() -> assertFalse(fruits.get(0).isEmpty()),
() -> assertFalse(fruits.get(1).isEmpty()),
() -> assertFalse(fruits.get(2).isEmpty()));
}
}
@Test 메서드가 종료된 이후 실행되는 @BeforeEach 예제 코드는 다음과 같습니다.
public class MyTestClass {
// Some test methods
@AfterEach
public void tearDown() {
// Perform cleanup tasks or reset the system state here
}
}
재밌는 점은 @Test 메서드를 실행 할 때마다 객체를 새로 생성하고 테스트 메서드를 실행한다는 점입니다.
만약 모든 테스트 메서드가 실행되기 전 딱 한번 실행되어야 하는 코드가 있다면
@BeforeAll 애너테이션을 사용할 수 있습니다.
public class MyTestClass {
@BeforeAll
public static void setUp() {
// Perform tasks that need to be done once before all tests are run
}
// Some test methods
}
반대로 @AfterAll() 메서드는 클래스의 모든 테스트 메서드를 실행한 뒤에 실행됩니다.
public class MyTestClass {
// Some test methods
@AfterAll
public static void tearDown() {
// Perform tasks that need to be done once after all tests are run
}
}
이 두 개의 메서드는 정적 메서드에 적용합니다.
테스트 메서드 간 실행 순서 의존과 필드 공유하지 않기!
테스트 메서드는 특정 순서대로 실행된다는 가정 하여 테스트 메서드를 가정하면 안 됩니다. 그 순서는 버전에 따라 달라질 수 있습니다. 순서에 따라 테스트가 실패한다는 것은 상당한 의문점을 가져야 하는 부분입니다.,
기억해야 할 점은 각 테스트 메서드는 서로 독립적으로 동작해야 한다는 것입니다.
이를 위해 서로 필드를 공유한다거나 실행 순서를 가정하고 테스트를 작성하지 말아야 합니다.
(Junit은 실행 순서를 지정하는 방법도 제공합니다.)
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(MethodOrderer.Alphanumeric.class)
public class MyTestClass {
@Test
public void testA() {
// Test code
}
@Test
public void testB() {
// Test code
}
@Test
public void testC() {
// Test code
}
}
모든 테스트 실행
코드를 원격 리포지토리에 푸시하거나 빌드 후 배포하기 전에는 모든 테스트를 실행해서 문제가 되는 부분이 없는지 확인해야 합니다.
이때 사용하는 방법은 간단하다.
- mvn test
- gradle test ( gradlew test)
하지만 대부분 빌드 툴에서는 빌드하기 전에 test 태스크를 먼저 진행하기 때문에 mvc pacakge, gradle build 하여도 테스트가 진행된다.
'Test' 카테고리의 다른 글
테스트 가능한 설계 (2) | 2022.12.15 |
---|---|
Spy (0) | 2022.12.14 |
테스트 코드에 작성 순서가 있다고? (0) | 2022.12.10 |
TDD init (0) | 2022.12.10 |
TDD 시작 (0) | 2022.12.10 |
댓글