Spring

[스프링 부트와 AWS로 혼자 구현하는 웹 서비스] 2. 테스트코드 작성하기

주니어주니 2023. 8. 1. 00:14

 

 

 

 

1. TDD와 단위 테스트

 

 

  • TDD (Test-Driven Development) : 테스트 주도 개발
    • 테스트 코드를 먼저 작성 

 

  • 단위 테스트 
    • 기능 단위의 테스트 코드 작성 
    • 개발 단계 초기에 문제 발견
    • 테스트 프레임워크 : xUnit 
      • 개발환경(x)에 따라 Unit 테스트를 도와주는 도구 (JUnit, DBUnit 등)

 

 

2. 메인 클래스 

 

package com.example.studyspringbootwebservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StudySpringbootWebserviceApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudySpringbootWebserviceApplication.class, args);
    }

}
  • @SpringBootApplication
    • 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성 자동 설정
    • 이 어노테이션이 있는 위치부터 설정 읽음 -> 프로젝트의 최상단

  • Application.run()
    • 내장 WAS 실행 
    • 톰캣 필요 X 

 

 

3. 컨트롤러 

package com.example.studyspringbootwebservice.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
    • @Controller + @ResponseBody
    • JSON 반환

 

 

4. 테스트

package com.example.studyspringbootwebservice.web;

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;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
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)
    • @RunWith(SpringRunner.class) (JUnit4) -> @ExtendWith(SpringExtension.class) (JUnit5)
    • @Autowired, @MockBean에 해당하는 것들만 application context를 로딩

  • @WebMvcTest(controllers = HelloController.class)
    • 필요한 빈만 등록하여 테스트
    • 웹과 관련된 빈만 주입 (@Controller)
    • MVC 부분 슬라이스 테스트 (보통 컨트롤러 하나만 테스트 하고싶을 때 사용)
    • @Service, @Component, @Repository 등은 사용 X

  • @SpringBootTest 는?
    • 내부에 있는 스프링 빈 모두 등록 -> 느리고 무거워짐
    • 실제 운영 환경과 가장 유사하게 테스트
    • 변경에 자유로움
    • @ExtendWith가 포함되어 있음

  • private MockMvc mvc
    • 웹 API 테스트할 때 사용
    • 이 클래스를 통해 HTTP GET, POST 등에 대한 API 테스트 수행
  • mvc.perform(get("/hello")
    • MockMvc를 통해 /hello 로 GET 요청

  • .andExpect(status().isOk())
    • mvc.perform의 결과 검증
    • HTTP Header의 status가 OK(200)인지 검증 

  • .andExpect(content().string(hello))
    • mvc.perform의 결과 검증
    • 응답 본문의 내용 검증

 

 

 

5. 롬복 적용

 

1) 롬복 Dto

package com.example.studyspringbootwebservice.web.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class HelloResponseDto {

    private final String name;
    private final int amount;

}
  • @RequiredArgsConstructor
    • @RequiredArgsConstructor + final -> 생성자 생성
    • final 없는 필드는 생성자 X

 

2) 롬복 Dto 테스트

package com.example.studyspringbootwebservice.web.dto;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

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);
    }

}

 

 

6. Dto 적용

 

1) 컨트롤러

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name,
                                     @RequestParam("amount") int amount) {
        return new HelloResponseDto(name, amount);
    }
}
  • @RequestParam
    • 외부에서 API로 넘긴 파라미터 가져옴
    • 외부에서 "name"이라는 이름으로 넘긴 파라미터를 메소드의 파라미터 name에 저장

 

2) Dto 적용 테스트 

package com.example.studyspringbootwebservice.web;

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;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = HelloController.class)
class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

...

    @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)))
                .andExpect(jsonPath("$.amount", is(amount)));
    }

}
  • param
    • API 테스트할 때 사용될 요청 파라미터 설정
    • String만 허용 (숫자, 날짜 -> 문자열로 변환해야 함)

  • jsonPath
    • JSON 응답값을 필드별로 검증할 수 있는 메소드 
    • $를 기준으로 필드명 명시