사실 지금까지 학과 프로젝트를 하면서 따로 테스트를 하면서 진행해본 적은 없었다. Spring / Spring Boot를 써본 적은 있었는데 오류나면 항상 어느 부분에서 터졌는지 System.out.println(); 하여 확인하고 수정하곤 했다. 그러다가 테스트를 해볼 기회가 생겼고 라이브러리 및 테스트 방법에 대해 이것저것 찾아보게 되었다.
참고로, 완벽히 이해한 상태는 아니고 지금까지 공부한 내용을 정리하는 개념으로 작성한 것이므로 틀린 부분이 있을 수도 있다...
단위 테스트는 응용 프로그램에서 테스트 가능한 작은 소프트웨어를 실행하여 예상대로 동작하는지 확인하는 테스트다. 테스트 대상 단위의 크기는 정해져있지 않고 개인적으로는 그 단위가 메소드 단위라고 생각한다.
Mvc를 위한 테스트로 컨트롤러가 예상대로 동작하는지 테스트하는데 사용된다.
package com.example.bestudy;
import com.example.bestudy.controller.UserController;
import com.example.bestudy.dto.UserDto;
import com.example.bestudy.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import static org.hamcrest.Matchers.is;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class)
public class WebMvcTestExample {
@MockBean
private UserService userService;
@Autowired
private MockMvc mvc;
@Test
void 단위테스트() throws Exception {
// given
UserDto.MyPageResponse response = UserDto.MyPageResponse
.builder()
.id("jduckling1024")
.name("지니")
.build();
given(userService.getUser("jduckling1024")).willReturn(response);
// when
final ResultActions actions = mvc.perform(
get("/api/users/jduckling1024")
.contentType(MediaType.APPLICATION_JSON))
.andDo(print());
// then
actions.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.name", is("지니")))
.andDo(print());
}
}
유저 정보를 아이디로 조회했을 때 어떻게 반환할지에 설정하고 실제로 조회했을 때 예상한 결과가 나오는지에 대한 테스트 코드를 간단하게 작성해보았다.
JPA 관련된 설정만 로드하며 JPARepository의 단위테스트 필요 시 사용하면 효과적이다.
in-memory embedded database에 대한 테스트를 진행한다.
in-memory 데이터베이스 : 디스크가 아닌 주 메모리에 모든 데이터를 보유하고 있는 데이터베이스로 H2, HSQL 등이 있다.
참고 : Intellij에서 H2 연결
단위 테스트가 끝날 때마다 자동으로 롤백한다.
package com.example.bestudy;
import com.example.bestudy.entity.Users;
import com.example.bestudy.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import java.util.List;
// 인메모리 DB를 사용
// 단위 테스트가 끝날 때마다 자동으로 DB 롤백
// 실제 DB에서 테스트를 하고싶으면
// @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 추가
@DataJpaTest
public class DataJpaTestExample {
// static은 의존성 주입이 안됩니다...
@Autowired
private UserRepository userRepository;
void 초기화(){
Users user1 = new Users("dd", "1234", "지니");
userRepository.save(user1);
Users user2 = new Users("duck", "1234", "오리");
userRepository.save(user2);
}
@Test
void 테스트(){
초기화();
List<Users> list = userRepository.findAll();
for (Users user : list) {
System.out.println(user.getId() + " " + user.getPassword());
}
}
}
예제 코드라고 칭하기도 굉장히 애매...하다. given when then을 지켜서 작성한 것은 아니고 단위 테스트가 끝날 때마다 자동으로 롤백이 이루어지는지를 확인하는데 중점을 두어 간단하게 작성해보게 되었다.
통합 테스트는 단위 테스트와 달리 외부 라이브러리 등 개발자가 변경할 수 없는 부분까지 묶어 검증할 때 사용하며 DB에 접근하거나 전체 코드 및 환경이 제대로 작동하는지 확인하는 작업이다.
Spring Boot에서는 @SpringBootTest를 붙여 통합 테스트를 한다. 이는 애플리케이션의 설정, 모든 Bean을 로드한다.
JUnit4를 사용할 때는 반드시 JUnit의 SpringJUnit4ClassRunner를 상속받는 @RunWith(SpringRunner.class)와 함께 사용해야 하며 JUnit5를 사용할 경우 생략 가능하다고 한다.
특정 클래스를 지정하지 않는 이상 @SpringBootTest는 모든 컴포넌트를 빈으로 등록하기 때문에 시간이 오래 걸린다. 전부 빈으로 등록하지 않고 특정 클래스에 대해서 통합 테스트를 진행하는 방법도 있다.
예를 들어, UserService.class와 UserRepository.class에 대해 통합 테스트를 진행하고 싶은 경우 아래와 같이 설정해주면 된다.
@SpringBootTest(classes = {UserService.class, UserRepository.class})
아직 제대로 된 테스트를 안해봤기에 여러 어노테이션에 대한 감은 잘 잡지 못하겠지만 동작 원리를 알고 실제로 테스트해보면 좀 더 잘 알 수 있을 것이라고 믿는다...!