본 포스팅은 백기선님의 "더 자바, 애플리케이션을 테스트하는 다양한 방법" 을 보고 정리한 글 입니다.
관심 있으신 분들은 https://www.inflearn.com/course/the-java-application-test 를 살펴보세요

개요

테스트 자동화를 위한 Assertions 를 알아보자.

assertEquals

  • 이름에서 알 수 있다시피 기대하는 값과 실제 값이 동일한 지 검사하는 메서드다.

      static void assertEquals(Object expected, Object actual) {
          assertEquals(expected, actual, (String) null);
      }
    
      static void assertEquals(Object expected, Object actual, String message) {
          if (!objectsAreEqual(expected, actual)) {
              failNotEqual(expected, actual, message);
          }
      }
    
      static void assertEquals(Object expected, Object actual, Supplier<String> messageSupplier) {
          if (!objectsAreEqual(expected, actual)) {
              failNotEqual(expected, actual, messageSupplier);
          }
      }

사용법

    @Test
    void assert_test() {
        Study study = new Study(-10, 0);
        assertEquals(StudyStatus.DRAFT, study.getStatus(), "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다.");
        assertEquals(StudyStatus.DRAFT, study.getStatus(), () -> "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다.");
        assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() {
            @Override
            public String get() {
                return "처음 스터디를 만들면 상태값은" + StudyStatus.DRAFT + "여야 한다.";
            }
        });
    }
  • 첫 번째 인수
    • expected. 기대하는 값을 넣어준다.
  • 두 번째 인수
    • actual. 실제 값을 넣어준다.
  • 세 번째 인수
    • message. 실패했을 때 콘솔에 기록되는 message를 넣어준다.

세 번째 인수에 String 이나 Supplier을 넣어줄 수 있다.
차이점은 String을 넣는다면 테스트가 실패하든 성공하든 매번 메시지를 생성하지만, Supplier는 실패할 때만 메시지를 생성한다.
조그마한 성능차이에도 불안하다면 Supplier를 사용하자.


assertThrows

  • 예상하는 익셉션이 발생하는 지 확인한다.

      public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable) {
          return AssertThrows.assertThrows(expectedType, executable);
      }

사용법

    @Test
    void assert_throws() {
        IllegalArgumentException illegalArgumentException =
                assertThrows(IllegalArgumentException.class, () -> new Study(10, -1));
        String message = illegalArgumentException.getMessage();
        assertEquals("최소 참석인원은 0 보다 커야 합니다.", message);
    }
  • 첫 번째 인수
    • Class expectedType. 발생하는 예외의 클래스를 넣어준다.
  • 두 번째 인수
    • Executable. 실제 행동을 넣어준다. 위 코드에서는 new Study(..) 를 했을 때 IllegalArgumentException이 발생하기를 기대한다.

assertAll

    public static void assertAll(Executable... executables) throws MultipleFailuresError {
        AssertAll.assertAll(executables);
    }
  • 원래 assertions 는 assertion 실패하면 그 밑의 코드는 더 이상 진행하지 않는다.
  • 만약 실패하더라도 다른 assertions 의 결과를 보고 싶다면 assertAll을 쓰자.

사용법

    @Test
    void assert_all() {
        Study study = new Study(-10, 0);
        assertAll(
                () -> assertNotNull(study),
                () -> assertEquals(StudyStatus.DRAFT, study.getStatus(),
                        () -> "스터디를 처음 만들면 " + StudyStatus.DRAFT + "상태 여야 합니다."),
                () -> assertTrue(study.getLimit() > 0, "스터디 최대 참석 가능 인원은 0보다 커야 합니다.")
        );
    }
  • assertAll 안에 있는 모든 assertion을 확인한다.

assertTimeout

/**
     * <em>Assert</em> that execution of the supplied {@code executable}
     * completes before the given {@code timeout} is exceeded.
     *
     * <p>Note: the {@code executable} will be executed in the same thread as that
     * of the calling code. Consequently, execution of the {@code executable} will
     * not be preemptively aborted if the timeout is exceeded.
     *
     * @see #assertTimeout(Duration, Executable, String)
     * @see #assertTimeout(Duration, Executable, Supplier)
     * @see #assertTimeout(Duration, ThrowingSupplier)
     * @see #assertTimeout(Duration, ThrowingSupplier, String)
     * @see #assertTimeout(Duration, ThrowingSupplier, Supplier)
     * @see #assertTimeoutPreemptively(Duration, Executable)
     */
    public static void assertTimeout(Duration timeout, Executable executable) {
        AssertTimeout.assertTimeout(timeout, executable);
    }
  • 지정한 시간 내에 assertion 실행이 완료되지 않는다면 테스트가 실패한다.

사용법

    @Test
    void assert_timeout() {
        assertTimeout(Duration.ofMillis(100), () -> {
            new Study(10, 10);
            Thread.sleep(300);
        });
    }
  • 100 밀리세컨드 이내에 Executable 코드가 실행완료 되어야 한다.
  • 주의할 점은 100 밀리세컨드가 지나자마자 테스트가 실패하는 게 아니라, Executable 코드가 모두 완료된 후에야 테스트 성공/실패를 결정짓는다.

깃헙 링크

링크

본 포스팅은 백기선님의 "더 자바, 애플리케이션을 테스트하는 다양한 방법" 을 보고 정리한 글 입니다.
관심 있으신 분들은 https://www.inflearn.com/course/the-java-application-test 를 살펴보세요

개요

Junit5의 기본 애너테이션이라고 할 수 있는 @Test, @BeforeAll, @BeforeEach, @AfterAll, @AfterEach, @Disabled 를 알아보자.

@Test

본 어노테이션을 붙이면 Test 메서드로 인식하고 테스트 한다.
JUnit5 기준으로 접근제한자가 Default 여도 된다. (JUnit4 까지는 public이어야 했었다.)

    @Test
    void create1() {
        Study study = new Study();
        assertNotNull(study);
        System.out.println("create1()");
    }

    @Test
    void create2() {
        System.out.println("create2()");
    }

@BeforeAll

본 어노테이션을 붙인 메서드는 해당 테스트 클래스를 초기화할 때 딱 한번 수행되는 메서드다.
메서드 시그니쳐는 static 으로 선언해야한다.

    @BeforeAll
    static void beforeAll() {
        System.out.println("@BeforeAll");
    }

@BeforeEach

본 어노테이션을 붙인 메서드는 테스트 메서드 실행 이전에 수행된다.

    @BeforeEach
    void beforeEach() {
        System.out.println("@BeforeEach");
    }

@AfterAll

본 어노테이션을 붙인 메서드는 해당 테스트 클래스 내 테스트 메서드를 모두 실행시킨 후 딱 한번 수행되는 메서드다.
메서드 시그니쳐는 static 으로 선언해야한다.

    @AfterAll
    static void afterAll() {
        System.out.println("@AfterAll");
    }

@AfterEach

본 어노테이션을 붙인 메서드는 테스트 메서드 실행 이후에 수행된다.

    @AfterEach
    void afterEach() {
        System.out.println("@AfterEach");
    }

@Disabled

본 어노테이션을 붙인 테스트 메서드는 무시된다.

    @Disabled
    @Test
    void create3() {
        System.out.println("create3()");
    }

코드

/**
 * @author : Eunmo Hong
 * @since : 2020/08/13
 */

class StudyTest {

    @Test
    void create1() {
        Study study = new Study();
        assertNotNull(study);
        System.out.println("create1()");
    }

    @Test
    void create2() {
        System.out.println("create2()");
    }

    @Disabled
    @Test
    void create3() {
        System.out.println("create3()");
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println("@BeforeAll");
    }


    @AfterAll
    static void afterAll() {
        System.out.println("@AfterAll");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("@BeforeEach");
    }

    @AfterEach
    void afterEach() {
        System.out.println("@AfterEach");
    }
}

실행 콘솔

  • @BeforeAll 이 실행된다.
    --------반복------
    1. @BeforeEach 가 실행된다
    2. @Test 를 붙인 메서드가 실행된다.
    3. @AfterEach 가 실행된다
    --------반복------
  • @AfterAll 이 실행된다

깃헙 주소

링크

코드

  • 다음과 같은 컨트롤러와 서비스가 있다.

SampleController

@RestController
public class SampleController {

    private SampleService sampleService;

    public SampleController(SampleService sampleService) {
        this.sampleService = sampleService;
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello " + sampleService.getName();
    }
}

SampleService

  @Service
  public class SampleService {
      public String getName() {
          return "gracelove";
      }
  }
  • localhost:8080/hello로 요청을 보내면 "hello gracelove" 를 응답값으로 되돌려준다.

테스트

MockMvc를 이용한 테스트

  • @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)

    • 디폴트값이다. 괄호 안에 옵션안주면 자동적용된다.

    • 내장톰캣 실행안시킨다.

    • MockMvc 빈으로 등록 안시킨다

    • 따라서 MockMvc를 이용하기 위해서는 @AutoConfigureMockMvc 가 필요하다

      @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
      @AutoConfigureMockMvc
      class SampleControllerTest {
      
      @Autowired
      private MockMvc mockMvc;
      
      @Test
      void hello() throws Exception {
          mockMvc.perform(MockMvcRequestBuilders.get("/hello"))
                  .andExpect(status().isOk())
                  .andExpect(content().string("hello gracelove"))
                  .andDo(print());
      }
      }

TestRestTemplate 을 이용한 테스트

  • RANDOM_PORT옵션주면 내장톰캣 실행한다.

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    class SampleControllerTest2 {
    
    @Autowired
    TestRestTemplate testRestTemplate;
    
    @Test
    void hello() {
    
        String result = testRestTemplate.getForObject("/hello", String.class);
        assertThat(result).isEqualTo("hello gracelove");
    }
    }

WebTestClient를 이용한 테스트

  • non-blocking 기반의 클라이언트다.

  • 이용하기 위해서는 pom.xml 에서 다음과 같이 의존성을 추가해주자.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SampleControllerTest3 {

    @Autowired
    WebTestClient webTestClient;

    @Test
    void hello() {
        webTestClient.get().uri("/hello").exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("hello gracelove");
    }
}

@WebMvcTest와 @MockBean을 이용한 테스트

  • 위 테스트코드들은 모두 통합테스트다. 기본적으로 @SpringBootTest 어노테이션을 사용하면 스프링이 관리하는 모든 빈을 등록시켜서 테스트하기 때문에 무겁다.

  • 하지만 @WebMvcTest는 web 레이어 관련 빈들만 등록하므로 비교적 가볍다.

  • web레이어 관련 빈들만 등록되므로 Service는 등록되지않는다. 따라서 가짜로 만들어줄 필요가 있다 (@MockBean)

    @WebMvcTest(SampleController.class)
    class SampleControllerTest4 {
    
     @Autowired
     MockMvc mockMvc;
    
     @MockBean
     SampleService mockSampleService;
    
     @Test
     void hello() throws Exception {
         when(mockSampleService.getName()).thenReturn("gracelove");
    
         mockMvc.perform(MockMvcRequestBuilders.get("/hello"))
                 .andDo(print())
                 .andExpect(status().isOk())
                 .andExpect(content().string("hello gracelove"));
     }
    }

깃헙 : https://github.com/gracelove91/springboot-with-whiteship/tree/d3a19620bd70d5dd4af4ffa14fcc9408bef3ed29

  • 스프링부트 2.2부터 JUnit5가 기본적으로 의존성에 추가된다.
  • 클래스와 테스트메서드에 더이상 public 접근제한자 설정 안해줘도 된다. package-private으로 설정하면 된다.

@Test

  • JUnit4의 @Test와 같은 애너테이션이라고 이해하면 된다.

@BeforeAll

  • 모든 테스트메서드가 실행하기 전 딱 한 번만 호출한다.
  • static void 여야한다

@AfterAll

  • 모든 테스트메서드가 실행한 후 딱 한 번만 호출한다.
  • static void 여야한다

@BeforeEach

  • 개별 테스트 메서드가 실행되기 전 호출한다.

@AfterEach

  • 개별 테스트 메서드가 실행한 후 호출한다

@Disabled

  • 테스트 메서드 위에 붙이면 그 테스트는 Ignored 된다.

기본적인 테스트 코드

class BasicTest {
    @Test
    void test1() {
        System.out.println("test1");
    }

    @Test
    void test2() {
        System.out.println("test2");
    }

    @BeforeAll
    static void beforeAll() {
        System.out.println("beforeAll");
    }


    @AfterAll
    static void afterAll() {
        System.out.println("afterAll");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("beforeEach");
    }

    @AfterEach
    void afterEach() {
        System.out.println("afterEach");
    }
}
  • 실행 순서

    beforeAll() => beforeEach() => test1() => afterEach() => beforeEach() => test2() => afterEach() => afterAll()

JUnit4와 JUnit5의 대응 애너테이션

JUnit4 JUnit5
@Test @Test
@BeforeClass @BeforeAll
@AfterClass @AfterAll
@Before @BeforeEach
@After @AfterEach
@Ignored @Disabled

+ Recent posts