[Spring Boot] REST API 게시판 서버 만들기 #1(프로젝트 생성 및 기본 예제)

안녕하세요.

스프링 부트를 이용한 REST API 게시판 서버 만들기 예제를 진행해보려고 합니다.

 

초급수준으로 진행하며 JUnit을 이용하여 테스트 코드를 작성할 것이고,

Jacoco 코드 커버리지도이용해보고자 합니다. 

 

코드 커버리지란 테스트 코드를 수행할 때 테스트하지 않은 부분을 퍼센트(%)로 측정할 수 있게 하는 부분입니다. pom.xml에 설정을 통해서 서버를 실행할 때 특정 퍼센트를 넘지 않으면 빌드할 수 없도록 설계도 가능합니다.

 

모든 소스는 이곳에서 확인하실 수 있습니다.

 

간단한 예제로 먼저 소개해보겠습니다.

 

프로젝트 스펙

- 스프링 부트 2.2.2 버전

- 자바 1.8

- 롬복

- JPA

- H2 

- spring-restdoc 

- JUnit(4.12)

- jacoco(0.8.5)

- spring-security 

 

예제

- pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project 
	xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation=
    "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>co.worker</groupId>
    <artifactId>board</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>board</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jacoco</groupId>
            <artifactId>org.jacoco.agent</artifactId>
            <version>0.8.5</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- jacoco 코드 커버리지 플러그인 -->
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

프로젝트를 진행하는데 필요한 의존성들을 모두 선언해줍니다. 현재는 테스트코드를 위한 JUnit, 코드커버리지를 위한 Jacoco, Lombok, Spring Web 을 추가해주었습니다.

 

- 프로젝트 패키지 구조

루트 패키지에 home 이라는 패키지를 생성하여 Home, HomeController를 생성해줍니다. test 코드에는 마찬가지로 home 패키지를 생성하고 HomeControllerTest 클래스를 생성해줍니다.

 

Home 클래스
package co.worker.board.home;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Home {
    private String name;
    private int age;
}

정확한 커버리지 측정을 위해서 Lombok의 @Data가 아닌 @Getter / @Setter 어노테이션을 이용해 필요한 것만 선언합니다. 다른 불필요한 메소드가 포함된 @Data 어노테이션은 커버리지의 퍼센트를 낮추게 됩니다. (커버리지를 정확히 측정하기 위해서 모든 메소드들은 필요에 의해서 선언되어야만 한다. -> 즉 사용되어져야 선언되어 있어야한다.)

 

HomeController 클래스
package co.worker.board.home;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/home")
public class HomeController {

    @GetMapping("/get")
    public ResponseEntity getHome(@RequestParam("name") String name,
                                  @RequestParam("age") String age){
        Home home = new Home();
        home.setAge(Integer.parseInt(age));
        home.setName(name);

        return ResponseEntity.ok(home);
    }
}

get 방식의 Home 객체를 가져올 메소드를 만듭니다. name과 age를 받아서 처리합니다. age가 String인 이유는 get방식의 파라미터는 String 타입이기 때문입니다.

또한 다들 아시겠지만 추가적으로 설명드리자면 GET 방식은 RequestBody가 없기 때문에 @RequestBody 어노테이션을 파라미터에 선언할 수 없습니다.

 

@RestController : 현재 컨트롤러가 데이터를 반환하는 REST 컨트롤러라는 것을 선언하는 것.

ResponseEntity : REST 방식을 구현하는 객체. ok() 라는 static 메소드를 이용하여 ResponseBody에 Json형식으로 데이터를 반환할 것이다.

@RequestParam(String) : String 파라미터로 들어가는 값의 파라미터명으로 

 

HomeControllerTest
package co.worker.board.home;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class HomeControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void getHome() {
        try {
            mockMvc.perform(get("/api/home/get")
                    .contentType(MediaType.APPLICATION_JSON_VALUE)
                    //.params(paramMap))
                    .param("name", "woo")
                    .param("age", "19"))
                    .andExpect(status().isOk())
                    .andDo(print());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

API 테스트를 위한 컨트롤러 테스트 코드를 작성하였습니다.

 

@AutoConfigureMockMvc : MockMvc 객체를 주입받아 사용하기 위해서 자동설정해주는 어노테이션입니다. 선언되지 않았을 경우 직접 빌드해주어야 하는 객체입니다.

.param() : 파라미터를 전달하는 메소드입니다.

.andExpect() : 예상되는 상태를 넣어주는 메소드입니다.

.andDo() : 테스트 동안 실행할 부분을 넣어주는 메소드입니다. 주로 print() 메소드로 아래의 출력결과 내용을 출력합니다.

 

실행결과
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /api/home/get
       Parameters = {name=[woo], age=[19]}
          Headers = [Content-Type:"application/json;charset=UTF-8"]
             Body = null
    Session Attrs = {}

Handler:
             Type = co.worker.board.home.HomeController
           Method = co.worker.board.home.HomeController#getHome(String, String)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"name":"woo","age":19}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

- 파라미터로 name과 age가 request에 의해 넘어간 것을 확인할 수 있습니다.

- response객체로 Home 클래스에 담은 파라미터 값들이 json 형태로 전달된 것을 확인할 수 있습니다.

 

Jacoco 코드커버리지

테스트코드를 실행하기 전에 cmd 혹은 터미널을 이용하여 해당 프로젝트로 가서

mvn clean verify

위의 명령어를 실행시킵니다. 이는 mvn 을 비워내어 새로 빌드하고 작성된 클래스들을 target 폴더에 다시 재 빌드하도록 하는 명령어입니다. 새로 작성한 .java 파일들은 아직 class파일로 컴파일되지 않았기 때문에 코드커버리지를 담당하는 jacoco가 코드커버리지를 측정하는 시점이 맞지 않게 됩니다. 이를 수정해주는 작업을 해주고 다시 테스트 코드를 실행해줍니다.

 

그러면 다음과 같은 target 디렉토리 구조가 나타나게 될것입니다.

 

target 디렉토리 구조

target 디렉토리의 하위 디렉토리들인데 site\jacoco에 들어있는 index.html을 브라우저로 실행을 하게 되면

 

코드커버리지 index.html 문서

이 문서를 통해 타고타고 들어가서 모든 소스코드들이 테스트 되었는지 어느부분은 되지 않았는지 검사할 수 있습니다.

예제 소스인 home 패키지의 경우 모든 소스를 실행하였기 때문에 100%가 나오게 됩니다. 전체 소스 퍼센트는 88%로 아주 높게 측정되었습니다.

 

 

 

 

댓글

Designed by JB FACTORY