2. 스프링 부트에서 테스트 코드를 작성해보자!

WOOK JONG KIM·2023년 2월 14일
0

스프링부트 & AWS

목록 보기
2/8
post-thumbnail

옛날에는 테스트 코드를 진행하는 비율이 많지 않았으나 요즘엔 서비스 회사에 취직하기 위해 테스트 코드는 절때 빠질수 없는 요소

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 응답값을 필드별로 검증할 수 있는 메서드로 $를 기준으로 필드명을 명시

profile
Journey for Backend Developer

0개의 댓글

관련 채용 정보