옛날에는 테스트 코드를 진행하는 비율이 많지 않았으나 요즘엔 서비스 회사에 취직하기 위해 테스트 코드는 절때 빠질수 없는 요소
ex) 빌드/테스트/배포 자동화 경험이 있으신 분
TDD와 단위 테스트(Unit Test)는 다른 이야기
위 그림은 TDD에 대해 잘 보여줌(TDD는 테스트 코드를 먼저 작성하는 것 부터 시작)
항상 실패하는 테스트를 먼저 작성하고(RED)
테스트가 통과하는 프로덕션 코드를 작성하고(Green)
테스트가 통과하면 프로덕션 코드를 리팩토링함(Refactor)
반면 단위테스트는 TDD의 첫번째 단계인 기능단위의 테스트 코드를 작성하는 것
을 의미
-> TDD와 달리 테스트 코드를 꼭 먼저 작성하는 것도 아니고, 리팩토링도 포함 X
-> 순수하게 테스트 코드만 작성하는 것 의미
테스트 코드의 가장 큰 장점은 빠른 피드백
-> 단위 테스트를 진행하기 전해는 1. 코드를 작성 2. 프로그램(Tomcat)을 실행 3. PostMan과 같은 API 테스트 도구로 HTTP 요청 4. 요청 결과를 println을 통해 눈으로 검증 후 5. 결과가 다르면 다시 프로그램(Tomcat)을 중지하고 코드를 수정
여기서 2~5를 코드를 수정할때마다 반복해야 함
-> 톰캣을 재시작 하는데 시간이 많이 소요(반복 횟수 또한 많음)
-> 또한 println을 통한 눈으로 검증(테스트 코드 작성시 눈으로 검증하지 않는 자동검증이 가능)
또한 개발자가 만든 기능을 안전하게 보호해줌
-> 예를 들어 B 기능이 잘 되어 오픈했더니 기존에 잘되던 A 기능에 문제가 생김(큰 서비스에선 빈번히 발생)
-> 이 때 하나의 기능을 추가할때 마다 너무나 많은 자원이 들기 때문에 서비스의 모든 기능을 테스트 하긴 힘듬
-> 이럴 때 기존의 기능이 잘 작동되는 것을 보장해주는것이 테스트 코드
-> 예를들어 A라는 기존 기능에 기본 기능을 비롯해 여러 경우를 모두 테스트 코드로 구현해 놓았다면 테스트 코드를 수행만 하면 문제를 조기에 찾을 수 있음
대표적으로 테스트 코드 작성을 도와주는 프레임워크는 xUnit
-> 밑의 예시코드는 JUnit5 보다는 4에 초점을 두었단 점 유의
일반적으로 패키지명은 웹 사이트 주소의 역순으로 함
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
application 클래스 작성(앞으로 만들 프로젝트의 메인 클래스)
@SpringBootApplication
으로 인해 스프링 부트가 제공하는 다양한 기능들을 자동으로 설정하고, 필요한 Bean들을 자동으로 설정
-> @Configuration, @EnableAutoConfiguration, @ComponentScan 등을 이 어노테이션 하나로 대체 가능
-> 이 어노테이션이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야만 함
main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS
를 실행
-> 내장 WAS란 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것
-> 이로 인해 항상 서버에 톰캣을 설치할 필요가 없게되고, 스프링 부트로 만들어진 Jar파일로 실행하면 됨
-> 또한 외장 WAS를 쓴다면 모든 서버는 WAS의 종류와 버전,설정을 일치 시켜야 함
-> 하지만 내장 WAS 사용시에는 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있음
(권장 사항)
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
}
@RestController
는 컨트롤러를 JSON으로 반환하는 컨트롤러로 만들어 줌
@GetMapping
인 HTTP Method의 Get 요청을 받을 수 있는 API를 만들어 줌
이를 테스트하기 위해 HelloControllerTest.java 파일 생성
JUnit 4 기준
@RunWith(SpringRunner.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));
}
}
@RunWith
는 JUnit 프레임워크에서 실행할 테스트 클래스의 실행자를 지정하는 애노테이션
테스트 진행 시 JUnit에 내장된 실행자 외에 다른 실행자를 실행시키며 여기서는 SpringRunner 사용
-> 즉 스프링 부트 테스트와 JUnit 사이에 연결자 역할
@WebMvcTest : 여러 스프링 테스트 어노테이션 중, Web(Spring Mvc)에 집중할 수 잇는 어노테이션으로, 선언할 시에 @Controller, @ControllerAdvice는 사용할 수 있지만, @Service, Component, Repository는 사용 불가
-> 컨트롤러 지정을 바탕으로, HelloController 클래스에 대한 MockMvc 인스턴스를 생성하여 요청 및 응답을 테스트
@Autowired : 스프링이 관리하는 빈 주입
private MockMvc mvc : 웹 API 테스트 할 때 사용, 스프링 MVC의 시작점으로 이 클래스를 통해 GET,POST 등에 대한 테스트 가능
mvc.perform(get("/hello") : MockMvc를 통해 Get 요청, 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 사용 가능
andExpect를 통해 perform 결과를 검증
테스트 코드 작성 시, 절대 수동으로 검증한 후 테스트 코드 작성 X
-> 테스트 코드로 먼저 검증 후, 정말 못 믿겠다 싶을 때 프로젝트 실행해 확인한다는 점 명심하기!
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
private final String name;
private final int amount;
}
RequiredArgsContstructor를 통해 final 필드가 포함된 생성자 생성(final이 아닌 경우 생성자에 포함 X)
5.0이상부터는 의존성 추가 방법 변경됨!(이것땜에 오류떴었음)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
test 코드
import static org.assertj.core.api.Assertions.assertThat;
public class HelloResponseDtoTest {
@Test
public 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은 검증하고 싶은 대상을 메소드 인자로 받음
-> 이때 JUnit의 기본 assertThat이 아닌 assertj의 asserThat을 사용
-> assertj가 자동완성이 좀 더 확실히 지원되고, 추가적인 라이브러리가 필요 X(Junit의 assertThat을 쓰면 is()와 같이 CoreMatchers 라이브러리가 필요)
Hello Controller에 메서드 추가
@GetMapping("/hello/dto")
public HelloResponseDto helloDto(
@RequestParam("name") String name,
@RequestParam("amount") int amount)
{
return new HelloResponseDto(name, amount);
}
// 테스트 코드
@Test
public void helloDto가_리턴된다() throws Exception{
String name = "hello";
int amount = 100;
mvc.perform(get("/hello/dto")
.param("name", name)
.param("amount", String.valueOf(amount)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is(name)))
.andExpect(jsonPath("$.amount", is(amount)));
}
여기서 name과 amount는 API를 호출하는 곳에서 넘겨준 값
@RequestParam
: 외부에서 API로 넘긴 파라미터를 가져오는 애노테이션param
: API를 테스트할 때 사용될 요청 파라미터 설정jsonPath
: JSON 응답값을 필드별로 검증
할 수 있는 메서드로 $를 기준으로 필드명을 명시