🔥 테스트 코드 정복

🔥 JUnit 정복해보기 - 1

j_estory 2023. 2. 5. 16:01

✅ 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하여 사용하면 된다.