단위 테스트
단위 테스트란 특정 소스코드의 모듈이 의도한 대로 잘 작동하는지 검증하는 테스트입니다. Spring에서 단위 테스트는 Spring Container에 올라와 있는 Bean을 테스트 하는 것입니다.
JUnit5
JUnit은 Java의 테스트 프레임워크 입니다. JUnit5는 가장 많이 쓰이는 JUnit 버전이며, JUnit4와 달리 JUnit Jupiter API가 추가되어 총 3개의 모듈로 구성됩니다.
JUnit5의 세가지 모듈
- JUnit platform
테스트를 발견하고 테스트 계획을 생성하는 Test Engine 인터페이스를 정의합니다. - JUnit Jupiter
JUnit5에서 테스트 및 Extension을 작성하기 위한 새로운 프로그래밍 모델과 확장 모델의 조합이며, TestEngine API 구현체 입니다. - JUnit Vintage
JUnit Vintage: Junit3, JUnit4를 실행할 수 있는 TestEngine 입니타. 하위 호환성을 위해서 존재합니다.
AssertJ
AsserJ는 JUnit Assertions의 사용을 도와주는 라이브러리 이며, spring-boot-starter-test에 포함되어 있습니다.
아래는 예시입니다.
assertEquals(A, B); // A, B가 동일한지
assertTrue(A); // A가 참인지 거짓인지
...
위와 같이 사용하여 JUnit으로 진행된 테스트에서 검증을 담당합니다.
Mockito
Mockitos는 Java에서 모의 객체를 생성하고 관리하는 프레임워크 입니다.
Mockito의 기능
- 모의 객체 생성: 모의 객체를 생성하고 객체의 메서드 호출을 기록하고, 호출 횟수와 인자를 확인
- 메서도 호출 검증: 모의 객체에서 메서드가 호출될 때 이를 검증
- Stub: 모의 객체에서 메서드가 호출될 때 반환되는 값을 직접 지정하는 스텁 기능
- Mock 객체 간 연결: 여러 개의 모의 객체를 연결하여 하나의 테스트에서 여러 객체를 사용
Mockito 사용법
- 의존성 주입
@Mock: 가짜 객체를 만들어 반환
@Spy: stub하지 않은 메소드들은 원본 메소드 그대로 사용하는 어노테이션
@InjectMocks: @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입 - Stub으로 결과 처리
doReturn(): 가짜 객체가 특정한 값을 반환해야 하는 경우
doNoting(): 가짜 객체가 아무것도 반환하지 않은 경우(void)
doThrow():가짜 객체가 예외를 발생시키는 경우
Junit과 Mockito를 사용한 테스트
- 컨트롤러 단위 테스트 예시
- 사용자 회원가입 API
@RestController
@RequiredArgsConstructor
public class UserController{
private final UserService userService;
@PostMapping("/users/signUp") // 회원가입
public ResponseEntity<UserResponse> signUp(@RequestBody SignUpRequest request){
return ReponseEntity.status(HttpStatus.CREATED)
.body(userService.signUp(request));
}
@GetMapping("/users")
public ResponseEntity<List<UserResponse>> findAll() {
return ResponseEntity.ok(userService.findAll());
}
}
-테스트 코드
@ExtendWith(MockitoExtension.class) //JUnit5와 Mockito 연동
class UserControllerTest{
@InjectMocks
private UserController userController; //테스트 대상에 가짜 객체 주입
@Mock
private UserService userService; //가짜 객체 생성
@private MockMvc mockMvc; // HTTP 호출을 위한 객체
@BeforeEach
public void init(){
mockMvc = MockMvcBuilders.standaloneSetup(UserController).build()
}
@DisplayName("회원 가입 성공")
@Test
void signUpSuccess() throws Exception {
// given 단계: stub 생성
SignUpRequest request = signUpRequest();
UserResponse response = userResponse();
doReturn(response).when(userService)
.singUp(any(SignUpRequest.class));
// when 단계: HTTP 요청, 여기서 gson은 json 구조를 띄는 직렬화 된 데이터를 JAVA 객체로
// 역직렬화, 직렬화 해주는 라이브러리
ResultActions resultActions =mockMvc.perform(
MockRequestBuilders.post("/user/signUp")
.contentType(ModiaType.APPLICATION_JSON)
.content(new Gson().toJson(request))
);
//then 단계: API 호출 결과로 200Reponse와 응답결과를 검증
MvcResult mvcResult = resultActions.andExpect(status().isOk())
.andExpect(jsonPath("email", response.getEmail()).exists())
.andExpect(jsonPath("pw", response.getPw().exists())
.andExpect(jsonPath("role", response.getRole()).exists());
}
private SignUpRequest signUpRequest(){
return SignUpRequest.builder()
.email("test@test.test")
.pw("test")
.build();
}
private UserResponse userResponse(){
return UserResponse.builder()
.email("test@test.test")
.pw("test")
.role(UserRole..ROLE_USER)
.build();
}
}
2. 서비스 단위 테스트 예시
- 사용자 목록조회 비지니스 로직
@Service
@RequiredArgsConstructor
@Transactional(readOnly = ture)
public class UserServiceImpl{
private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder
@Transactional
public UserResponse signUp(final SignUpRequest request){
final User user = User.builder()
.email(request.getEmail())
.pw(passwordEncoder.encode(request.getPw()))
.role(UserRole.ROLE_USER)
.build();
return UserResponse.of(userRepository.save(user));
}
public List<User> findAll() {
return userRepository.findAll().stream()
.map(UserResponse::of)
.collect(Collectors.toList()));
}
}
- 테스트 코드
@DisplayName("사용자 목록 조회")
@Test
void findAll() {
// given
doReturn(userList()).when(userRepository)
.findAll();
// when
final List<UserResponse> userList = userService.findAll();
// then
assertThat(userList.size()).isEqualTo(5);
}
private List<User> userList() {
List<User> userList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
userList.add(new User("test@test.test", "test", UserRole.ROLE_USER));
}
return userList;
}
3. 레포지토리 단위 테스트 예시
- 사용자 레퍼지토리
public interface UserRepository extends JpaRepository <User, Long> {
}
- 사용자 추가 테스트
@DataJpaTest
class UserRepositoryTest{
@Autowired
private UserRepository userRepository;
@DisplayName("사용자 추가")
@Test
void addUser(){
//given
User user = user();
// when
User savedUser = userRepository.save(user);
//then
assertThat(savedUser.getEmail()).isEqualTo(user.getEmail());
assertThat(savedUser.getPw()).isEqualTO9user.getPw());
assertThat(savedUser.getROle()).isEqualTo(user.getROle());
}
private User user() {
return User.builder()
.email("email")
.pw("pw")
.role*UserRole.ROLE_USER).build();
}
}
Reference
https://velog.io/@choidongkuen/Junit-%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C-e0w6tlvp
https://dkrnq.tistory.com/51
https://mangkyu.tistory.com/145
'IT > QA' 카테고리의 다른 글
Jenkins, Docker-compose로 컨테이너화 하기 (0) | 2024.12.03 |
---|---|
통합테스트, SpringBootTest (0) | 2024.12.02 |
Zephyr를 이용한 테스트 관리 (1) | 2024.11.28 |
유닛 테스트 도구 Junit, Vscode에서 사용하기 (0) | 2024.11.26 |
Jira를 사용한 테스트는 언제 진행할까? (0) | 2024.11.24 |