나는 지금까지 단 한번도 프로젝트에 대한 '테스트'를 진행해본 적이 없다. 취업을 준비하는 요즘, 자격요건 혹은 우대사항에 '테스트 경험이 있는 개발자'라는 문구를 자주 보았고, 도대체 테스트가 무엇인지 궁금해졌다.
: 테스트가 주도하는 개발. 즉, 테스트 코드를 먼저 작성한 후 개발을 한다. 자세한 순서는 아래의 레드 그린 사이클을 따른다.
레드 그린 사이클
1. RED : 항상 실패하는 테스트를 먼저 작성
2. GREEN : 테스트가 통과하는 프로덕션 코드를 작성
3. REFACTOR : 테스트가 통과하면 프로덕션 코드를 리팩토링
: TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 의미한다. 테스트 코드를 꼭 먼저 작성해야하는 것이 아니며, 리팩토링이 필수도 아니다.
그저 테스트 코드를 작성하는 것에 의의를 둔다.
먼저, 조금 더 단순한 단위 테스트를 학습해보고자 한다.
나는 이전 Node로 서버를 만들 때, 문제가 발생했을 때 다음과 같은 방식으로 오류를 수정해 나갔다.
매우 귀찮은 방식이긴 했다. 하지만, 단위 테스트를 진행한다면 이러한 번잡스러움을 사전에 차단하여 더 빠른 오류 검증 및 수정을 빠르게 해결이 가능해진다.
만약 기존 A의 기능에 기능 B를 추가한다고 하자. B를 완성하여 서버를 실행시켰는데, 갑자기 A에서 오류가 발생한 것이다. 이러한 상황은 빈번하게 발생한다.
때문에 A에 대한 단위테스트가 있다면, A기능을 검증하여 사전에 A의 오류를 잡아낼 수 있게될 것이다!
주절주절 얘기했지만, 나도 단위 테스트 라는 것을 처음 접하는 입장으로써 어떤 것인지 두루뭉술하게만 이해할 뿐, 완벽하게 와닿진 않는다.😢 일단, 한번 간단한 테스트 코드를 직접 작성해보며 이해해보자!
이전 글에서 만든 프로젝트에서 [src] > [main] > [java] 폴더에 package를 생성하자. package 이름은 주로 사이트의 url을 뒤집어서 만든다고 한다. 나는 url이 없으니 그냥 com.book.springboot 라고 지었다.
새로 생성한 package에 Java 클래스를 생성하자. 클래스 명은 Application 으로 한다.
Application 클래스에 다음과 같이 코드를 작성하자.
package com.boot.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);
}
}
위에서 생성한 package 하위에 web package를 생성하자. 앞으로 모든 컨트롤러와 관련된 클래스는 web package에 생성할 것이다.
web package 하위에 HelloController 클래스를 생성해보자!
HelloController 클래스에 아래와 같은 코드를 작성하자
package com.book.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는 "/hello" url로 Get 요청을 받으면 "hello"를 응답한다. 작성한 코드가 제대로 작동하는지 테스트해보자! 단, 기존의 방식처럼 WAS를 실행시켜서 확인하는 것이 아닌, 테스트 코드로 검증 하는 것이다.
[src] > [test] > [java] 폴더에 위에서 만들어주었던 패키지를 그대로 생성한 후, web package에 HelloControllerTest 클래스를 생성하자. 생성한 클래스에 다음과 같이 코드를 작성하자.
package com.book.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 sun.reflect.annotation.ExceptionProxy;
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));
}
}
import 해오는게 매우 많다.. 하지만, 기능별로 하나씩 이해해보자.
- @RunWith
JUnit에 내장된 실행자 외에 다른 실행자를 실행시키는데, 여기선 SpringRunner 실행자를 사용한다. 즉, 스프링 부터 테스트와 JUnit 사이에 연결자 역할을 한다.- @WebMvcTest
여러 스프링 테스트 어노테이션 중, Web에 집중할 수 있는 어노테이션이다.
이를 호출할 경우 @Controller, @ControllerAdvice 등을 사용할 수 있다.- @Autowired
스프링이 관리하는 빈을 주입받는다.- private MockMvc mvc
웹API를 테스트할 때 사용한다. 이 클래스를 통해 HTTP GET, POST등에 대한 API 테스트를 할 수 있다.- mvc.perform(get("/hello"))
MockMvc를 통해 /hello 주소로 HTTP GET 요청을 한다.- andExpect(status().isOk())
mvc.perform의 결과를 검증한다.
isOk()는 status가 200인지 아닌지를 검증한다.- andExpect(content().string(hello))
응답 본분의 내용을 검증한다.
Controller에서 "hello"를 정상적으로 리턴하는지 검증한다.
테스트 코드를 실행하면 다음과 같이 테스트가 통과된 것을 확인할 수 있다.
Tomcat을 실행하더라도, 정상적으로 hello가 리턴되는 것을 확인할 수 있다. 이처럼 브라우저로 한 번씩 검증은 하되, 테스트 코드로 먼저 검증 후, 확실한 믿음이 필요할 때 프로젝트를 실행하여 확인할 것을 강조한다. 절대, 수동으로 검증 후 테스트 코드를 작성하진 않는다고 한다 ㅎㅎ..
아직은 많이 낯설지만, 이번 기회를 통해 테스트 코드를 작성하는 습관을 길러보고자 한다. ☺️