본문 바로가기

스프링부트와 AWS로 구현하는 웹서비스

[스프링부트/AWS] 2장 스프링 부트에서 테스트 코드를 작성하자(1/2) - JUnit4

- 테스트 코드 소개

TDD와 단위 테스트(Unit Test)는 다른 이야기 입니다. TDD는 테스트가 주도하는 개발을 이야기 합니다. 테스트 코드를 먼저 작성하는 것부터 시작힙니다. 반면 단위 테스트는 TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 이야기 합니다.

테스트 코드를 배운 후 TDD를 배워보길 추천한다고 합니다.

repo.yona.io/doortts/blog/issue/1

 

"TDD 실천법과 도구" 책 전체를 PDF 공개합니다.

2010년 6월에 출간되었던 "TDD 실천법과 도구" 책 전체를 PDF로 공개합니다. 책소개: http://naver.me/GaYZCDjD Updated --- - [1장 - 테스트주도개발 Test Driven Development](https://repo.yona.io/doortts/blog/issue/2) - 18.07.18 -

repo.yona.io

단위 테스트를 배우기 전에 진행한 개발 방식은 다음과 같습니다.

① 코드 작성

② 프로그램(Tomcat) 실행

③ API 테스트 도구를 사용한 HTTP 요청

④ System.out.println()으로 검증

⑤ 결과가 다를 경우 프로그램(Tomcat) 중지 후 코드 수정

 

테스트 코드를 작성하면 다음 세가지의 장점이 있습니다.

첫 번째로 여기서 ② ~ ⑤  매번 코드를 수정할 때마다 반복해야만 합니다. 하지만 테스트 코드를 작성하면 이런 문제가 해결되므로 굳이 손으로 직접 톰캣을 계속 올렸다 내렸다 할 필요가 없습니다.

두 번째로 System.out.println()을 할 경우 사람이 눈으로 검증을 해야 하지만, 테스트 코드를 작성하면 자동 검증이 가능합니다.

세 번째로 개발자가 만든 기능을 안전하게 보호해 줍니다. 예를 들어 B라는 기능이 추가되어 테스트를 할 경우, B 기능이 잘 되어 오픈했더니 기존에 잘되던 A기능에 문제가 생긴 것을 발견합니다. 이럴 대 하나의 기능을 추가할 때마다 서비스의 모든 기능을 테스트할 수는 없습니다.  이렇게 새로운 기능이 추가될 때, 기존 기능이 잘 작동되는 것을 보장해 주는 것이 테스트 코드입니다.

테스트 코드 작성 프레임 워크
  • JUint - Java
  • DBUnit - DB
  • CppUnit - C++
  • NUnit - .net

※ 아직 많은 회사에서 JUnit5보다는 JUnit4를 사용하고 있기에, 이 책에서도 역시 JUnit4로 테스트 코드를 작성합니다.

- Hello Controller 테스트 코드 작성하기

package com.jordy.books.springboot;

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

클래스의 코드를 위의 코드과 같이 작성합니다. 인텔리제이에서 패키지를 가져오는 단축키는 Alt + Enter(윈도우) 입니다.

방금 생성한 Application 클래스는 앞으로 만들 프로젝트의 메인 클래스가 됩니다.

@SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean읽기와 생성을 모두 자동으로 설정됩니다. 특히나 @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트 최상단에 위치해야만 합니다.

main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS(Web Application Server, 웹 어플리케이션 서버)를 실행합니다. 이렇게 되면 항상 서버에 톰캣(Tomcat)을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일(실행 가능한 Java 패키징 파일)로 실행하면 됩니다.

스프링 부트에서는 내장 WAS를 사용하는 것은 권장하고 있습니다. 이유는 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있기 때문입니다.

※ 간혹 내장 WAS를 쓰면 성능상 이슈가 있지 않냐고 하시는 분들이 있다고 합니다. 하지만 대표적인 WAS인 톰캣 역시 서블릿으로 이루어진 자바 애플리케이션입니다. 똑같은 코드를 사용하고 있으므로 성능상 이슈는 크게 고려하지 않아도 된다고 합니다.

 

※ IntelliJ 대소문자 가리지 않고 자동완성하기

기본적으로 IntelliJ는 대/소문자를 구분하기 때문에 불편한 경우가 많습니다. 아래처럼 Match case를 체크 해제하면 대/소문자를 가리지 않고 자동완성이 됩니다.

FIle - Settings - Editor - General - Code Completion - Match case 체크 해제

 

- Hello API 생성(src/main/java/..)

패키지 캡쳐

package com.jordy.books.springboot.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

  • 컨트롤러를 JSON을 반환하는 컨트롤러로 만들어 줍니다.
  • 예전에는 @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해줍니다.

② @GetMapping

  • HTTP Method인 Get의 요청을 받을 수 있는 API를 만들어 줍니다.
  • 예전에는 @RequestMapping(method = RequestMethod.GET)으로 사용되었습니다. 

위 코드는 /hello로 요청이 오면 문자열 hello를 반환하는 기능을 가집니다.

 

www.baeldung.com/spring-controller-vs-restcontroller

 

The Spring @Controller and @RestController Annotations | Baeldung

Learn about the differences between @Controller and @RestController annotations in Spring MVC.

www.baeldung.com

www.baeldung.com/spring-request-response-body

 

Spring's RequestBody and ResponseBody Annotations | Baeldung

Learn about the Spring @RequestBody and @ResponseBody annotations.

www.baeldung.com

 

- 테스트 코드 작성할 클래스 생성(src/test/java)

일반적으로 테스트 클래스는 대상 클래스 이름에 Test를 붙입니다.

package com.jordy.books.springboot;

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 3개는 직접 작성 8)
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;

import com.jordy.books.springboot.web.HelloController;

@RunWith(SpringRunner.class) // 1)
@WebMvcTest(controllers = HelloController.class) // 2)
public class HelloControllerTest {
    @Autowired // 3)
    private MockMvc mvc; // 4)

    @Test
    public void hello가_리턴된다() throws Exception{
        String hello = "hello";

        mvc.perform(get("/hello")) //5)
                .andExpect(status().isOk()) // 6)
                .andExpect(content().string(hello)); // 7)

    }
}

1) @RunWith(SpringRunner.class)

  • 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킵니다.
  • 여기서 SpringRunner라는 스프링 실행자를 사용합니다.
  • 즉, 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 합니다.

2) @WebMvcTest

  • 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션 입니다.
  • 선언할 경우 @Controller, @CotrollerAdvice 등을 사용할 수 있습니다.
  • 단, @Service, @Component, @Repository 등은 사용할 수 없습니다.
  • 여기서는 컨트롤러만 사용하기 때문에 선언합니다.

3) @Autowired

  • 스프링이 관리하는 빈(Bean)을 주입받습니다.

4) private MockMvc mvc

  • 웹 API를 테스트할 때 사용합니다.
  • 스프링 MVC 테스트의 시작점입니다.
  • 이 클래스를 통해 HTTP, GET, POST 등에 대한 API테스트를 할 수 있습니다.

5) mvc.perform(get("hello"))

  • MockMvc를 통해 /hello 주소로 HTTP GET 요청을 합니다.
  • 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언할 수 있습니다.

6) .andExpect(status().isOk())

  • mvc.perform의 결과를 검증합니다.
  • HTTP Header의 Status를 검증합니다.
  • 우리가 흔히 알고 있는 200, 404, 500 등의 상태를 검증합니다.
  • 여기선 OK 즉, 200이 아닌지를 검증합니다.

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

  • mvc.perform의 결과를 검증합니다.
  • 응답 본문의 내용을 검증합니다.
  • Contoller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증합니다.

8) static imprt문

  • import문 : 클래스를 사용할 때 패키지 명을 생략할 수 있다.
new java.util.Date();
↓
import java.util.Date;
new Date();
  • static import문 : static 멤버를 사용할 때, 클래스 이름을 생략할 수 있다.
(1) import
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

mvc.perform(MockMvcRequestBuilders.get("/hello"))
	.andExpect(MockMvcResultMatchers.status().isOk())
	.andExpect(MockMvcResultMatchers.content().string("hello"));
                              	
(2) static import
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;

mvc.perform(get("/hello"))
	.andExpect(status().isOk())
	.andExpect(content().string("hello"));

 

- 테스트 메소드 실행

실행할 메소드 좌측의 재생버튼 클릭 - Run 또는 Debug 클릭
테스트 완료 확인

위에서 검증용으로 선언했던 .andExpect(status().isOk())와 .andExpect(content().string(hello))가 모두 테스트 통과했음을 의미합니다. 테스트 코드 이후 수동으로 실행하여 정상적으로 출력되는지도 확인을 해보겠습니다.

※ Debug모드 클릭 시 중단점 설정 후 진행

중단점을 지정할 row 좌측 클릭시 지정 됨

※ 잘못된 요청일 경우 (Ex. 404, 500, ....)

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

  mvc.perform(get("/hello2"))
  .andExpect(status().isOk())
  .andExpect(content().string(hello));
}

200을 기대했지만, 실제로는 404를 리턴했다.

- 수동 실행

Application.java 파일로 이동해서 main메소드 왼쪽 화살표 버튼 클릭하거나 상단에 있는 Navigation bar에 있는 실행환경에서 Application 선택 후 Run 클릭

우측 상단 Nagivation bar 또는 Application.java로 이동 후 Run 클릭
Url 호출 시 hello노출 확인

테스트 코드의 결과와 같은 것을 알 수 있습니다. 절대 수동으로 검증하고 테스트 코르를 작성하진 않는다고 합니다. 테스트 코드로 먼저 검증 후, 프로젝트를 실행해확인한다는 점 명심해야 합니다.

 

출처 : 

jojoldu.tistory.com/463

 

[스프링 부트와 AWS로 혼자 구현하는 웹 서비스] 출간 후기

(출판사: 프리렉, 쪽수: 416, 정가: 22,000원) 서적 링크 오프라인 서점에는 2019.12.04 (수) 부터 올라갈 예정입니다. 강남 교보문고나 광화문 교보문고는 주말에도 올라올 순 있겠지만, 혹시 모르니

jojoldu.tistory.com