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 |
|
@TestMethodOrder |
|
@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 |
|
@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:티스토리]
간단한 사용법
테스트 내용
- @BeforeAll를 사용하여 테스트전 setting을 해줄 것이다.
- 회원가입 로직과 로그인 로직을 테스트할 예정입니다.
회원가입
@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 를 생성
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());
}
'몰아 넣기' 카테고리의 다른 글
[알고리즘] 시간 복잡도와 Big-O 대해서 간단한 예제 (0) | 2022.08.18 |
---|---|
[Collection] HashMap의 특징, 사용법 (0) | 2022.08.16 |
[Java/Spring] Spring Boot 메일 발송하기(Google SMTP With thymeleaf) (0) | 2022.07.18 |
[java8] 자바 Stream의 foreach 와 for-loop에 대해서 (0) | 2022.07.14 |
[Spring/AWS] S3 저장소와 스프링부트(Spring Boot) 연동하기 및 사용법 (0) | 2022.07.12 |