스프링 부트
통합 테스트 VS 단위 테스트
newwisdom
2021. 8. 6. 12:13
반응형
2021-04-16글
통합 테스트
실제 운영 환경에서 사용될 클래스들을 통합하여 테스트한다.
스프링 프레임워크에서 전체적으로 플로우가 제대로 동작하는지 검증하기 위해 사용한다.
여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트이다.
장점
- 스프링 부트 컨테이너를 띄워 테스트하기 때문에 운영환경과 가장 유사한 테스트가 가능하다.
- 전체적인 Flow를 쉽게 테스트 할 수 있다.
단점
- 애플리케이션의 설정, 모든 Bean을 로드하기 때문에 시간이 오래걸리고 무겁다.
- 테스트 단위가 커 디버깅이 어렵다.
@SpringBootTest
- 통합 테스트를 제공하는 기본적인 스프링 부트 테스트 어노테이션.
@SpringBootTest
@ActiveProfiles("test")
@Transactional
class ChessServiceImplTest {
// ...
}
@SpringBootTest 의 파라미터들
- value: 테스트가 실행되기 전에 적용할 프로퍼티 주입.(기존의 프로퍼티 오버라이드)
- properties : 테스트가 실행되기 전에 {key=value} 형식으로 프로퍼티 추가.
- classes : ApplicationContext에 로드할 클래스 지정. (지정하지 않으면 @SpringBootConfiguration을 찾아서 로드)
- webEnvironment : 어플리케이션이 실행될 때의 웹 환경을 설정. (기본값은 Mock 서블릿을 로드하여 구동)
➕ webEnvironment
- MOCK :
ServletContainer
를 테스트용으로 띄우지않고 서블릿을 mocking 한 것이 동작한다. (내장 톰캣이 구동되지 않는다.)
MockMvc는 브라우저에서 요청과 응답을 의미하는 객체로서 Controller 테스테 사용을 용이하게 해주는 라이브러리 - RANDOM_PORT : 임의의 Port Listener. EmbeddedWebApplicationContext를 로드하며 실제 서블릿 환경을 구성
@ActiveProfiles
원하는 프로파일 환경 값 설정이 가능하다. (프로파일 전략)
@Transactional
테스트 완료 후 자동으로 Rollback 처리가 된다.
하지만 WebEnvironment.RANDOM_PORT, DEFINED_PORT
를 사용하면 실제 테스트 서버는 별도의 스레드에서 테스트를 수행하기 때문에 트랜잭션이 롤백되지 않는다. (왜?)
WebEnvironment.RANDOM_PORT, DEFINED_PORT
실제 웹 실행 환경을 띄운다.
단위 테스트
장점
- WebApplication 관련된 Bean들만 등록하기 때문에 통합 테스트보다 빠르다.
- 통합 테스트를 진행하기 어려운 테스트를 진행 가능하다.
단점
- 요청부터 응답까지 모든 테스트를 Mock 기반으로 테스트하기 때문에 실제 환경에서는 제대로 동작하지 않을 수 있다.
@WebMvcTest
- MVC를 위한 테스트로, 웹 상에서 요청과 응답에 대한 테스트.
- MVC 관련된 설정인
@Controller, @ControllerAdvice, @JsonCompoent와 Filter, WebMvcConfiguer, HandlerMetohdAgumentResolver
만 빈으로 등록된다. (디스패쳐 서블릿에서 사용되는 아이들만 주입받는다.) - 때문에 Service, Repository 와 같은 웹 계층 아래 빈들은 등록되지 않아 의존성도 끊긴다.
- 테스트에 사용하는 의존성이 있다면 @MockBean으로 만들어 사용한다.
@WebMvcTest(ChessController.class)
class ChessControllerTest {
private final static long CHESS_GAME_TEST_ID = 0;
@Autowired
MockMvc mockMvc;
@MockBean
ChessService chessService;
@Test
@DisplayName("게임 리스트 조회 테스트")
void getGames() throws Exception {
List<ChessGameManager> chessGameManagers = new ArrayList<>();
ChessGameManager chessGameManager = ChessGameManagerFactory.createRunningGame(CHESS_GAME_TEST_ID);
chessGameManagers.add(chessGameManager);
given(chessService.findRunningGames()).willReturn(new ChessGameManagerBundle(chessGameManagers));
mockMvc.perform(get("/games"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.runningGames." + CHESS_GAME_TEST_ID)
.value("WHITE"));
}
@Test
@DisplayName("새로운 게임 시작 테스트")
void gameStart() throws Exception {
ChessGameManager chessGameManager = ChessGameManagerFactory.createRunningGame(CHESS_GAME_TEST_ID);
given(chessService.start()).willReturn(chessGameManager);
mockMvc.perform(get("/game/start"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(CHESS_GAME_TEST_ID))
.andExpect(jsonPath("$.color").value("WHITE"))
.andExpect(jsonPath("$.piecesAndPositions.size()").value(32));
}
@Test
@DisplayName("게임 점수 조회 테스트")
void getScore() throws Exception {
ChessGameStatistics chessGameStatistics = ChessGameStatistics.createNotStartGameResult();
given(chessService.getStatistics(CHESS_GAME_TEST_ID)).willReturn(chessGameStatistics);
mockMvc.perform(get("/game/" + CHESS_GAME_TEST_ID + "/score"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.matchResult").value("무승부"))
.andExpect(jsonPath("$.colorsScore.size()").value(2));
}
@Test
@DisplayName("해당 게임 로딩 테스트")
void loadGame() throws Exception {
ChessGameManager chessGameManager = ChessGameManagerFactory.createRunningGame(CHESS_GAME_TEST_ID);
given(chessService.findById(CHESS_GAME_TEST_ID)).willReturn(chessGameManager);
mockMvc.perform(get("/game/" + CHESS_GAME_TEST_ID + "/load"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(CHESS_GAME_TEST_ID))
.andExpect(jsonPath("$.color").value("WHITE"))
.andExpect(jsonPath("$.piecesAndPositions.size()").value(32));
}
@Test
void movePiece() throws Exception {
Gson gson = new Gson();
String content = gson.toJson(new MoveRequestDto("a2", "a3"));
given(chessService.isEnd(CHESS_GAME_TEST_ID)).willReturn(false);
given(chessService.nextColor(CHESS_GAME_TEST_ID)).willReturn(Color.BLACK);
mockMvc.perform(MockMvcRequestBuilders
.post("/game/" + CHESS_GAME_TEST_ID + "/move")
.content(content).header("Content-Type", "application/json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.end").value(false))
.andExpect(jsonPath("$.nextColor").value("BLACK"));
}
}
given - willReturn
: 특정행위에 대한 반환 값을 지정하여 실제 객체처럼 동작하게 한다.
@JdbcTest
- JDBC 기반 구성 요소에만 초점을 맞춘 JDBC 테스트 어노테이션
- 테스트를 위한
JdbcTemplate
이 생성된다. - 기본적으로 트랜잭션이 이루어진다.
- in-memory database가 설정된다.
@JdbcTest
class JdbcTemplateChessDaoTest {
private static final long DEFAULT_CHESS_GAME_ID = 1;
ChessGame chessGame;
ChessGameManager sampleGame;
private final JdbcTemplate jdbcTemplate;
private final JdbcTemplateChessDao jdbcTemplateChessDao;
@Autowired
public JdbcTemplateChessDaoTest(JdbcTemplate jdbcTemplate, @Qualifier("dataSource") DataSource dataSource) {
this.jdbcTemplate = jdbcTemplate;
this.jdbcTemplateChessDao = new JdbcTemplateChessDao(jdbcTemplate, dataSource);
}
@BeforeEach
void beforeEach() {
String sample = "RKBQKBKRPPPPPPPP................................pppppppprkbqkbkr"; // move a2 a3 한 번 진행
chessGame = new ChessGame(DEFAULT_CHESS_GAME_ID, WHITE, true, sample);
sampleGame = ChessGameManagerFactory.loadingGame(chessGame);
}
@Test
@DisplayName("체스 게임을 저장한다.")
void save() {
long newId = jdbcTemplateChessDao.save(chessGame);
assertThat(newId).isEqualTo(2);
}
@Test
@DisplayName("id로 체스 게임을 찾는다.")
void findById() {
assertThat(jdbcTemplateChessDao.findById(DEFAULT_CHESS_GAME_ID).isPresent()).isTrue();
}
@Test
@DisplayName("체스 게임 정보를 업데이트한다.")
void update() {
sampleGame.move(Position.of("a2"), Position.of("a4"));
jdbcTemplateChessDao.update(new ChessGame(sampleGame));
ChessGame expectedChessGame = jdbcTemplateChessDao.findById(DEFAULT_CHESS_GAME_ID).get();
ChessGameManager expectedChessGameManager = ChessGameManagerFactory.loadingGame(expectedChessGame);
Square a4 = expectedChessGameManager.getBoard().findByPosition(Position.of("a4"));
assertThat(a4.getPiece().getClass()).isEqualTo(Pawn.class);
assertThat(a4.getPiece().getColor()).isEqualTo(WHITE);
}
@Test
void findAllOnRunning() {
List<ChessGame> allOnRunning = jdbcTemplateChessDao.findAllOnRunning();
assertThat(allOnRunning.size()).isEqualTo(1);
}
@Test
void delete() {
//when
jdbcTemplateChessDao.delete(DEFAULT_CHESS_GAME_ID);
//then
try {
jdbcTemplateChessDao.findById(DEFAULT_CHESS_GAME_ID);
} catch (Exception e) {
}
}
}
참고
반응형