1. 테스트 코드란
TDD는 테스트가 주도하는 소프트웨어 개발 방법론 을 말합니다. 반면에 단위 테스트는 TDD의 첫번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 말합니다. 단위 테스트는 TDD에서 활용될 수도 있지만, 반드시 TDD 방식으로 개발할 때만 사용되는 것은 아닙니다. 단위테스트는 TDD와 달리 테스트 코드를 먼저 작성해야 할 필요도 없고, 리팩토링도 포함되지 않습니다. 순수 테스트 코드를 작성하는 것을 말합니다.
단위 테스트의 장점
1) 단위 테스트는 개발단계 초기에 문제를 발견하게 도와줍니다.
2) 단위 테스트는 개발자가 나중에 코드를 리팩토랑하거나 라이브러리 업그레이드 등에 서 기존 가능이 올바르게 작동하는지 확인할 수 있습니다 (예시: 회귀 테스트)
3) 단위 테스트는 기능에 대한 불확실성을 감소시킬 수 있습니다. 하
4) 단위 테스트는 시스템에 대한 실제 문서를 제공합니다. 즉, 단위 테스트 자체가 문서 로 사용할 수 있습니다.
테스트 코드를 사용하지 않을 때에 저자는 코드를 작성하고, 프로그램(Tomcat)을 실행한 뒤, Postman과 같은 API 테스트 도구로 HTTP 요청하고, 요청 결과를 System.out.println()으로 눈으로 검증했습니다. 결과가 다르면 다시 프로그램(Tomcat)을 중지하고 코드를 수정하여 앞의 과정을 반복해야 했다고 합니다.
#Apache Tomcat은 Java 기반 웹 애플리케이션을 실행하기 위한 오픈 소스 웹 서버이자 서블릿 컨테이너입니다.
나의 기능을 테스트할 때 많은 자원(시간, 돈)이 드는데 단위별로 기능을 분할하면 일부 기능만 테스트할 수 있습니다. 예를 들어 A기능이 완료되었다면 B기능을 수정하였을 때 영향이 가지 않기 때문에 개발자가 만든 기능이 보호됩니다.
이렇게 새로운 기능이 추가될 떄, 기존 기능이 잘 작동되는 것을 보장해주는 것이 테스트 코드입니다.
이러한 테스크 코드의 작성을 쉽게 해주는 xUnit 프레임워크들은 다음과 같습니다.
JUnit - Java
DBUnit - DB
CppUnit-C++
NUnit -.net
해당 책에서는 JUnit4를 사용하였습니다.
2. test controller 작성해보기
1) main > java 폴더에서 도메인 패키지를 만들어봅시다.
일반적으로 패키지명은 웹사이트 주소의 역순으로 작성합니다. com.ulbbang.book.firstproject 라고 작성하였습니다.
2) Application class
1. Application.class
package com.ulbbang.book.firstproject;
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);
}
}
gradle 동기화(sync)를 진행하여 올바르게 동작하는 지 확인할 수 있습니다.
참고자료)
gradle - intellij coummunity 종속성 sync 하기
build.gradle에 종속성 추가 시, intellij coummunity 버전에서는 자동으로 sync가 되지않음 View - Tool...
blog.naver.com
alt+enter로 패키지 가져오기를 할 수 있습니다.
Application 클래스는 앞으로 만들 프로젝트의 메인 클래스로 동작합니다. @SpringBootApplication으로 스프링 부트의 자동설정, 스프링 Bean 읽기와 생성이 자동으로 설정됩니다.
@SpringBootApplication이 있는 위치부터 설정이 읽히기 때문에 이 클래스는 항상 프로젝트 최상단에 위치해야 합니다.
스프링부트에서는 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있는 내장 WAS를 권장하고 있습니다.
main메소드에서 실행하는 SpringApplication.run은 내장 WAS(Web Application Server)를 실행해줍니다. 이를 통해 항상 서버에 Tomcat을 설치할 필요없이 스프링 부트로 만들어진 Jar 파일(java 패키징 파일)로 바로 실행할 수 있습니다.
2. web 패키지
HelloController.class
package com.ulbbang.book.firstproject.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "hello";
}
}
# gradlew clean build를 터미널에서 친 후
File > Invalidate Caches / Restart로 뭔가 오류를 해결했습니다.
@RestController : 컨트롤러를 JSON으로 반환하는 컨트롤러 로 만들어줍니다. @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용하게 해줍니다.
이전
package com.ulbbang.book.firstproject.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
@ResponseBody
public String sayHello() {
return "hello";
}
}
@GetMapping : HTTP 메소드인 Get 의 요청을 받을 수 있는 API를 만들어줍니다. 예전에는 @RequestMapping(method = RequestMethod.GET)으로 사용되었습니다.
이제 이프로젝트는 /hello로 요청이 오면 문자열 hello를 반환하는 기능을 가지게 되었습니다.
3. test코드 작성하기
/hello로 요청을 보냈을 때 200상태가 되돌아오고, hello를 return하는 것을 확인해보겠습니다.
package com.ulbbang.book.firstproject;
import com.ulbbang.book.firstproject.web.HelloController;
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 helloTest() throws Exception {
String hello = "hello";
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string(hello));
}
}
@RunWith(Spring Runner.class)
• 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킵니다. 여기서는 Spring Runner라는 스프링 실행자를 사용합니다. 즉 스프링 부트 테스트와 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"를 리턴하기 때문에 이 값이 맞는지 검증합니다.
제일 첫번째 Run을 누르고 기다렸을 때 아래와 같은 결과 (test passed)가 나오면 성공입니다. 200상태가 되돌아오고, hello를 return하는 것을 확인했습니다.
이제 Application.java 파일에서 실행해봅시다.
저같은 경우에는 이전에 postgres 서버를 만져보다가 8080포트를 계속 켜놓는(윈도우는 자동으로 갱신됨) 뻘짓...을 해서
- Win + R 키를 눌러 실행 창을 엽니다.
- services.msc를 입력하고 Enter를 누릅니다.
- postgres가 실행중이라면 종료합니다.
- powershell을 관리자 권한으로 열어 netstat -ano | findstr :8080 로 포트를 점유중인 프로세스를 찾고 종료합니다. taskkill /PID 7876 /F
위와 같은 방법으로 해결했습니다.
올바르게 tomcat서버가 8080으로 실행되었다는 로그가 뜨면 성공입니다. 웹 브라우저에 http://localhost:8080/hello 를 쳐서 아래와 같이 나와도 성공입니다.
3. 롬복 설치
롬복은 자주사용하는 Getter, Setter, 기본생성자, toString등을 어노테이션으로 자동 생성해줍니다. 이전에 이클립스를 사용할 때는 jar 파일을 라이브러리에 일일이 등록해주어야 했기 때문에 번거로웠지만 이클립스에서는 간단하게 할 수 있습니다.
1) build.gradle을 다음과 같이 수정하여 줍니다.
buildscript {
ext {
springBootVersion = '2.6.3'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'org.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '11' // Java 11로 변경
targetCompatibility = '11' // Java 11로 변경
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2'
implementation 'org.projectlombok:lombok:1.18.30' // 최신 Lombok 버전 사용
annotationProcessor 'org.projectlombok:lombok:1.18.30' // Lombok 어노테이션 처리기 추가
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'junit:junit:4.13.2'
}
2) gradle을 refresh해서 라이브러리(의존성)를 다운 받습니다.
3) 롬복 플러그인을 설치합니다. (이미 설치되어 있다면 넘어갑니다.)
4) enable annotation processing을 체크하여 롬복에 대해 설정합니다.
설정 전에 인텔리제이를 재시작해야 할 수 있습니다.
4. Hello controller 코드를 롬복 스타일 코드로 바꾸기
이 프로젝트는 작은 규모의 프로젝트이기 때문에 기존 코드를 롬복으로 쉽게 변경할 수 있습니다.
1) dto패키지와 HelloResponseDto 파일을 추가하겠습니다.
package com.ulbbang.book.firstproject.web.dto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
private final String name;
private final int amount;
}
@Getter
선언된 모든 필드의 get 메소드를 생성해줍니다.
@RequiredArgsConstructor
선언된 모든 final필드가 포함된 생성자를 생성해줍니다. final이 없는 필드는 생성자에 포함되지 않습니다.
2)test 코드 추가
package com.ulbbang.book.firstproject.dto;
import com.ulbbang.book.firstproject.web.dto.HelloResponseDto;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class HelloResponseDtoTest {
@Test
public void lombok_test_one() {
//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라는 테스트 검증 라이브러리의 검증 메소드입니다. 검증하고 싶은 대상(dto.getName())을 메소드 인자로 받습니다. 메소드 체이닝이 지원되어 isEqualTo같은 메소드를 이어 사용할 수 있습니다.
Junit에서는 기본 assertThat도 지원해주는 데 Junit은 is()와 같이 CoreMatchers 라이브러리가 필요하고 assertj의 assertThat에 비해 자동원성이 잘 지원되지 않기 때문에 assertj의 assertThat를 사용했습니다.
isEqualTo
assertj의 동등 비교 메서드입니다. assertThat에 있는 값과 isEqualTo의 값을 비교하여 같으면 성공 판정입니다.
자이제 HelloController안에 ResponseDto를 사용하도록 코드를 추가해봅십다.
package com.ulbbang.book.firstproject.web;
import com.ulbbang.book.firstproject.web.dto.HelloResponseDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "hello";
}
@GetMapping("/hello/dto")
public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount) {
return new HelloResponseDto(name, amount);
}
}
RequestParam
외부에서 API로 넘긴 파라미터를 가져오는 어노테이션입니다. 여기서는 외부에서 name(@RequestParam("name"))이란 이름으로 넘긴 파라미터를 메소드 파라미터 name(String name)에 저장하게 됩니다.
helloControllerTest를 다음과 같이 변경합니다.
package com.ulbbang.book.firstproject;
import com.ulbbang.book.firstproject.web.HelloController;
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.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HelloController.class)
public class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
public void helloTest() throws Exception {
String hello = "hello";
mvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string(hello));
}
@Test
public void helloDtoTest() 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)));
}
}
mvc.perform의 .param(,)
API 테스트를 할 때 사용될 요청 파라미터를 설정합니다. 단 값은 String만 허용됩니다. 그래서 숫자, 날짜 등의 데이터를 등록할 때는 문자열로 변경해야 합니다.
jsonPath
JSON 응답값을 필드별로 검증할 수 있는 메소드입니다. $를 기준으로 필드명을 명시합니다. 여기서는 name과 amount를 검증하니 $name, $ amount로 검증합니다.
추가된 API역시 정상적으로 테스트가 통과된 것을 확인할 수 있습니다.
'대외활동 > DRACONIST-백엔드' 카테고리의 다른 글
스프링부트 스터디 2주차. MVC 패턴, springboot initializer, 어노테이션 (0) | 2025.01.11 |
---|---|
2025년 1월 스프링부트 스터디 0주차. 스터디 계획 (0) | 2025.01.10 |
스프링부트 스터디 1주차. 문제 (0) | 2025.01.06 |
스프링부트 스터디 1주차. 3단원 JPA 데이터베이스 (0) | 2025.01.06 |
스프링부트 스터디 1주차. 1단원 인텔리제이 설치 (0) | 2025.01.04 |