JUnit 5 공식 가이드 문서   

PDF 다운로드 

 

JUnit 5 User Guide

Although the JUnit Jupiter programming model and extension model do not support JUnit 4 features such as Rules and Runners natively, it is not expected that source code maintainers will need to update all of their existing tests, test extensions, and custo

junit.org

 

JUnit5란?

자바 개발자가 많이 사용하는 테스팅 기반 프레임워크를 말합니다.JUnit 5는 런타임에 Java 8(또는 그 이상)이 필요하며, JUnit Platform과 JUnit  Jupiter, Junit Vintage 결합한 형태이다.

즉,  JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform: 
 테스트를 실행해주는 런처와 TestEngine API를 제공함.

Jupiter:               
TestEngine API 구현체로 JUnit5에서 제공함.

Vintage:             
TestEngine API 구현체로 JUnit3, 4에서 제공함.

 

주석설명

주석 설명
@Test
메소드가 테스트 메소드임을 나타냅니다. JUnit 4의 @Test주석과 달리 이 주석은 JUnit Jupiter의 테스트 확장이 자체 전용 주석을 기반으로 작동하기 때문에 속성을 선언하지 않습니다. 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 .
@ParameterizedTest
메소드가 매개변수화된 테스트 임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 .
@RepeatedTest
메소드가 반복 테스트 를 위한 테스트 템플릿임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 .
@TestFactory
메소드가 동적 테스트 를 위한 테스트 팩토리임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 .
@TestTemplate
메소드는 등록된 제공자 에 의해 반환된 호출 컨텍스트의 수에 따라 여러 번 호출되도록 설계된 테스트 케이스의 템플릿 임을 나타냅니다 . 이러한 메서드는 재정의 되지 않는 한 상속 됩니다 .
@TestClassOrder
주석이 달린 테스트 클래스의 테스트 클래스에 대한 테스트 클래스 실행 순서 를 구성하는 데 사용됩니다 @Nested. 이러한 주석은 상속 됩니다.
@TestMethodOrder
주석이 달린 테스트 클래스에 대한 테스트 메서드 실행 순서 를 구성하는 데 사용됩니다 . JUnit 4의 @FixMethodOrder. 이러한 주석은 상속 됩니다.
@TestInstance
주석이 달린 테스트 클래스 의 테스트 인스턴스 수명 주기 를 구성하는 데 사용됩니다 . 이러한 주석은 상속 됩니다.
@DisplayName
테스트 클래스 또는 테스트 메서드에 대한 사용자 지정 표시 이름 을 선언합니다 . 이러한 주석은 상속 되지 않습니다 .
@DisplayNameGeneration
테스트 클래스에 대한 사용자 지정 표시 이름 생성기 를 선언합니다 . 이러한 주석은 상속 됩니다.
@BeforeEach
주석이 달린 메서드가 현재 클래스의 각 , , 또는 메서드 보다 먼저 실행되어야 함을 나타냅니다 . JUnit 4와 유사합니다 . 이러한 메서드는 재정의 되거나 대체 되지 않는 한 상속 됩니다 (즉, Java의 가시성 규칙과 상관없이 서명만 기반으로 대체됨). @Test@RepeatedTest@ParameterizedTest@TestFactory@Before
@AfterEach
주석이 달린 메서드는 현재 클래스의 각 , , 또는 메서드 다음 에 실행되어야 함을 나타냅니다 . JUnit 4와 유사합니다 . 이러한 메서드는 재정의 되거나 대체 되지 않는 한 상속 됩니다 (즉, Java의 가시성 규칙에 관계없이 서명만 기반으로 대체됨). @Test@RepeatedTest@ParameterizedTest@TestFactory@After
@BeforeAll
주석이 달린 메서드가 현재 클래스의 모든 , , 및 메서드 보다 먼저 실행되어야 함을 나타냅니다 . JUnit 4와 유사합니다 . 이러한 메서드는 상속 되거나 무시 되거나 대체 되지 않는 한 (즉 , Java의 가시성 규칙에 관계없이 서명만 기반으로 대체됨) "클래스별" 테스트 인스턴스 수명 주기 가 사용되지 않는 한 상속되어야 합니다. @Test@RepeatedTest@ParameterizedTest@TestFactory@BeforeClassstatic
@AfterAll
현재 클래스의 모든 , , 및 메서드 다음 에 주석이 달린 메서드가 실행되어야 함을 나타냅니다 . JUnit 4와 유사합니다 . 이러한 메서드는 상속 되거나 무시 되거나 대체 되지 않는 한 (즉 , Java의 가시성 규칙에 관계없이 서명만 기반으로 대체됨) "클래스별" 테스트 인스턴스 수명 주기 가 사용되지 않는 한 상속되어야 합니다. @Test@RepeatedTest@ParameterizedTest@TestFactory@AfterClassstatic
@Nested
주석이 달린 클래스가 비정적 중첩 테스트 클래스 임을 나타냅니다 . Java 8 ~ Java 15에서는 "클래스별" 테스트 인스턴스 수명 주기@BeforeAll 가 사용되지 않는 한 테스트 클래스 에서 @AfterAll메서드를 직접 사용할 수 없습니다 . Java 16부터 테스트 인스턴스 수명 주기 모드를 사용 하여 테스트 클래스 에서 와 같이 메서드를 선언할 수 있습니다 . 이러한 주석은 상속 되지 않습니다 .@Nested@BeforeAll@AfterAllstatic@Nested
@Tag
클래스 또는 메서드 수준에서 테스트 필터링을 위한 태그 를 선언하는 데 사용됩니다 . TestNG의 테스트 그룹이나 JUnit 4의 카테고리와 유사합니다. 이러한 주석은 클래스 수준에서 상속 되지만 메서드 수준에서는 상속되지 않습니다.
@Disabled
테스트 클래스 또는 테스트 메서드 를 비활성화 하는 데 사용됩니다 . JUnit 4와 유사합니다 @Ignore. 이러한 주석은 상속 되지 않습니다 .
@Timeout
실행이 지정된 기간을 초과하는 경우 테스트, 테스트 팩토리, 테스트 템플릿 또는 수명 주기 메서드를 실패하는 데 사용됩니다. 이러한 주석은 상속 됩니다.
@ExtendWith
확장을 선언적으로 등록 하는 데 사용됩니다 . 이러한 주석은 상속 됩니다.
@RegisterExtension
필드를 통해 프로그래밍 방식으로 확장 을 등록하는 데 사용됩니다 . 이러한 필드는 음영 처리 되지 않는 한 상속 됩니다 .
@TempDir
라이프 사이클 방법 또는 테스트 방법에서 필드 주입 또는 매개 변수 주입을 통해 임시 디렉토리 를 제공하는 데 사용됩니다 . org.junit.jupiter.api.io패키지 에 있습니다 .

 

Java 단위 테스트(Unit Test) 작성 준비

필요한 라이브러리 

요즘 Java 단위테스트 작성에는 크게 2가지 라이브러리가 사용된다.

  • JUnit5: 자바 단위 테스트를 위한 테스팅 프레임워크
  • AssertJ: 자바 테스트를 돕기 위해 다양한 문법을 지원하는 라이브러리

JUnit 만으로도 단위 테스트를 충분히 작성할 수 있다. 하지만 JUnit에서 제공하는 assertEquals()와 같은 메소드는 AssertJ가 주는 메소드에 비해 가독성이 떨어진다. 그렇기 때문에 순수 Java 애플리케이션에서 단위 테스트를 위해 JUnit5와 AssertJ 조합이 많이 사용된다.

 

given/when/then 패턴 

요즘 단위테스트는 거의 given-when-then 패턴으로 작성하는 추세이다. given-when-then 패턴이란 1개의 단위 테스트를 3가지 단계로 나누어 처리하는 패턴으로, 각각의 단계는 다음을 의미한다.

  • given(준비): 어떠한 데이터가 준비되었을 때
  • when(실행): 어떠한 함수를 실행하면
  • then(검증): 어떠한 결과가 나와야 한다.

추가적으로 어떤 메소드가 몇번 호출되었는지를 검사하기 위한 verify 단계도 사용하는 경우가 있는데, 그렇게 실용성이 크지 않으므로 메소드의 호출 횟수가 중요한 테스트에서만 선택적으로 사용하면 될 것 같다.

출처: https://mangkyu.tistory.com/144 [MangKyu's Diary:티스토리]

 

 

 

간단한 사용법

테스트 내용

  1. @BeforeAll를 사용하여 테스트전 setting을 해줄 것이다.
  2. 회원가입 로직과 로그인 로직을 테스트할 예정입니다.

 

회원가입

@PostMapping("/user/signup")
public UserRegisterRespDto registerUser(@RequestBody UserRequestDto Dto,
                                        @RequestParam(value = "isEmailCheck", defaultValue = "true") boolean isEmailCheck) {
    return userService.registerUser(Dto, isEmailCheck);
}

 

로그인

@PostMapping("/user/login")
public TokenResponseDto login(@RequestBody UserRequestDto userRequestDto) {
    return userService.login(userRequestDto);
}

 

 

project명/src/main  경로에 test라는 패키지를 만들어 테스트하려는 Contoroller와 같은 경로에 ControllerTest.java 를 생성

UserController에 있는 메소드를 Test할 예정이기에 같은 경로상에 UserControllerTest를 생성

UserControllerTest 생성

간단하게 주석으로 설명을 남겼습니다. 보면서 자신의 코드에 맞게 메소드만 변경하면 됩니다.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //랜덤 포트 설정
@TestInstance(TestInstance.Lifecycle.PER_CLASS)  //테스트 인스턴스 수명 주기를 클래스로 설정
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)  // Method에 order를 사용하는 설정
public class UserControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;     //통신을 위한 

    private HttpHeaders headers;             // HttpHeaders 설정을 위한 
    private ObjectMapper mapper = new ObjectMapper();  //직열화를 위한  ObjectMapper
    
    @Autowired
    private UserInfoInJwt userInfoInJwt;  //개인적인 토큰 파싱할 때 이용하는 의존클래스

}

 

@BeforeAll코드 작성

 @BeforeAll를 사용한 메소드는 setup으로 @Test 어노테이션을 달아놓은 메소드가 실행하기 전 딱 한번 실행된다. 

@AfterAll ->  @Test 메소드가 모두 끝나고 딱  한번 실행되는 메소드에 사용된다.
@BeforeEach,  @AfterEach   ->   위와 동일하지만 @Test가 시작되고 끝날 때마다고 연속으로 실행되는 어노테이션
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 
@TestInstance(TestInstance.Lifecycle.PER_CLASS)  
 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;     //통신을 위한 

    private HttpHeaders headers;             // HttpHeaders 설정을 위한 
    private ObjectMapper mapper = new ObjectMapper();  //직열화를 위한  ObjectMapper


    @BeforeAll
    public void setup() throws JsonProcessingException {
        headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
    }

}

 

Test 코드 작성

테스트할 메소드에는 @Test라는 어노테이션으로 테스트가 가능하며  @DisplayName()라는 어노테이션을 사용해 테스트 내용을 적을 수 있다.

@Test
@DisplayName("회원가입")
public void 회원가입() throws JsonProcessingException {
	//given
    UserDto userDto = UserDto.builder()
            .email("whitew295@gmail.com")
            .username("test4321")
            .password("testest1234")
            .nickname("테스트코드유저")
            .build();

    String requestBody = mapper.writeValueAsString(userDto);
    HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);

    //when
    ResponseEntity<UserRegisterRespDto> userDtoResponseEntity = restTemplate.postForEntity(
            "/user/signup?isEmailCheck=false",
            stringHttpEntity,
            UserRegisterRespDto.class
    );

    //then
    assertEquals(HttpStatus.OK, userDtoResponseEntity.getStatusCode());

    UserRegisterRespDto responseUserDto = userDtoResponseEntity.getBody();
    assertEquals(200, responseUserDto.getStatusCode());
    assertEquals("회원가입 성공", responseUserDto.getErrorMsg());
}

 

@Test
@DisplayName("로그인 성공")
public void itSuccessLogin() throws JsonProcessingException {
    //given
    UserDto userDto = UserDto.builder()
            .username("test4321")
            .password("testest1234")
            .build();

    String body = mapper.writeValueAsString(userDto);
    HttpEntity<String> entity = new HttpEntity<>(body, headers);

    //then
    ResponseEntity<TokenDto> responseEntity2 = restTemplate.postForEntity(
            "/user/login",
            entity,
            TokenDto.class
    );

    //when
    assertEquals(HttpStatus.OK, responseEntity2.getStatusCode());
    TokenDto tokenDto = responseEntity2.getBody();


    //토큰을 파싱했을 때 로그인한 username이 같은지
    assertEquals(
            userDto.getUsername(),
            userInfoInJwt.getToken("Bearer " + tokenDto.getAccessToken()).get("username")
    );
}

 

 

@AfterAll 및 주의할 점

아래 코드는 메소드가 다 돌고 나서 테스트한 유저를 제거해주는 로직이다. 아래처럼 직접적으로 DB에 접근한다면 특히 JPA 라면 @Rollback(value = false) 로 롤백되는 것을 막아줘야한다!

@AfterAll
@Rollback(value = false)
public void deleteTestUser() {
    Users users = userRepository.findByUsername("test4321")
            .orElseThrow(() -> new RuntimeException("TestCode 회원을 못찾았습니다."));
    folderRepository.deleteByUsersId(users.getId());
    userRepository.deleteById(users.getId());
}

 

복사했습니다!