최근 개발 추세
- 테스트 코드에 관한 요구사항 증가. 코딩 테스트를 알고리즘 -> 프로젝트 생성 및 단위 테스트를 필수 조건으로.
테스트 코드 작성과 테스트 관련 경험이 중요해지고 있다!
*TDD란 무엇인가?
Test Driven Development의 약자로 테스트 주도 개발을 의미함. 즉, 테스트 코드를 먼저 작성하는 것부터 개발이 시작됨.
레드 그린 사이클
- Red : 항상 실패하는 테스트를 먼저 작성한다.
- Green : 테스트가 통과하는 프로덕션 코드를 작성한다.
- Refactor : 테스트가 통과하면 프로덕션 코드를 refactoring.
*단위 테스트란?
TDD의 첫번째 단계인 기능 단위 테스트 코드를 작성하는 것으로 테스트 코드를 먼저 작성할 필요가 없다는 점, 리펙토링이 포함되지 않는다는 점에서 TDD와 다름.
단순 테스트 코드 작성하는 것만을 의미함
*테스트 코드를 작성해야하는 이유?
단위테스트 이전에 개발 방식
코드 작성 -> tomcat실행 -> api 테스트 http 요청 -> 결과 system.out.println으로 눈으로 직접 확인 -> 결과 다르면 톰캣 중지 후 코드 수정
톰캣을 내렸다 켜는 시간이 오래 걸리기에 코드 수정없이 오랜 시간이 소요된다는 단점.
테스트 코드의 장점.
- 개발 단계 초기에 문제 발견
- 코드 리팩토링, 라이브러리 업그레이드 등 기존 기능의 작동 확인 --> 기능 불확실성 감소
새 기능이 추가 되었을 때 기존 기능이 잘 작동하는지 확인 가능하다.
기존 기능에 대한 테스트 코드를 만들어 놓고 해당 코드가 잘 작동하는지 확인해보면 되기 때문임.
- 테스트 자체가 시스템에 대한 실제 문서 역할.
- 눈으로 확인하는 것보다 정확하게 검증이 가능.
테스트 코드 작성 프레임 ㅝ크
JUnit - Java
현재 JUnit5까지 나왔으나 이 책에선 JUnit4 사용할 예정.
2.2 Hello Controller 테스트 코드 작성하기
과거 인프런 강의에선 테스트 코드부터 작성한적은 없는데 새로운 경험이다.
package org.example.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Apllication {
public static void main(String []args){
SpringApplication.run(Apllication.class,args);
}
}
해당 클래스가 앞으로 사용할 프로젝트의 메인 클래스이다.
(직전에 java project를 해봐서 Main class랑 헷갈렸다 자바 메인 클래스는 따로다.)
@SpringBootApplication
스프링 부트의 자동설정, 스프링 Bean 읽기, 생성을 모두 자동으로 함. 해당 위치부터 설정을 읽어가기때문에 항상 프로젝트 최상단에 위치한다.
main 메소드 내부의 SpringApplication.run이 실행되어 내장 WAS(Web Application Server)를 실행함.
내장 WAS를 실행하면 외부 서버를 이용하는 것이 아니기에 Tomcat을 설치할 필요가 없고 jar파일로 실행함.
내장WAS 사용을 권장하는 이유? -> 언제 어디서나 스프링부트를 배포할 수 있기때문임.
외장 WAS는 모든 서버의 WAS의 종류, 버전, 설정을 일치시켜야하는 여러서버의 동기화 문제가 발생함.
package org.example.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";
}
}
HelloController 작성 코드
@RestController : 해당 컨트롤러가 JSON을 반환하는 컨트롤러로 만들어줌. (@ResponseBody를 각각 선언했던 것을 대체함.)
JSON이란?
자바스크립트에서 객체를 표기하는 방법, 데이터 이름과 값의 쌍으로 나타나며 데이터끼리는 쉼표로 구분함.
예시 : "데이터 이름" :
객체는 중괄호{}로 표현하며 배열은 대괄호[]로 표현함. DB에서 테이블 표기하는 방법하고 비슷한듯..?
JSON 객체 배열 표기 예시.
"dog": [
{"name": "식빵", "family": "웰시코기", "age": 1, "weight": 2.14},
{"name": "콩콩", "family": "포메라니안", "age": 3, "weight": 2.5},
{"name": "젤리", "family": "푸들", "age": 7, "weight": 3.1}
]
@GetMapping
HTTP Method중 하나인 Get의 요청을 받는 API 생성.
HelloControllerTest 작성 ( 테스트 코드로 검증하는 방법 )
package org.example.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 org.springframework.test.web.servlet.ResultActions;
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)//HelloContorllerTest.class 여서 404 오류 발생
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));
}
}
Status expected:<200> but was:<404> 에러 발생
에러의 이유를 몇 가지 찾아보았다.
1. 파일의 절대경로 문제? -> 패키지 절대 경로를 비교해보니 똑같다.
2. String을 반환하는 경우 문자열이 아닌 view와 템플릿 엔진을 찾기에 404오류가 발생한다?
.....였으나..! 코드를 자세히 읽어보니 @WebMvcTest(controllers = HelloController.class)를 HelloControllerTest.class로 작성해 404 오류가 발생했던것. 결국 코드를 잘못쳐서 그런거다...ㅎ..ㅎ....
404 오류는 찾으려는 데이터가 존재하지 않을때 발생하는 오류인데 controller로 HelloControllerTest.class(존재하지 않음)를 사용해서 404 오류가 발생했다.
@RunWith(SpringRunner.class)
-테스트 진행 시 JUnit 외의 실행자를 시키는 역할, 여기선 SpringRunner라는 스프링 실행자를 사용해 스프링 부트와 JUnit의 연결 역할을 함.
@WebMvcTest (controllers = HelloController.class) // 컨트롤러는 HelloController.class를 사용하겠다.
여러 스프링 어노테이션중 Spring MVC에만 집중할 수 있는 어노테이션.
@Controller, @ControllerAdvicd는 사용이 가능하나 반대로 @Service, @Component, @Repository는 사용이 불가능함.
MVC : model / view / controller의 약자. controller에 비즈니스 로직을 작성하고 관련된 내용 및 데이터를 model이라는 객체에 담아 view 템플릿으로 시각적으로 화면에 띄우는 개발 방법론.
mvc 관련 참고자료
https://cocoon1787.tistory.com/733
[개발상식] MVC 패턴이란? (Model-View-Controller)
🚀 이번 포스팅은 개발자 면접에서 자주 나오는 질문 중의 하나인 "MVC패턴"에 대한 내용입니다. MVC패턴의 의미와 사용해야 하는 이유, 사용 예시 등등에 대해 알아보겠습니다. 💡 MVC 패턴이란?
cocoon1787.tistory.com
@Autowired
스프링이 관리하는 Bean을 주입 받는다.
-Bean이란?
@private mockMvc mvc
웹 API를 테스트할때 사용하는 변수, MVC 테스트의 시작점. 이를 활용해 get,post 메소드 사용가능
ex) mvc.perform(get("/hello")) : hello 주소로 http get 요청을 보냄.
.andExpect() : mvc.perform의 결과를 검증함. 예상되는 결과와 실제 값이 맞는지 확인
1.status().isOK() : HTTP Header의 Status를 검증함. 200,404,500등 (isOK() == 200인지 확인함.)
2, content().string(hello) 앞서 선언한 hello 변수와 결과가 동일한 지 확인
수동으로 직접 실행해 정상 값이 출력되는지 확인하기.
main class 에서 main함수를 실행해주자.
두 가지 방법으로 작성한 코드가 제대로 동작하는지 확인할 수 있다. 다만, 브라우저의 검증은 종종 하되, 테스트 코드는 반드시 작성하는 습관을 들이는 것을 강조한다.
2.3 롬복 소개 및 설치
롬복이란?
자바 개발자들의 필수 라이브러리로 자주 사용하는 Getter, Setter, 기본 생성자, toString등을 어노테이션으로 자동생성해준다.
엇 나는 이미 설치가 되어있었다.
2.4 HelloController 코드를 롬복으로 전환하기
책의 코드를 진행하던 중 @RequiredArgsConstructor 어노테이션을 쓰던 중 다음 오류가 발생했다.
> Could not find method complie() for arguments [org.projectlombok:lombok] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
build.gradle 파일에서 dependencies에 lombok 관련 'org.projectlombok:lombok'에 대한 complie method가 작동을 안하는 것 같은데 Gradle 버전도 낮췄는데도 왜 안되지.... 해서 이번엔 그냥 implement를 사용했다.
dependencies {
implementation 'org.projectlombok:lombok'
compile('org.springframework.boot:spring-boot-starter-web')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
(build.gradle의 dependency부분)
롬복을 활용한 HelloResponseDto
package org.example.springboot.web.dto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
private final String name;
private final int amount;
}
@Getter
선언된 모든 필드 ( name, amount )의 getter를 만들어준다.
일반적인 Getter 코드는 한 객체 내부에서 해당 변수를 반환해주는 그런 역할을 한다.
//getter code 예시
public String getName(){
return name; // name은 class내의 변수
}
@RequiredArgsConstructor : 선언된 모든 final 필드가 포함된 생성자를 만들어준다. (final필드가 없으면 생성 안됨)
객체의 생성자를 자동으로 만들어주는 역할을 하는 듯 하다.
이렇게 우리가 기존에 작업하던 코드 --> 롬복으로 전환함 코드의 수정이 발생
기존 기능이 잘 작동하는지 확인해봐야함 -> 테스트코드 작성
HelloResponseDtoTest 코드
package org.example.springboot.web.dto;
import org.junit.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);
}
}
*assertThat
- assertj라는 테스트 검증 라이브러리의 검증 메소드
- 인자로 검증하고 싶은 대상을 받고 .istEqualTo를 사용해 자신의 parameter (dto.getName())과 name이 같은지를 확인해준다.
*isEqualTo : assertThat의 인자와 자신의 인자가 같으면 성공
JUnit vs assertj
assertj가 조금 더 장점이 많다?
- 추가라이브러리 필요없음.
- 자동완성이 더 확실하게 지원.
//given //when // then?
테스트 코드를 작성할때 쓰는 로직으로 테스트 코드를 3단계로 나눠서 작성하는 방법이다.
차례로 준비 - 실행 - 검증의 단계를 의미하며 이 단계에 맞추어 테스트 코드를 작성한다.
Given : 테스트를 준비하는 과정. / 구체적인 행동을 하기전 상
테스트에 사용되는 변수, 입력값을 정의하고 Mock객체를 정의한다.
When : 실제 액션을 하는 테스트를 실행하는 과정으로 테스트 하고자 하는 기능, 행동을 의미함.
주어진 정보를 기반으로 이런 상황이 발생했다!!! 가장 중요한 구문이지만 대체로 짧다.
Then : 마지막 테스트를 검증하는 과정
assertThat을 활용해 실제결과와 예상되는 결과를 비교한다.
위 코드 예시
Given : name에 "test"를 , amount가 1000이 주어졌다고 하자.
When : 해당 변수를 가진 HelloResponseDto 객체(dto)가 생성되었을때.
Then : dto의 name 이 주어진 name과 같은지 , amount가 주어진 amount와 같은지 확인하라.
참고한 블그
https://brunch.co.kr/@springboot/292
Given-When-Then Pattern
테스트 코드 작성 표현 방법 (스프링 부트 환경에서) | 이번 글에서는, 테스트 코드 작성 시 자주 사용하는 Given-When-Then Pattern에 대해서 간략하게 소개하겠다. 별 내용 없는 글이므로, 아주 편한
brunch.co.kr
HelloController에 새로만든 ResponseDto 사용 코드
@GetMapping("/hello/dto")
public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount){
return new HelloResponseDto(name,amount);
}
@RequestParam : 외부 api로 넘겨 받는 파라미터
http요청시 해당 파라미터를 같이 받아서 위 메소드의 파라미터로 저장한다.
사용 url 예시 : http://localhost:8080/hello/dto?name=test&amount=1000
객체 자체를 json 방식으로 보여주는 듯
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)));
}
@Param : 외부 API에서 받아 사용할 (@RequestParam의 내용) 파라미터를 설정하는 것
ex) .param("name",name) name 이란 파라미터에 해당 메소드 지역변수 'name'을 설정하라
단, String만 값으로 허용된다. 따라서, 숫자/날짜의 값을 등록시 문자열로 변경해야함
(ex : String.valueOf(amount) )
@jsonPath : json객체로 응답된 값을 필드별로 검증하는 메소드
- $를 기준으로 필드명을 명시함. ex $.name : name 필드를 지칭.
버전 관련 작가님이 직접 작성한 블로그 링크 공유
https://jojoldu.tistory.com/539
'BE > 6기 코테이토 - Spring Study' 카테고리의 다른 글
3회 - Ch.5 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 (0) | 2023.02.07 |
---|---|
3회 - Ch4 머스테치로 화면 구성하기 (0) | 2023.02.03 |
2회 Ch3. 스프링부트에서 JPA로 데이터베이스를 다뤄보자 (0) | 2023.01.27 |
Gradle이란? (0) | 2023.01.23 |
1회 - Ch1. 인텔리제이로 스프링부트 시작하기 (0) | 2023.01.20 |