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

세이라·2023년 7월 21일
0

스터디를 통해 스프링부트와 AWS로 혼자 구현하는 웹 서비스(저자 이동욱) 서적을 공부하는 중입니다.

공부/실습한 내용을 정리한 포스팅입니다.
책에 모르는 부분이 있으면 구글링하거나 챗gpt에 물어봐서 보충하였습니다.
(아직 초보라 모르는 부분이 많아 이것저것 다 적었습니다.)

참고한 사이트 출처는 포스팅 맨 하단에 적었습니다.

테스트 코드

  • 견고한 서비스를 만들고 싶은 개발자나 팀에선 TDD를 하거나 최소한 테스트 코드는 작성.
  • 최근 들어 대부분의 서비스 회사가 테스트 코드에 관해 요구.

TDD(Test Driven Development)

  • 테스트가 주도하는 개발.
  • 레드 그린 사이클
    -항상 실패하는 테스트를 먼저 작성(Red)
    -테스트가 통과하는 프로덕션 코드를 작성(Green)
    -테스트가 통과하면 프로덕션 코드를 리팩토링(Refactor)
    ※ 프로덕션 코드 : 프로그램 구현을 담당하는 부분으로 사용자가 실제로 사용하는 소스 코드

단위 테스트

  • TDD의 첫 번째 단계인 기능 단위의 테스트 코드 작성.
  • TDD와 단위테스트는 서로 다른 것. TDD의 단계 중 첫 단계일 뿐.
    : TDD와 달리 단위 테스트는 테스트 코드를 꼭 먼저 작성X 리팩토링도 포함되지 않음. 순수하게 테스트 코드만 작성하는 것.

단위 테스트 이점

개발자라면 반드시 익혀야 할 기술이자 습관

위키피디아

  • 개발 단계 초기에 문제 발견하게 도와줌.
  • 나중에 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지 확인 가능.
  • 기능에 대한 불확실성 감소.
  • 시스템에 대한 실제 문서를 제공. 즉, 단위 테스트 자체가 문서로 사용될 수 있음.

개발 경험

  • 테스트 시 자동검증 / WAS 시작 필요X
    (단위 테스트를 통하지 않으면 톰캣 재시작을 반복하며 눈으로 검증해야 함.)
  • 개발자가 만든 기능 안전하게 보호
    - 규모가 큰 서비스 개발 시 하나의 기능을 추가할 때 기존의 기능에 문제가 생김. 그리고 기능 추가 시마다 서비스의 모든 기능을 테스트하면 너무나 많은 자원이 듦.
    - 단위 테스트 진행 시 기존 기능이 잘 작동되는 것을 보장하며 테스트 가능. → 기존 기능 모두 테스트 코드로 구현해놓았다면 테스트 코드 수행만 하면 문제 조기 발견 가능.

테스트 프레임워크

  • 테스트 코드 작성을 도와주는 프레임워크
  • xUnit(개발환경(x)에 따라 Unit 테스트를 도와주는 도구)
  • 예) JUnit(Java), DBUnit(DB), CppUnit(C++)

Hello Controller 테스트 코드 작성하기

1. 패키지 및 프로젝트 메인 클래스, 컨트롤러 생성

과정

  1. src>main>java에 웹사이트 주소 역순 이름을 가진 패키지 생성
  2. Application 이름을 가진 클래스 생성
  3. 아래와 같이 코드 작성
package com.webservice.springboot.springboot_board;

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);
    }
}
  1. 1번에서 만든 패키지 밑에 web이라는 패키지 생성
    (Controller를 모두 web 패키지에 생성)
  2. web 패키지에 HelloController class 생성
  3. 아래의 코드 작성
package com.webservice.springboot.springboot_board.web;

import com.webservice.springboot.springboot_board.web.dto.HelloResponseDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

    @GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amonut){
        return new HelloResponseDto(name,amonut);
    }
}

설명

메인 클래스

  • @SpringBootApplication
    : 스프링부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정
    - @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단 위치에 있어야 함.
  • main 메서드의 SpringApplication.run
    : 내장 WAS(Web Application Server) 실행

내장 WAS

  • 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS 실행.
  • 서버에 톰캣을 설치할 필요 없게 되고, 스프링부트로 만들어진 Jar 파일로 실행하면 됨.
    ※ Jar파일 : 실행 가능한 Java 패키징 파일
  • 스프링 부트는 내장 WAS를 사용하는 것을 권장.
    - 언제 어디서나 같은 환경에서 스프링 부트 배포 가능.
    - 외장 WAS를 쓰면 모든 서버는 WAS의 종류와 버전, 설정을 일치 시켜야 함. (많은 서버 WAS 버전 올린다 가정하면, 실수가 발생할 수 있으며 시간이 많이 걸림.) → 내장 WAS 사용 시 문제 해결

RestController

  • 컨트롤러를 JSON으로 반환하는 컨트롤러로 변환
  • 예전에는 @Controller@ResponseBody를 각 메소드마다 선언했던 것을 한 번에 사용 가능하게 해준다고 생각하면 됨.
    (메소드에서 @ResponseBody 어노테이션을 사용하면 Spring은 반환 값을 변환하여 HTTP Response 에 자동으로 씀)
@GetMapping
  • HTTP Method인 Get 요청을 받을 수 있는 API를 만들어 줌.
  • 예전에는 @RequestMapping(method = RequestMethod.GET)으로 사용 됨.

2. HelloController 테스트 코드로 검증

과정

  1. src>test>java 디렉토리에 앞서 생성했던 패키지 그대로 생성
  2. web패키지 안에 HelloControllerTest class 생성
  3. 아래와 같이 작성
package com.webservice.springboot.springboot_board;

import com.webservice.springboot.springboot_board.web.HelloController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
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.*;

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

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

설명

@RunWith(SpringRunner.class)

  • 테스트를 진행할 때 JUnit 내장된 실행자 외에 다른 실행자를 실행
  • SpringRunner라는 스프링 실행자를 사용함으로써 스프링부트 테스트와 JUnit 사이에 연결자 역할을 함.
  • JUnit4에서는 보통 스프링 부트 테스트 시 @RunWith(SpringRunner.class)@SpringBootTest를 통해 테스트 실행. → @RunWith(SpringRunner.class)은 JUnit 테스트를 Spring과 통합하여 실행할 수 있도록 해줌. JUnit 테스트가 SpringRunner를 사용하여 Spring과 연동되어 실행.@RunWith(SpringRunner.class) 자체로는 애플리케이션 컨텍스트를 로드하지는 않음.(@RunWith(SpringRunner.class)@SpringBootApplication이 지정된 클래스를 실행하여 Spring Container를 활용할 수 있게 되지만, 이것만으로는 테스트에서 활용하기는 어려움. 테스트 환경에서는 Application Context 로드X)@SpringBootTest은 Spring Boot Application Context를 로드하여 테스트에 필요한 Bean들과 설정 정보를 사용할 수 있도록 해줌.
  • (JUnit5부턴 해당X) @SpringBootTest를 사용하면 application context를 전부 로딩해서 자칫 잘못하면 무거운 프로젝트가 되어버림. 특정 테스트 케이스에서 모든 Application Context를 로드할 필요가 없는 경우, JUnit4에서 지원하는 @RunWith(SpringRunner.class)를 사용하면서 @SpringBootTest 대신에@WebMvcTest와 같은 어노테이션을 사용하여 더 가볍게 Context 로드가 가능. @WebMvcTest의 경우, 웹 계층에 집중하여 필요한 Bean들만 로드하여 더 빠르게 테스트 실행 가능.

※ 상세 설명

  • Spring에서 JUnit 실행 시 SpringRunner.class라는 확장된 클래스를 실행. (JUnit에서 기본적으로 제공하는 Runner를 SpringBoot 테스트 환경에 맞게 확장된 Runner)

※ 꼭 알아야 할 참고사항

  • SpringBoot 2.1 버전부터는 Junit 버전이 5.x 로 변경되었고 @RunWith(SpringRunner.class) 역할을 하는 어노테이션이 @ExtendWith(SpringExtension.class) 로 변경됨.
  • 해당 어노테이션이 @SpringBootTest 어노테이션에 내장되어 더이상 이를 별도로 사용X

@WebMvcTest

  • 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션.
    - Application Context를 완전하게 Start 시키지 않고 web layer를 테스트 하고 싶을 때 사용. @SpringBootTest의 경우, 모든 Bean을 로드하기 때문에 테스트 구동시간이 오래 걸리므로, Controller만 테스트 시 @WebMvcTest 사용.
  • 선언할 경우 @Controller, @ControllerAdvice 등 사용 가능하지만, @Service, @Component, @Repository 등은 사용할 수 있없음.
  • 이 코드에선 컨트롤러만 사용하기 때문에 선언.

@Autowired

  • 스프링이 관리하는 Bean을 주입 받음.

MockMvc

  • 애플리케이션 배포 없이 웹 API/스프링 MVC 동작을 테스트할 수 있는 라이브러리

mvc.perform(get("/hello")

  • MockMvc를 통해 해당 주소로 GET 요청.
  • 체이닝 지원으로 여러 검증 기능을 이어서 선언 가능.

.andExpect(status().isOk())

  • mvc.perform 결과 검증(HTTP Header의 상태 코드 검증)

.andExpect(content().string(hello))

  • mvc.perform 결과 검증(응답 본문 내용 검증)

★ 작성 중 Spring 모르는 개념은 블로그 포스팅으로 따로 정리
Spring 관련 개념 정리(Spring Boot, MVC 패턴, IoC, DI, Application Context, Bean, BeanFactory, REST API)

출처

스프링부트와 AWS로 혼자 구현하는 웹 서비스(저자 이동욱)
Production Code & Test Code & JUnit
[Spring] Restful한 스프링 @ResponseBody vs @RestController
@RunWith(SpringRunner.class)이란?
@RunWith(SpringRunner.class)
[Spring Boot] Controller 단위 테스트 (@WebMvcTest, MockMvc)

0개의 댓글