Develope Me!

[SpringBoot] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - 테스트코드 본문

Java/SpringBoot

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

코잘알지망생 2022. 2. 16. 17:46

개발환경 세팅을 마쳤다면 이제 본격적으로 테스트코드를 작성해보고자 한다.

개인적으로 팀 프로젝트를 하면서 Junit4를 사용해서 DAO와 Service를 단위 테스트를 해본 경험이 있다. 

Junit을 사용하기 이전엔 코드 작성하고 톰캣을 실행해서 sysout을 통해 결과를 확인하는 과정을 반복했었다.

이렇게 서버를 재시작하다보면 시간이 꽤 걸렸는데 테스트 코드를 작성해두면 이런 불편함을 줄일 수 있었다.

 

 

테스트 코드

 

테스트 코드는 기능 단위의 Unit Test만 있는 게 아니다.

좀 더 넓은 범위에서 TDD 테스트가 있다.

 

 

1) TDD

 

TDD (Test Driven Development, 테스트 주도 개발)는 테스트가 개발을 이끌어 간다라는 의미를 담고 있다.

레드 그린 사이클

TDD의 과정을 살펴보자면 

[RED] 항상 실패하는 테스트 먼저 작성하고

[GREEN] 이후 테스트가 통과하는 프로덕션 코드 작성한다.

[REFACTOR] 테스트를 통과했다면 프로덕션 코드를 코드 구조를 재조정하는 리팩토링을 한다. 

 

이렇게 테스트 코드를 작성하는 이유

1. 개발 단계 초기에 문제를 발견하게 도와주고

2. 개발자가 나중에 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 동작하는지 확인 가능하며

3. 기능에 대한 불확실성 감소시킬 수 있고

4. 시스템에 대한 실제 문서를 제공하기 때문이다. 

 

 

2) 단위 테스트(UnitTest)

 

단위 테스트는 '기능 단위의 테스트 코드 작성'을 의미하며 순수하게 테스트 코드를 작성하는 것이다.

팀 프로젝트에서 사용했던 Junit Test를 복습하는 의미에서 대표적인 JAVA 테스트 코드 프레임워크인 Junit를 사용해보고자 한다.

 

일단 스프링 부트의 자동 설정 및 스프링 Bean 읽기/생성을 담당하는 @SpirngBootApplication 어노테이션을 프로젝트 상단에 위치 시켰다. 

메인 메소드에 SpringApplication.run으로 외부에 WAS(ex.Tomcat)을 두지 않고 내장 되어 있는 WAS를 실행했다.

근데 여기서 궁금한 점이 생긴다. 왜 따로 외부 WAS를 설치하지 않고 내장 WAS를 사용하는 것일까?

 

이에 관한 이유를 살펴보자면 스프링 부트는 '내장 AWS 사용을 권장'한다는 것이다.

내장 WAS를 사용하면 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있다. 

반대로  외장 WAS를 쓴다면 새로운 서버를 추가할 때 마다 그에 따른 종류, 버전, 설정을 모두 일치 시켜야 한다.  

수많은 서버가 있고 매번 모든 서버 버전을 변경해야 한다고 가장한다면..? 생각만 해도 번거롭다.

 

그렇다고 내장 WAS가 간편하기 때문에 외장 WAS보다 좋다!라고 하기에는 다소 비약이 있긴 할테다..

(반대로 외장 WAS가 성능 면에서는 훨씬 더 뛰어나다!라고 보기에도 여러 논란이 있는 듯하다...)

 

 

@RestController
public class HelloController {

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

@RestController 

- 컨트롤러를 JSON을 반환하는 컨트롤러로 만듦

- @ResponseBody를 각 메소드마다 선언할 필요없음

 

@GetMapping

- Get 요청을 받을 수 있는 API 만들어줌

- /hello 요청이 오면 문자열 hello를 반환

앞서 Application 클래스를 살펴봤다면 Controller 클래스를 작성해봤다.                

 

package com.study.duple.springboot.web;
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.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;

@RunWith(SpringRunner.class) //스프링 부트 테스트와 JUnit 사이 연결자 역할
@WebMvcTest(controllers = HelloController.class) //선언할 경우 @Controller, @ControllerAdive 등 사용 가능
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc; //웹 API 테스트할 때 사용, 스프링 MVC 테스트 시작점 (GET,POST 등 테스트 가능)

    @Test
    public void hello_is_return() throws Exception {
        String hello = "hello";

        mvc.perform(get("/hello")) //(1) get 요청
                .andExpect(status().isOk()) //(2) mvc.perform 결과 검증(Ok 즉, 200상태인지 검증)
                .andExpect(content().string(hello)); //(3) mvc.perform 결과 검증(응답 본문 내용 검증)
    }
}

HelloController를 테스트 해보기 위해서 src/test/java 디렉토리에 동일한 이름의 패키지를 생성하고 HelloControllerTest를 작성했다.

이후 해당 테스트 메소드 왼쪽의 화살표를 클릭 > Run ~ 을 클릭 > Test passed: 1 of 1 test를 통해 테스트를 통과했음을 확인했다.

 

 

Application.java 파일로 이동해서 수동으로 실행했을 때도 정상적으로 값이 출력되는 지 확인 해봤다.

 

 

근데 갑자기 발생한 오류... 찾아보니 intelliJ, Gradle 환경에서 프로젝트를 처음으로 실행했을 때 주로 발생하는 오류라고 한다.

 

File >Settings에 들어가서

 Build and run using, Run tests using, Gradle JVM 설정을 바꾸어주니 수동으로 Test 했을 때도 정상적으로 작동했다.

 

 

 

 

롬복(Lombok)
    // lombok
    implementation('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')
    testImplementation('org.projectlombok:lombok')
    testAnnotationProcessor('org.projectlombok:lombok')

롬복은 Getter/Setter, 기본 생성자, toString 등의 어노테이션을 자동으로 생성해주는 라이브러리이다.

build.gradle 파일에 dependencies 부분에 해당 코드를 추가해줬다.

책에 보면 코드를 추가한 뒤 따로 플러그인을 설치해야 한다고 나와 있으나

인텔리제이 2020.03 버전 이후부터는 자동으로 플러그인이 설치되어 있다고 한다. 

 

 

package com.study.duple.springboot.web.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter //get 메소드 생성
@RequiredArgsConstructor //선언된 모든 final 필드가 포함된 생성자 생성
public class HelloResponseDto {
    
    private final String name;
    private final int amount;
}

 

package com.study.duple.springboot.web.dto;

import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class HelloResponseDtoTest {

    @Test
    public void lombok_test() {
        String name = "test";
        int amount = 1000;

        HelloResponseDto dto = new HelloResponseDto(name, amount);
        
        //assertThat: assertj라는 테스트 검증 라이브러리 검증 메소드
        //검증하고 싶은 대상을 메소드 인자로 받음
        //메소드 체이닝이 지원되어 동등 비교 메소드인 isEqualTo와 같이 메소드를 이어 사용 가능
        assertThat(dto.getName()).isEqualTo(name);
        assertThat(dto.getAmount()).isEqualTo(amount);

    }

}

 

기존 코드를 롬복으로 변경하고자 dto를 생성해주고 해당 dto의 Test 코드를 작성한 뒤 테스트를 해줬다.

Test를 진행할 때 assertj의 assertThat을 import 했다. assertj도 Junit에서 자동으로 라이브러리 등록을 해준다고 한다. 

Junit의 assertThat을 쓰면 CoreMatchers 라이브러리가 필요한데 assertj에서는 추가적인 라이브러리 등록이 필요하지 않다고 한다. 

 

 

다음으로 기존에 작성했던 HelloController에도 ResponseDto를 사용해볼 것이다.

   @GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name,
                                     @RequestParam("amount") int amount){
        //@RequestParam(): 외부에서 API로 넘긴 파라메타를 가져오는 어노테이션
        return new HelloResponseDto(name, amount);
    }

HelloController에 외부에서 name, amount란 이름으로 넘긴 파라메타를 저장해줬다. 

 

@Test
    public void helloDto_return() throws Exception{
        String name = "hello";
        int amount = 500;

        mvc.perform(get("/hello/dto")
                .param("name", name)
                .param("amount", String.valueOf(amount)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is(name))) //JSON 응답 값을 필드별로 검증할 수 있는 메소드
                .andExpect(jsonPath("$.amount", is(amount)));
    }

HelloControllerTest 클래스에 추가된 API를 테스트하는 코드를 넣어줬고 정상적으로 테스트가 진행되었음을 확인했다.

 

다음에는 JPA로 DB를 다루는 방법에 대해서 배우고자 한다.

Comments