🌱JAVA/Spring

[Spring Boot] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 - 03

뉴발자 2022. 11. 8.
728x90

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

레드 그린 사이클

 

 

 

 

테스트 코드

TDD (Test Driven Development,테스트 주도 개발)와 단위 테스트 (Unit Test)는 다른 개념이다.

 

 

TDD란?

TDD는 테스트가 주도하는 개발을 뜻하고 테스트 코드를 먼저 작성하는 것부터 시작된다.

 

 

레드 그린 사이클

  1. 항상 실패하는 테스트를 먼저 작성한다. (Red)
  2. 테스트가 통과하는 프로덕션 코드를 작성한다. (Green)
  3. 테스트가 통과하면 프로덕션 코드를 리팩토링한다. (Refactor)

 


 

단위 테스트란?

  • TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 뜻한다.
  • TDD와 달리 테스트 코드를 꼭 먼저 작성해야 하는 것도 아니고, 리팩토링도 포함되지 않는다.
  • 순수하게 테스트 코드만을 작성하는 것을 뜻한다.

 

 

단위 테스트 코드 작성시 이점

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

 

1.  빠른 피드백을 받을 수 있다.

일반적인 개발 방식

  1. 코드를 작성한다.
  2. 프로그램(Tomcat)을 실행한다.
  3. Postman 같은 API 테스트 도구로 HTTP를 요청한다.
  4. 요청 결과를 System.out.println()으로 받아 눈으로 검증한다.
  5. 원하는 결과가 나올때 까지 프로그램(Tomcat)을 중지하고 코드를 수정한다.

 

여기서 2~5번은 매번 코드를 수정할 때마다 반복해야한다.

 

톰캣을 재시작하는 시간은 수십 초에서 코드의 양이 많아지면 1분이상 소요되기도 하고

 

수십 번씩 수정해야 하는 상황에서 아무런 코드 작업 없이 1시간 이상 소요되기도 한다.

 

이는 테스트 코드가 없다 보니 눈과 손으로 직접 수정된 기능을 확인할 수 밖에 없기 때문에 발생한다.

 

테스트 코드를 작성하게 되면 이런 문제가 해결되므로 굳이 손으로 직접 톰캣을 올렸다 내렸다 할 필요가 없어진다.

 

 

2. System.out.println()을 통해 눈으로 검증해야한다는 문제

 테스트 코드를 작성하게 되면 더는 사람이 눈으로 검증하지 않아도 되고 자동검증이 가능해진다.

 

 

3. 개발자가 만든 기능을 안전하게 보호해 준다.


B라는 기능이 새로 추가되어 테스트를 한 후 B 기능이 잘돼서 오픈했더니

 

기존에 잘되던 A 기능에 문제가 생긴 것을 발견한다.


 

위의 예시는 규모가 큰 서비스에서는 빈번하게 발생하는 문제이다.

 

하나의 기능을 추가할 때마다 너무나 많은 자원이 들기 때문에 서비스의 모든 기능을 테스트할 수는 없다.

 

이렇게 새로운 기능이 추가되는 경우, 기존에 있던 기능이 잘 작동되는 것을 보장해 주는 것이 테스트 코드이다.

 

A 라는 기존 기능에 기본 기능을 비롯해 여러 경우를 모두 테스트 코드로 구현해 놓았다면 테스트를 수행만 하면 문제를 조기에 찾을 수 있게 된다.

 

이처럼 테스트 코드의 작성을 도와주는 프레임워크들이 있다.

 

가장 대중적으로 사용하는 프레임워크는 xUnit이고 이는 개발환경( x )에 따라서 Unit 테스트를  도와주는 도구라고 생각하면 된다.

 


대표적인 xUnit 프레임워크

  • JUnit - Java
  • DBUnit - DB
  • CppUnit - C++
  • NUnit - .Net

 

이 중에 필자가 사용할 것은 Java언어의 xUnit 프레임워크인 jUnit을 사용하고

 

책에서 jUnit4를 사용하기 때문에 jUnit4로 테스트 코드를 작성할 것이다.

 

 

 

 

테스트 코드 작성

1. Hello Controller 테스트 코드 작성하기

1) 신규 패키지 생성

프로젝트에서 패키지를 하나 생성한다. [디렉토리 우클릭 -> 새로 만들기 -> 패키지]

 

(일반으로 패키지명은 웹 사이트 주소의 역순으로 생성한다. / ex. com.tistory.tlseoqja)

테스트 코드 작성용 신규 패키지 생성

 

 

2) 신규 Java Class 생성

패키지 바로 아래에 Java클래스를 생성한다. [디렉토리 우클릭 -> 새로 만들기 -> Java 클래스]

 

(클래스 명은 아무렇게나 해도 되고 필자는 책을 따라서 Application으로 생성했다.)

테스트 코드 작성을 위한 신규 Java Class 생성

 

 

3) 메인 클래스 코드 및 내장 WAS 실행 코드 작성

메인 클래스 및 내장 AWS 실행 코드 작성

 

package com.tistory.tlseoqja;

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

    }

}

 

어노테이션이나 메소드는 Ctrl+Space를 누르면 자동완성 및 메소드 추천 목록을 제공해준다.

 


코드 설명

 

@SpringBootApplication

  • Spring Boot의 자동 설정 Spring Bean 읽기와 생성 자동 설정을 해주는 어노테이션
  • 이 어노테이션이 위치한 곳부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야만 한다.

 

 

SpringApplication.run(ClassName.class, args)

  • main메소드에서 실행하는 SpringAppliction.run() 명령어로 인해 내장 WAS를 실행시킨다.
  • WAS : Web Application Server

 

 

내장 WAS란?

  • 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 뜻한다.
  • 항상 서버에서 Tomcat을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar파일로 실행하면 된다.
  • 꼭 내장 WAS 사용할 필요는 없지만, 스프링 부트에서는 내장 WAS를 사용하는 것을 권장한다.

 

내장 WAS 사용을 권장하는 이유

  • 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있기 때문이다.
  • 외장 WAS를 사용한다면 모든 서버는 WAS의 종류, 버전 설정을 일치시켜야만 한다.

 

내장 WAS를 사용한다면 이러한 작업 없이 업무를 진행할 수 있다.

 


 

 

4) 테스트를 위한 Controller생성

방금전에 생성한 패키지안에 'web'이라는 이름의 새로운 패키지를 생성해준다.

[패키지 우클릭 -> 새로 만들기 -> 패키지]

이전에 만든 패키지 안에 하위 패키지 신규 생성

 

'web'패키지안에 Java Class를 신규로 생성해준다.

 

필자는 'HelloController'란 이름으로 생성했다.

 

web 패키지에 HelloController Java Class 신규 생성

 

생성이 완료됐다면 간단한 API를 만드는 코드를 HelloController.java에 작성하겠다.

 

 

5) HelloController.java 코드 작성

간단한 API 코드 작성

 

package com.tistory.tlseoqja.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을 반환하는 컨트롤러로 만들어주는 어노테이션
  • Java에서 @ResponsBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해주는 어노테이션

 

 

@GetMapping

  • HTTP Method인 Get의 요청을 받을 수 있는 API를 만들어준다.
  • Java에서 @RequestMapping(mathod = RequestMethod.GET)으로 사용되었다.
  • 이 프로젝트에서 /hello로 요청이 오면 문자열 hello를 반환하는 기능을 가지게 되었다.

 


 

 

6) 작성된 코드의 동작 확인

먼저 WAS를 실행하지 않고 테스트 코드로 검증해보겠다.

 

src/test/java 디렉토리에 앞에서 생성한 패키지를 그대로 다시 생성해서 테스트를 진행한다.

 

com.tistory.tlseoqja.web 패키지를 생성하고 그 안에 HelloControllerTest Java Class를 신규로 생성했다.

 

(일반적으로 테스트 클래스는 테스트 대상 클래스 이름 뒤에 Test를 붙여서 생성한다.)

코드 테스트를 위한 테스트 패키지 및 클래스 신규 생성

 

패키지 및 클래스 생성 후에 아래와 같이 코드를 작성해주면 된다.

 

테스트 코드 HelloControllerTest 작성

 

하지만!

 

이대로 작성하면 아래쪽 get, status, content 명령어에서 오류가 날 것이다.

 

이유는 자동으로 import해주지 못해서 발생하는 문제이다.

 

자동 완성 단축키(Ctrl + Space)를 눌러도 반응하지 않거나 다른 명령어가 추천될 것이다.

 

해결방법은 직접 라이브 러리를 import해주는 것이다.

 

get, status, content 오류시 해결방법

 

package com.tistory.tlseoqja.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)
@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(SpringRunner.class)

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

 

 

@WebMvcTest

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

 

 

@Autowired

  • 스프링이 관리하는 Bean을 주입받는 어노테이션

 

 

@private MockMvc mvc

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

 

 

@mvc.perform(get("/hello"))

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

 

 

@.andExpect(status().isOk())

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

 

 

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

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

 


 

 

7)코드 실행

테스트 코드 실행 화면

 

코드를 실행하기 위해서는 해당 메소드 왼쪽 초록색 화살표를 누른 후 '실행 hello가_리턴된다()'를 클릭해주면 실행된다.

 

하지만!

 

필자의 경우 아래와 같은 오류가 발생했다.

 

테스트 코드 실행 후 발생한 오류

 

위와 같은 오류가 왜 나는지 검색해보니 메소드명에 한글을 넣어 표시가 안돼 생기는 오류였다.

 

Execution failed for task ':test' 오류 해결방법

 

해결방법

[파일 - 설정 - 빌드,실행,배포 - 빌드 도구 - Gradle의 다음을 사용하여 테스트 실행 Gradle -> IntelliJ]

 

설정을 변경 한 후 적용 해주면 빌드가 정상적으로 실행된다.

 

정상적으로 실행된 'hello가_리턴된다' 메소드

 

위 사진과 같이 테스트가 정상적으로 통과하는 것을 확인할 수 있다.

 

하지만 더욱 확실하게 하기 위해서 수동으로 코드를 실행시키고 정상적으로 값이 출력되는지 확인해보겠다.

 

 

7-1)수동으로 코드 실행

우선 main폴더에 만들어둔 Application.java파일로 이동해서 마찬가지로

 

main메소드의 왼쪽 화살표 버튼을 클릭한 후 실행 'Application.main()'을 클릭한다.

 

수동으로 테스트 코드 실행하는 방법

 

서버가 실행되면서 콘솔창에 서버가 돌아가는 것을 확인할 수 있고

 

localhost:8080/hello로 접속할 수 있는 상태가 된다.

 

서버가 실행되고 있고 콘솔창에 포트번호가 표시된다.

 

localhost:8080/hello 사이트로 접속하면 문자열 'hello'가 리턴되는 페이지를 확인 할 수 있다.

 

localhost:8080/hello 페이지의 접속 화면

 

 

수동으로 서버를 검증하기 전에 테스트 코드를 작성하면 오류를 줄일 수 있고 견고한 소프트웨어를 만드는데 도움이된다.

 

 

 

다음번에는 롬복의 소개와 설치 방법을 알아보겠다.

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90

댓글