오늘은 테스트 코드에 대해서 끄적여볼까 한다
이 역시 나를 위한 포스팅이다. 까먹기 전에 기록해놔야지
package com.example.study.api;
import com.example.study.dto.BoardDto;
import com.example.study.entity.Board;
import com.example.study.repository.BoardRepository;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BoardApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private BoardRepository boardRepository;
@After
public void clean() {
boardRepository.deleteAll();
}
@Test
public void 게시글_저장() throws Exception{
//given
String title = "테스트 제목";
String content = "테스트 본문";
String author = "테스트 계정";
BoardDto boardDto = BoardDto.builder()
.title(title)
.content(content)
.author(author)
.build();
String url = "http://localhost:"+port+"/api/board";
//when
ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, boardDto, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Board> saved = boardRepository.findAll();
assertThat(saved.get(0).getTitle()).isEqualTo(title);
assertThat(saved.get(0).getContent()).isEqualTo(content);
assertThat(saved.get(0).getAuthor()).isEqualTo(author);
}
@Test
public void 게시글_수정() throws Exception{
//given
Board saved = boardRepository.save(Board.builder()
.title("title")
.content("content")
.author("author")
.build());
Long updatedId = saved.getId();
String updatedTitle = "title1";
String updatedContent = "content1";
BoardDto boardDto = BoardDto.builder()
.title(updatedTitle)
.content(updatedContent)
.build();
String url = "http://localhost:"+port+"/api/board/"+updatedId;
HttpEntity<BoardDto> requestEntity = new HttpEntity<>(boardDto);
//when
ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Board> updated = boardRepository.findAll();
assertThat(updated.get(0).getTitle()).isEqualTo(updatedTitle);
assertThat(updated.get(0).getContent()).isEqualTo(updatedContent);
}
@Test
public void 게시글_삭제() throws Exception{
//given
Board saved = boardRepository.save(Board.builder()
.title("title")
.content("content")
.author("author")
.build());
Long savedId = saved.getId();
String url = "http://localhost:"+port+"/api/board/"+savedId;
HttpEntity<Board> savedEntity = new HttpEntity<>(saved);
//when
ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, savedEntity, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Board> deleted = boardRepository.findAll();
assertThat(deleted).isEmpty();
}
}
전체 코드를 보면 다음과 같다
생성, 수정, 삭제 API에 대한 테스트 코드이다
나도 공부 중에 구글링, 책 보면서 참고한 코드들이라 이게 정답인지는 확실치는 않다
그럼 하나하나 살펴보자
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BoardApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private BoardRepository boardRepository;
@After
public void clean() {
boardRepository.deleteAll();
}
● @RunWith(SpringRunner.class)
● @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
spring-boot-test 의존성을 추가하면 @SpringBootTest라는 어노테이션을 사용할 수 있다
이 어노테이션을 사용하면 테스트에 사용할 ApplicationContext를 쉽게 생성하고 조작할 수 있다
기존 spring-test에서 사용하던 @ContextConfiguration의 발전된 기능이라고 할 수 있다
@SpringBootTest는 매우 다양한 기능들을 제공한다
전체 빈 중 특정 빈을 선택하여 생성한다든지, 특정 빈을 Mock으로 대체한다든지, 테스트에 사용할 프로퍼티 파일을 선택하거나 특정 속성만 추가한다든지, 특정 Configuration을 선택하여 설정할 수도 있다
또한, 주요 기능으로 테스트 웹 환경을 자동으로 설정해주는 기능이 있다
앞에서 언급한 다양한 기능들을 사용하기 위해서 첫 번째로 가장 중요한 것은 @SpringBootTest 기능은 반드시
@RunWith(SpringRunner.class)와 함께 사용해야 한다는 점이다
● WebEnvironment.RANDOM_PORT
@SpringBootTest의 webEnvironment 속성은 테스트 웹 환경을 설정하는 속성이며,
기본값은 SpringBootTest.WebEnvironment.MOCK이다
WebEnvironment.MOCK은 실제 서블릿 컨테이너를 띄우지 않고 서블릿 컨테이너를 mocking 한 것이 실행된다
이 속성 값을 사용할 때는 보통 MockMvc를 주입받아 테스트한다
스프링 부트의 내장 서버를 랜덤 포트로 띄우려면 webEnvironment를 SpringBootTest.WebEnvironment.RANDOM_PORT로 설정하면 된다
이 설정은 실제로 테스트를 위한 서블릿 컨테이너를 띄운다
WebEnvironment.MOCK을 사용할 때와는 달리 TestRestTemplate를 주입받아 테스트한다
● TestRestTemplate
@SpringBootTest와 TestRestTemplate을 사용한다면 편리하게 웹 통합 테스트를 할 수 있다
TestRestTemplate은 이름에서 알 수 있듯이 RestTemplate의 테스트를 위한 버전이다
@SpringBootTest에서 Web Environment설정을 하였다면 TestRestTemplate은 그에 맞춰서 자동으로 설정되어 빈이 생성된다
1. 생성
@Test
public void 게시글_저장() throws Exception{
//given
String title = "테스트 제목";
String content = "테스트 본문";
String author = "테스트 계정";
BoardDto boardDto = BoardDto.builder()
.title(title)
.content(content)
.author(author)
.build();
String url = "http://localhost:"+port+"/api/board";
//when
ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, boardDto, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Board> saved = boardRepository.findAll();
assertThat(saved.get(0).getTitle()).isEqualTo(title);
assertThat(saved.get(0).getContent()).isEqualTo(content);
assertThat(saved.get(0).getAuthor()).isEqualTo(author);
}
생성 API를 테스트하는 테스트 코드이다
코드를 보면 given, when, then 패턴으로 코드를 작성한 것이 보인다
간단하게 말하면 순서대로 각각 준비, 실행, 검증이다
given은 테스트를 위해 준비를 하는 과정이다.
테스트에 사용되는 변수, 입력 값 등을 정의한다
when은 실제로 액션을 하는 테스트를 실행하는 과정이다
보통 하나의 메서드만 수행하는 것이 바람직하기 때문에, when 가장 짧고 심플한 구문이다
마지막으로 then은 검증을 하는 과정이다
예상한 값, 실제 실행을 통해서 나온 값을 검증한다
2. 수정
@Test
public void 게시글_수정() throws Exception{
//given
Board saved = boardRepository.save(Board.builder()
.title("title")
.content("content")
.author("author")
.build());
Long updatedId = saved.getId();
String updatedTitle = "title1";
String updatedContent = "content1";
BoardDto boardDto = BoardDto.builder()
.title(updatedTitle)
.content(updatedContent)
.build();
String url = "http://localhost:"+port+"/api/board/"+updatedId;
HttpEntity<BoardDto> requestEntity = new HttpEntity<>(boardDto);
//when
ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Board> updated = boardRepository.findAll();
assertThat(updated.get(0).getTitle()).isEqualTo(updatedTitle);
assertThat(updated.get(0).getContent()).isEqualTo(updatedContent);
}
다음은 수정 API 테스트이다
과정을 보면 다음과 같다
Repository에 데이터를 저장한다
그리고 Dto를 통해서 값을 변경한다
그 후에 HttpEntity에 데이터를 담고, 해당 메서드를 실행시킨다 (PUT)
마지막으로 responseEntity를 검증하면 끝!
근데 공부를 하던 중에 HttpEntity와 ResponseEntity가 정확히 뭔지 궁금해졌다
지금까지 사용했을 때는 저렇게 써야 하는 게 하나의 정답이라고 생각하며 써왔다
사실 그냥 생각 없이 썼다. 왜 써야하는지, 언제 뭘 써야하는지 모르고..
그래서 이참에 구글링으로 찾아봤다
● HttpEntity
Spring에서는 HttpEntity라는 클래스를 제공한다
이 클래스는 Http 프로토콜을 이용하는 통신의 header와 body 관련 정보를 저장할 수 있게 한다
그리고 이를 상속받은 클래스로 RequestEntity와 ResponseEntity가 존재한다
즉, 통신 메시지 관련 header와 body의 값들을 하나의 객체로 저장하는 것이 HttpEntity클래스 객체이고
Request 부분일 경우 HttpEntity를 상속받은 RequestEntity가,
Response 부분일 경우 HttpEntity를 상속받은 ResponseEntity가 하게 된다
3. 삭제
@Test
public void 게시글_삭제() throws Exception{
//given
Board saved = boardRepository.save(Board.builder()
.title("title")
.content("content")
.author("author")
.build());
Long savedId = saved.getId();
String url = "http://localhost:"+port+"/api/board/"+savedId;
HttpEntity<Board> savedEntity = new HttpEntity<>(saved);
//when
ResponseEntity<Long> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, savedEntity, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Board> deleted = boardRepository.findAll();
assertThat(deleted).isEmpty();
}
마지막으로 삭제 테스트이다
전체적으로 보면 위에 코드들과 큰 차이는 없다
오늘은 테스트 코드에 대해서 공부를 해봤다
아직 모르고 사용하는 기능들이 많은 거 같다
조급해하지 말고 하나하나 천천히 공부해가자!
'Spring Boot' 카테고리의 다른 글
스프링부트 검색, 페이징처리 하기 Pageable - 2 - (0) | 2020.09.23 |
---|---|
스프링부트 검색, 페이징처리 하기 Pageable (0) | 2020.09.22 |
게시판 게시글 정렬 하는법 (0) | 2020.09.11 |
[스프링부트 JPA] 연관관계 매핑 (0) | 2020.09.06 |
자바 빌더 패턴에 대해서 + @Builder (1) | 2020.08.25 |