TDD(Test Driven Development) 테스트-주도 개발
테스트-주도 개발의 기본 흐름은 실패하는 테스트를 작성하고, 테스트를 통과하는 가장 간단한 코드를 작성한 후, 리팩터링을 통해 중복을 제거하는 것이다.
단위 테스트 코드를 작성함으로써 얻는 이점
Application
클래스를 생성하자.
package com.jojoldu.book.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Application
클래스는 앞으로 만들 프로젝트의 메인클래스가 된다.
@SpringBootApplication
으로 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정해준다. 이 클래스는 현재 자신의 클래스가 있는 위치부터 설정을 읽어가기 때문에 프로젝트의 최상단에 위치하도록 하는게 편하다.
main
메서드에서 실행하는 SpringApplication.run
으로 인해 내장 WAS(Web Application Server, 웹 애플리케이션 서버)를 실행한다.
내장 WAS를 통해 애플리케이션을 실행하면 서버에 톰캣을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar파일로 실행하면 된다.
HelloController
클래스를 생성하자.
package com.jojoldu.book.springboot.web;
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
@ResponsBody
와 @Controller
를 합친 어노테이션이라 생각하면된다.@GetMapping
HelloControllerTest
클래스를 생성하자.
테스트 클래스를 직접 생성하는 방법도 있고 커맨드+쉬프트+T(⌘+⇧+T)
를 통해 생성하는 방법도 있다.
package com.jojoldu.book.springboot.web;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
void hello가_리턴된다() throws Exception {
String hello = "hello";
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string(hello));
}
}
*참고: JUnit5를 통한 테스트 코드 작성이라 책과는 약간 다르다.
@WebMvcTest
@controller
, @ControllerAdvice
등을 사용할 수 있다.@Service
, @Component
, @Repository
등은 사용할 수 없다.@ExtendWith(SpringExtension.class)
Autowired
private MockMvc mvc
mvc.perform(get("/hello"))
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
andExpect(status().isOk())
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
andExpect(contetn().string(hello))
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
롬복은 자바 개발 시 자주 사용하는 코드 Getter, Setter, 기본생성자, toString 등을 어노테이션으로 자동 생성해 준다.
build.gradle의 dependencies
를 변경하자.
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation('org.projectlombok:lombok')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
annotationProcessor 'org.projectlombok:lombok'
}
HelloResponseDto 클래스를 생성하자.
package com.jojoldu.book.springboot.web.dto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
private final String name;
private final int amount;
}
@Getter
@RequiredArgsConstructor
HelloResponseDtoTeset를 생성하자
package com.jojoldu.book.springboot.web.dto;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class HelloResponseDtoTest {
@Test
void 롬복_기능_테스트() {
//given
String name = "test";
int amount = 1000;
//when
HelloResponseDto dto = new HelloResponseDto(name, amount);
//then
assertThat(dto.getName()).isEqualTo(name);
assertThat(dto.getAmount()).isEqualTo(amount);
}
}
assertThat
HelloController에 HelloResponseDto를 사용하도록 코드를 추가하자.
@GetMapping("/hello/dto")
public HelloResponseDto helloDto(
@RequestParam("name") String name, @RequestParam("amount") int amount) {
return new HelloResponseDto(name, amount);
}
@RequestParam
HelloControllerTest에 테스트를 추가하자
package com.jojoldu.book.springboot.web;
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
void hello가_리턴된다() throws Exception {
...
}
@Test
void helloDto가_리턴된다() throws Exception {
String name = "hello";
int amount = 1000;
mvc.perform(get("/hello/dto")
.param("name", name)
.param("amount", String.valueOf(amount)))
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.amount", is(amount)));
}
}
param
jsonPath
static import 참고
is
-> import static org.hamcrest.Matchers.is;
jsonPath
-> import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
참고
- 테스트 코드에서 static import를 통해 줄여진 메서드명이 많다.
- 빨간줄이 뜬다고 당황하지말고 import해주자.
- build.gradle에 dependencies에서
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
를 꼭 제거해주자! 그렇지 않으면 테스트코드 실행 시 에러가 발생한다!