반응형

프로젝트 생성 및 깃 연동

Project : Gradle Project
Language : Java
SpringBoot : 2.7.2
Packaging : Jar
Java : 11

Dependencies : Spring Web

새로운 프로젝트 생성

오늘부터 2주간 다시 프로젝트를 만들어서 복습해보는 시간을 가지려고 합니다.
이전 프로젝트는 너무 장기간, 공부시간을 제대로 가지지 못해서 더더욱 헤매었으나 이번에는 조금 더 길고 짧게 배워보겠습니다.
먼저 오늘은 프로젝트를 생성하고 Git과 연동을 할 예정입니다. 프로젝트 생성과 깃 연결 방법은 예전에 포스팅했던 프로젝트를 참고해주셔도 좋습니다. ^^

2021.11.11 - [programming/SpringBoot] - [SpringBoot] 1장 인텔리제이에서 깃과 깃허브 사용하기

 

[SpringBoot] 1장 인텔리제이에서 깃과 깃허브 사용하기

최근의 개발 상황에서 버전 관리는 뺄 수 없는 요소이다. 이 버전 관리는 SVN에서 깃으로 완전히 전환되어 가는 중이며, 실제로 대부분의 IT 서비스 회사는 깃을 통해 버전 관리를 하고 있다. 대표

roobi-story.tistory.com

설정한 대로 잘 되어있는지 build.gradle을 열어서 확인해 줍니다.

새로운 프로젝트를 만들 때 마다 레파지토리를 새로 만들고 거기에 git을 연결해줬었는데 다시 보니 인텔리 J에서 너무 쉽게 레파지토리 생성, 연결을 해주어서 정말 편했습니다.

왜 기억이 안났던가..ㅠ

역시 공부란 제대로 해야 된다는 걸 다시 한번 느꼈습니다.

springframework web, test가 잘 있는지 확인
새로 하는 커밋!

저는 살면서 복습을 제대로 해본 적이 거의 없는데 뭔가 새로운 느낌이 들었습니다.
어떤 식으로 코딩을 할 거라는 건 알고 있지만 그 전에는 어떻게 작동하는지 정확히 몰랐으나 이번 프로젝트에서는 어떻게 작동하는지 분석할 수 있다 라는 새로운 기대감을 가졌습니다. 

 

테스트 코드 작성

 테스트 코드를 작성하는 이유는 다양한 이유가 있습니다.
단위 테스트를 배우기 전 진행하는 방식은
1. 코드 작성
2. 프로그램 실행 (Tomcat)
3. Postman과 같은 API 테스트 도구로 HTTP 요청
4. 요청 결과를 System.out.println()으로 눈 검증
5. 결과가 다르면 다시 프로그램을 중지하고 코드 수정 후 재 확인

* 테스트 코드를 사용하는 이유
1) 2~5번은 매번 코드를 수정할 때마다 반복을 해야 합니다. 프로그램에 따라 톰캣을 재시작하는 시간은 수십 초에서 1분 이상을 소요할 수 있습니다. 테스트 코드는 이런 시간적 문제를 해결할 수 있습니다.
2) 자동 검증이 가능하다.
3) 개발자가 만든 기능을 안전하게 보호해준다.

자 그럼 개발자의 기본인 HelloWorld를 찍어보겠습니다.
// main/java/web/HelloController.java

package com.tistory.roobistory.roobirona.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";
    }
}​
// test/java/web/HelloControllerTest.java

package com.tistory.roobistory.roobirona.web;

import org.junit.jupiter.api.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.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));
    }
}

 

책과 똑같이 입력 시
mvc.perform(get("/hello"))
                    ^
  symbol:   method get(String)
  location: class HelloControllerTest

cannot find symbol method get(String)
이라는 오류가 뜹니다.

위에 import 구간에 밑에 문구를 추가해주셔야 합니다.

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

추가하고 실행해보시면 Test 코드에 성공하시는 모습을 볼 수 있습니다.

HelloControllerTest 성공!

 여기서 만약 HelloController.java에 return 값을 Hello가 아닌 Hello World!라고 바꿔보시면 오류가 납니다.
			mvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string(hello));​

Controller에서 "hello"를 리턴하기 때문에 이 값이 아니라면 밑에 이미지처럼 실제값과 비교를 해 줍니다.

리턴 값이 Hello가 아니라 Hello World!라서 오류가 발생.

자 이번에는 기존 코드를 롬복으로 전환해보려고 합니다.
롬복은 자바로 개발할 때 자주 사용하는 코드인 Getter, Setter, 기본 생성자, toString 등을 어노테이션으로 자동 생성해 줍니다.
만약 이 프로젝트가 작은 규모가 아닌 큰 규모의 프로젝트였다면 롬복으로 전환하는 게 쉽지 않았을 것입니다. 어떤 기능이 제대로 작동될지 안 될지 예측할 수 없기 때문입니다. 하지만 테스트 코드가 지켜주기 때문에 쉽게 변경을 할 수 있습니다.
// main/java/web/dto/HelloResponseDto

package com.tistory.roobistory.roobirona.web.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor

public class HelloResponseDto {
    private final String name;
    private final int amount;
}​


// test/java/web/dto/HelloResponseDtoTest
package com.tistory.roobistory.roobirona.web.dto;

import org.junit.jupiter.api.Test;

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

public class HelloResponseDtoTest {
    @Test
    public void 롬복_기능_테스트() {
        //given
        String name = "test";
        int amount = 1000;

        //when
        HelloResponseDto dto = new HelloResponseDto(name, amount);

        //then
        assertThat(dto.getName()).isEqualTo(name);
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}

테스트 코드를 실행해 보면 아래와 같이 variable name not initialized in the default constructor 오류가 발생하는 것을 볼 수 있습니다. 해당 오류는 책의 저자님의 깃헙 이슈에서 확인해볼 수 있었습니다. 저자님 말씀대로 
// bulid.gradle

annotationProcessor 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.projectlombok:lombok'​

bulid.gradle에 의존성 추가하고 업데이트해주면 오류가 해결되었습니다.

롬복 오류?!
의존성 추가하니 테스트 성공!

롬복의 @Getter로 get메서드가 @RequiredArgsConstructor로 생성자가 자동으로 생성되는 것이 증명되었습니다.
HelloController에도 새로 만든 ResponseDto를 사용하도록 추가해보겠습니다.
// main/java/web/HelloController.java
...
@GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount) {
        return new HelloResponseDto(name, amount);
}
...​


name과 amount는 API를 호출하는 곳에서 넘겨준 값들입니다. 추가된 API를 테스트하는 코드를 HelloControllerTest에 추가합니다.

// test/java/web/HelloControllerTest

...

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

...

추가하시고 실행시키시면 아래 그림처럼 오류가 뜹니다.
is 라이브러리를 추가해주지 않아서 발생하는데요 추가적으로 import 시켜줍니다.

import static org.hamcrest.Matchers.is;​

is 구문에서 오류가 발생합니다.
import 후 실행시키면 정상 작동합니다.

 

어노테이션 정리

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

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

@RunWith(SpringRunner.class)
- 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킵니다.
- 여기서는 SpringRunner라는 스프링 실행자를 사용합니다.
- 즉 스프링 부트 테스트와 JUnit 사이에 역할자를 합니다. 

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

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

private MockMvc mvc
- 웹 API를 테스트할 때 사용합니다.
- 스프링 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"를 리턴하기 때문에 이 값이 맞는지 검증합니다.

@Getter
- 선언된 모든 필드의 get 메서드를 생성해 줍니다.

@RequiredArgsConstructor
- 선언된 모든 final 필드가 포함된 생성자를 생성해 줍니다.
- final이 없는 필드는 생성자에 포함되지 않습니다.

assertThat
- assertj라는 테스트 검증 라이브러리의 검증 메서드입니다.
- 검증하고 싶은 대상을 메서드 인자로 받습니다.
- 메소드 체이닝이 지원되어 isEqualTo와 같이 메서드를 이어서 사용할 수 있습니다.

isEqualTo
- assertj의 동등 비교 메서드입니다.
- assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공입니다.

@RequestParam
- 외부에서 API로 넘긴 파라미터를 가져오는 어노테이션입니다.
- 여기서는 외부에서 name (@RequestParam("name"))이란 이름으로 넘긴 파라미터를 메서드 파라미터 name(String name)에 저장하게 됩니다.

param
- API 테스트할 때 사용될 요청 파라미터를 설정합니다.
- 단, 값은 String만 허용됩니다.
- 숫자/날짜 등의 데이터도 등록할 때는 문자열로 변경해야 가능합니다.

jsonPath
- JSON 응답 값을 필드별로 검증할 수 있는 메서드입니다.
- $를 기준으로 필드명을 명시합니다.
- 여기서는 name과 amount를 검증하니 $.name, $amount로 검증합니다.

 

반응형
복사했습니다!