✅ JUnit 5 소개
- 자바 8 이상을 필요로 한다.
- Platform
- 테스트를 실행해주는 런처 제공
- IDE에서도 platform 을 제공한다.
- Jupiter
- TestEngine API 구현체로 JUnit 5를 제공
- Vintage
- JUnit 4와 3을 지원하는 TestEngine 구현체
✅ JUnit 5 시작하기
기본 어노테이선에 대해 알아보자
@Test
- spring-boot-starter가 제공하는 기본 junit 의존성이 5.xx 대로 변경되어 별다른 의존성 없이 jnit5 바로 사용 가능
- 단위 테스트를 하고 싶은 메소드에 해당 어노테이션을 달아주면 테스트 가능
@BeforeAll / @AfterAll
- 모든 테스트 들이 시작 하기 전 / 종료 한 후 딱 한번 실행되는 메소드
@BeforeEach / @AfterEach
- 각 테스트들이 시작 하기 전 / 종료 한 후 실행되는 메소드
@Disabled
- 해당 테스트는 진행하고 싶지 않을 경우 사용
✅ JUnit 5 - 테스트 이름 표기하기
기본 테스트 메소드 표기 전략 → 메서드 이름
@DisplayNameGeneration
- 클래스에 적용하는 어노테이션으로 해당 클래스에 속해있는 모든 메소드들에 적용된다.
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest { ... }
- 이렇게 사용할 경우 메서드 이름에 _ 로 표기 되어 있는 부분을 ' ' 공백으로 치환한다는 의미
@DisplayName
- 테스트 메소드 이름으로 테스트를 표현하려면 메소드 명이 길어지기 마련이다.
따라서 메소드 이름보다는 해당 어노테이션을 사용하여 표현하는 것을 더 권장 - @DisplayNameGeneration 보다 우선순위가 높다.
📌 실행 TIP ..?
테스트를 하고자 하는 메소드에 커서를 올리고 control + shitf + r 을 사용하면 해당 메소드만 실행
그 외에 커서가 올라가면 해당 class 내에 속해있는 모든 테스트들이 실행
✅ JUnit 5 - Assertion
assertEquals
- 실제 값이 기대한 값과 같은지 확인
public static void assertEquals(Object expected, Object actual, String message) {
AssertEquals.assertEquals(expected, actual, message);
}
- 위와 같이 기대한 값이 첫번째 파라미터에 오고, 그 뒤에 실제 값이 오게 된다.
순서가 변경되어도 되지만 정의되어 있는 것 처럼 사용하는것을 권장한다. - 맨 뒤, message는 테스트가 실패했을 경우, 출력해주는 message이다.
- 나중에 내가 짠 테스트 코드를 보았을 때, 이게 왜 실패가 되는거지 ..? 라고 생각할 때,
좀 더 명확하게 실패 원인을 적어 주면 도움이 될 것 같다!
- 나중에 내가 짠 테스트 코드를 보았을 때, 이게 왜 실패가 되는거지 ..? 라고 생각할 때,
assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() {
@Override
public String get() {
return "스터디를 처음 만들면 DRAFT 상태이다.";
}
});
- message 부분에 오늘 부분을 Supplier<>를 사용해 작성해 줄 수 있다.
- 문자열만 적어주는게 더 간단하지 않을까 ?
람다식을 사용하여 함수로 정의해주면 해당 테스트에서 실패 케이스가 되었을때에만 해당 함수를 실행하게 되어
만약 해당 부분에 "" + "" 이러한 연산 기능이 있다면 불필요한 연산을 줄여줄 수 있다. - 문자열 연산 비용 및 성능을 신경쓰는 입장이라면 람다식을 사용하게 되면 더 유용하다.
- 문자열만 적어주는게 더 간단하지 않을까 ?
@Test
@DisplayName("스터디 만들기") // 테스트 이름을 좀 더 쉽고 간결하게 제공하도록 도와주는 어노테이션
void create_new_study() {
Study study = new Study(-10);
assertNotNull(study);
// 해당 테스트도 실패
// 테스트 1
assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "스터디를 처음 만들면 DRAFT 상태이다.");
// 해당 테스트도 실패
// 테스트 2
assertTrue(study.getLimit() > 0 , "스터디 최대 인원은 0보다 커야 한다.");
}
해당 테스트를 돌려보면, 테스트 1에 대한 실패 이유만 나오고,
테스트 1에 대한 case가 해결되야 테스트 2에 대한 실패 이유가 나오게 된다.
한번에 확인할 수 있는 방법은 없을까?
assertAll() 을 사용하여 한번에 확인 할 수 있다.
@Test
@DisplayName("스터디 만들기") // 테스트 이름을 좀 더 쉽고 간결하게 제공하도록 도와주는 어노테이션
void create_new_study() {
Study study = new Study(-10);
assertAll(
() -> assertNotNull(study),
() -> assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "스터디를 처음 만들면 DRAFT 상태이다."),
() -> assertTrue(study.getLimit() > 0 , "스터디 최대 인원은 0보다 커야 한다.")
);
}
assertThrows()
예외 발생을 확인해준다.
@Test
//@Disabled - 테스트를 진행하지 않고 싶을 경우 사용
void create_new_study_again() {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new Study(-10));
String msg = e.getMessage();
assertEquals("limit은 0보다 커야 한다.", e.getMessage());
}
assertTimeount(duration_executable)
특정 시간 안에 실행이 완료되는지 확인해준다.
assertTimeout(Duration.ofMillis(100), () -> {
new Study(10);
Thread.sleep(300); // 테스트 실패
});
100ms로 완료가 되어야 하는데 Thread.sleep을 통해 300ms 를 지연시키므로 테스트가 실패가 된다.
만약, 100ms안에 완료되지 않으면 해당 테스트를 종료하고 싶을 때는
assertTimeoutPreemptively() 를 사용하면 된다.
📌 assertTimeoutPreemptively() 사용 시, 유의 할 점
만약, 테스트 코드 안에 spring ThreadLocal에 대한 로직이 있다면
ThreadLocal은 다른 스레드 안에서 공유가 되지 않는다.
그렇기에, spring transactional한 테스트는 기본적으로 rollback을 기본으로 하는데
이 트랜잭션의 설정을 가지고 있는 스레드와 별개의 스레드로 해당 코드를 실행하기 때문에
rollback이 안되고 db에 반영이 될 수 있다.
따라서 해당 테스트 코드 안에 스레드와 연관된 코드가 있다면 주의 해야 하며
다소 시간이 걸릴지라도 assertTimeout()을 사용하는 것이 안전한 방법이다.
✔️ 그 외 메소드
assertNotNull(actual)
- 값이 null이 아닌지 확인
assertTrue(boolean)
- 다음 조건이 참인지 확인
📌 그 외 다른 라이브러리
- assertJ 등의 라이브러리를 사용할 수 있다.
- assertJ나, Hemcrest 등은 기본 spring-boot-starter가 제공해주고 있다.
- 따라서 별도 의존성 추가 없이 import하여 사용하면 된다.