클러치 프로젝트에서 테스트를 하지 않고 서비스를 구현하였는데 리팩토링에 들어가면서 단위 테스트를 도입해보려고 한다
TDD는 테스트가 주도하는 개발이고 테스트 코드를 먼저 작성하는 것부터 시작한다
단위테스트는 TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 이야기한다. TDD와 달리 테스트 코드를 꼭 먼저 작성 해야하는 것이 아니라 순수하게 테스트 코드만 작성하는 것을 이야기한다.
테스트 코드를 작성하지 않았을 때
1. 코드를 작성하고
2. 프로그램을 실행한 뒤
3. Postman과 같은 API 테스트 도구로 HTTP요청하고
4. 요청 결과를 System.out.println()으로 눈으로 검증하고
5. 결과가 다르면 다시 프로그램을 중지하고 코드를 수정한다
이 방법은 현재 내가 개발을 하면서 택하고 있는 방법이고 항상 번거롭다고 느꼈었다. 이번 학습을 통해 테스트 코드를 작성하면서 효율적인 개발 환경을 구축해보고 싶다.
일반적으로 패키지 명은 웹사이트 주소의 역순
@SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 빈 읽기와 생성을 모두 자동으로 설정. 특히 이 어노테이션이 있는 위치부터 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야 한다
내장 WAS는 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 이야기한다 -> 항상 서버에 톰캣을 설치할 필요 없이 Jar파일로 실행하면 된다
스프링 부트에서는 내장 WAS를 사용하는 것을 권장하고 있다. 이유는 "언제 어디서나 같은 환경에서 스프링 부트를 배포"할 수 있기 때문이다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
@RestController
- 컨트롤러에서 JSON을 반환하는 컨트롤러를 만들어 준다
- 예전에는 @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해준다고 생각하면 된다
위 코드가 제대로 작동하는지 테스트를 해보았다.
일반적으로 테스트 클래스는 대상 클래스 이름에 Test를 붙인다
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
public class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void hello가_리턴된다() throws Exception{
String hello = "hello";
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string(hello));
}
}
@ExtendWith(SpringExtension.class): 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 한다
WebMvcTest:여러 스프링 테스트 어노테이션 중 Web에 집중할 수 있는 어노테이션이다.
@Autowired:스프링이 관리하는 Bean을 주입받는다
private MockMvc mvc: 웹 API를 테스트 할 때 사용하고 Spring MVC 테스트의 시작점이다.
mvc.perform(get("/hello")): MockMVC를 통ㅇ해 /hello주소로 HTTP GET요청을 한다
.andExpect(status().isOk()):mvc.perform의 결과를 검증한다. 응답 본문의 내용을 검증한다. Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증한다
Test해보면 이런 화면을 볼 수 있다
Test가 성공했으니 Build를 해보면 localhost:8080에서 성공적으로 Build가 된것을 볼 수 있다
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
private final String name;
private final int amount;
}
@Getter: 선언된 모든 필드의 get메서드를 생성해준다
@RequiredArgsConstructor:선언된 모든 final 필드가 포함된 생성자를 생성해준다. final이 없는 필드는 생성자에 포함되지 않는다.
public class HelloResponseDtoTest {
@Test
public void 롬복_기능_테스트(){
String name = "test";
int amount = 1000;
//when
HelloResponseDto dto = new HelloResponseDto(name,amount);
//then
org.assertj.core.api.Assertions.assertThat(dto.getName()).isEqualTo(name);
org.assertj.core.api.Assertions.assertThat(dto.getAmount()).isEqualTo(amount);
}
}
assertThat: assertj라는 테스트ㅡ 검증 라이브러리의 검증 메소드이다. 검증하고 싶은 대상을 메소드 인자로 받는다. 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용할 수 있다.
isEqualTo: assertj의 동등 비교 메소드이다. assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공
여기서 Junit의 기본 assertThat을 안쓰는 이유는 assertj는 coreMatchers와 달리 추가적으로 라이브러리가 필요하지 않고 자동완성이 좀 더 확실하게 지원된다
@Test
public void helloDto가_리턴된다() throws Exception{
String name ="hello";
int amount = 1000;
mvc.perform(get("/hello/dto")
.param("name",name)
.param("amount",String.valueOf(amount)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name",is(name)))
.andExp![](https://velog.velcdn.com/images/almondbreez0_3/post/f1f363b7-dec5-4fb7-bc37-6a6521a3ad3c/image.jpg)
![](https://velog.velcdn.com/images/almondbreez0_3/post/b830c198-77d5-4fee-a8d0-947ff5cdab1f/image.jpg)
![](https://velog.velcdn.com/images/almondbreez0_3/post/6ca7944c-8a4e-404c-a461-10860860d0d6/image.jpg)
ect(jsonPath("$.amount",is(amount)));
}
여기까지 생성한 test함수 모두 Test통과함을 볼 수 있다