
안녕하세요🐱
오늘은 Spring boot의 테스트 코드에 대해서 알아볼텐데요
개발에 있어서 테스트 과정이란 매우 중요하다는 건 다들 아실겁니다.
지난 포스트에서 테스트 코드를 한 번 다룬 적이 있었지만, 작성 방식의 패턴이나 자주 사용하는 어노테이션에 대한 내용은 부족했기 때문에, 오늘 그 내용들을 마저 공부하고 정리해보겠습니다!
테스트 코드 패턴을 알아보기 전에, TDD에 대한 개념을 먼저 알아보겠습니다.
TDD란, Test Driven Development의 약자로, 테스트 주도 개발을 의미합니다.
쉽게 말해, 메소드나 함수를 작성할 때 작성 종료 조건을 먼저 정해놓고 코딩을 시작하는 방식이라고 생각하시면 편합니다.

RED : 항상 실패하는 테스트를 먼저 작성GREEN : 테스트에 통과하는 프로덕션 코드 작성REFACTOR : 테스트가 통과하면 프로덕션 코드를 리팩토링위의 사이클처럼 테스트를 먼저 작성하고, 그 테스트를 통과하는 코드를 만들고, 해당 과정을 반복하면서
제대로 동작하는지에 대한 피드백을 적극적으로 받는 방식입니다.
TDD를 사용하면 이러한 이점이 있습니다.
우리가 오늘 살펴볼 테스트 코드 패턴은 TDD를 수행할 때 매우 유용한 방법론 중 하나입니다. 그렇기 때문에 TDD에 대해서 이렇게 한 번 알아보았습니다.
테스트 코드를 작성할 때 사용할 수 있는 패턴은 다양한데, 그 중 가장 보편적으로 사용되는 given-when-then 패턴에 대해 알아보겠습니다.
given-when-then 패턴은 TDD 주도 개발과 BDD 주도 개발에서 많이 쓰이는 패턴으로,
아래의 세 단계로 구분해 작성하는 방식을 말합니다.
given : 테스트 실행을 준비하는 단계when : 테스트를 진행하는 단계then : 테스트 결과를 검증하는 단계예를 들면 다음과 같습니다.
@DisplayName("새로운 멤버를 저장한다.")
@Test
public void saveMemberTest() {
// given : 멤버를 저장하기 위한 준비 과정
final String name = "b1uesoda";
final int age = 25;
final Member member = new Member(name, age);
// when : 실제로 멤버를 저장
final long savedId = memberService.save(member);
// then : 멤버가 잘 추가되었는지 확인
final Member savedMember = memberService.findById(savedId).get();
assertThat(savedMember.getName()).isEqualTo(name);
assertThat(savedMember.getAge()).isEqualTo(age);
}
코드를 보면 given, when, then 이렇게 세 부분으로 나누어져 있는 것을 볼 수 있습니다.
given절when절then절테스트 코드의 패턴은 이정도로 감을 잡고, 이제 테스트를 위한 도구를 살펴보겠습니다.

spring-boot-starter-test는 애플리케이션을 테스트하기 위한 다양한 도구를 제공합니다.
JUnit : 자바 프로그래밍 언어용 단위 테스트 프레임워크
Spring Test & Spring Boot Test : 스프링 부트 애플리케이션을 위한 통합 테스트 지원
AssertJ : 검증문인 어설션을 작성하는데 사용되는 라이브러리
Mockito : 테스트에 사용할 가짜 객체인 목 객체를 쉽게 만들고, 관리하고, 검증할 수 있게 지원하는 테스트 프레임워크
JSONassert : JSON용 어설션 라이브러리
JsonPath : JSON 데이터에서 특정 데이터를 선택하고 검색하기 위한 라이브러리
Hamcrest : Matcher Object 라이브러리로 필터, 검색등을 위해 값을 비교할 때 좀 더 편리하게 사용
이 중에서 JUnit과 AssertJ에 대해서 자세히 알아보겠습니다.
JUnit은 자바 언어를 위한 단위 테스트 프레임워크입니다.
여기서 단위 테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미하고, 보통 단위는 메서드가 됩니다.
JUnit의 특징은 다음과 같습니다.
@Test 어노테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트 가능JUnit을 사용해 작성한 테스트 코드의 예시를 보겠습니다.
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
public class JUnitTest {
@DisplayName("1 + 2는 3이다") // 테스트 이름 명시
@Test
public void junitTest(){
int a = 1;
int b = 2;
int sum = 3;
Assertions.assertEquals(a + b, sum); // 값이 같은지 확인
}
}
JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고 테스트가 종료되면 실행 객체를 삭제합니다.
이 테스트에서는 JUnit에서 제공하는 검증 메서드인 assertEquals()로 a + b와 sum의 값이 같은지 확인합니다.
이번에는 JUnit에서 자주 사용되는 어노테이션에 대해서 알아보겠습니다.
import org.junit.jupiter.api.*;
@BeforeAll // 전체 테스트를 시작하기 전에 1회 실행하므로 메서드는 static으로 선언
static void beforeAll() {
System.out.println("@BeforeAll");
}
@BeforeEach // 테스트 케이스를 시작하기 전마다 실행
public void beforeEach() {
System.out.println("@BeforeEach");
}
@Test
public void test1() {
System.out.println("test1");
}
@Test
public void test2() {
System.out.println("test2");
}
@Test
public void test3() {
System.out.println("test3");
}
@AfterAll // 전체 테스트를 마치고 종료하기 전에 1회 실행하므로 메서드는 static으로 선언
static void afterAll() {
System.out.println("@AfterAll");
}
@AfterEach // 테스트 케이스를 종료하기 전마다 실행
public void afterEach() {
System.out.println("@AfterEach");
}
@BeforeAll
전체 테스트를 시작하기 전에 처음으로 한번만 실행합니다. 전체 테스트 실행 주기에서 한 번만 호출되기 때문에 static으로 선언합니다.
ex ) 데이터베이스 연결 및 테스트 환경 초기화
@BeforeEach
각 테스트 케이스를 시작하기 전에 매번 실행됩니다. 각 인스턴스에 대해 메서드를 호출하므로 static이 아니어야 합니다.
ex ) 테스트 메서드에서 사용하는 객체 및 데이터 초기화
@AfterAll
전체 테스트를 마치고 종료하기 전에 한번만 실행합니다. 전체 테스트 실행 주기에서 한 번만 호출되므로 static으로 선언합니다.
ex ) 데이터베이스 연결 종료
@AfterEach
각 테스트 케이스를 종료하기 전 매번 실행합니다.
@BeforeEach와 마찬가지로 static이 아니어야 합니다.
ex ) 테스트 이후 특정 데이터 삭제

그럼 이 테스트 코드를 직접 실행한 결과를 살펴보겠습니다.

결과를 보면 @BeforeAll로 설정한 메서드가 먼저 실행되고, 그 이후에는 테스트 케이스 개수만큼 @BeforeEach→@Test→@AfterEach의 생명주기로 테스트가 진행되는 것을 볼 수 있습니다.
그리고 모든 테스트가 케이스가 끝나면 @AfterAll로 설정한 메서드를 실행하고 종료하는 것까지을 확인할 수 있습니다.
AssertJ는 JUnit과 함께 사용하여 검증문의 가독성을 높여주는 라이브러리입니다.
아까 들었던 예시를 다시 보면 기댓값과 비교값을 한 눈에 파악하기 조금 어려워 보이죠?
Assertions.assertEquals(a + b, sum);
여기다가 AssertJ를 적용하면 이렇게 바꿀 수 있습니다.
assertThat(a+b).isEqualTo(sum);
a와 b를 더한 값이 sum과 같아야 한다는 의미를 훨씬 명확하게 표현할 수 있습니다. 훨씬 가독성이 좋죠?
이 isEqualTo()외에도 다양한 메서드들이 있습니다.
한 번 살펴보죠!
| 메서드 이름 | 설명 |
|---|---|
| isEqualTo(A) | A값과 같은지 검증 |
| isNotEqualTo(A) | A값과 다른지 검증 |
| contains(A) | A값을 포함하는지 검증 |
| doesNotContain(A) | A값을 포함하지 않는지 검증 |
| startsWith(A) | 접두사가 A인지 검증 |
| endsWith(A) | 접미사가 A인지 검증 |
| isEmpty() | 비어있는 값인지 검증 |
| isNotEmpty() | 비어있지 않은 값인지 검증 |
| isPositive() | 양수인지 검증 |
| isNegative() | 음수인지 검증 |
| isGreaterThan() | 1보다 큰 값인지 검증 |
| isLessThan(1) | 1보다 작은 값인지 검증 |
이제 앞서 설명한 given-when-then 패턴을 적용해 테스트 코드를 한 번 작성해 보겠습니다.
저는 id와 name을 가진 Member엔티티의 리스트를 최종적으로 반환하는 TestController를 이렇게 미리 만들어 놓았는데요, 이 클래스에 대한 테스트 코드를 만들어 보았습니다.
package com.b1uesoda.myo;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 생성
@AutoConfigureMockMvc // MockMvc 생성 및 자동 구성
class TestControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@Autowired
private MemberRepository memberRepository;
@BeforeEach // 테스트 실행 전 실행하는 메서드
public void mockMvcSetup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@AfterEach // 테스트 실행 후 실행하는 메서드
public void cleanUp() {
memberRepository.deleteAll();
}
}
각각의 어노테이션의 역할에 대해 살펴보겠습니다.
@SpringBootTest
메인 어노테이션인 @SpringBootApplication이 있는 클래스를 찾고 그 클래스에 포함되어 있는 빈을 찾은 다음 테스트용 애플리케이션 컨텍스트라는 것을 만듭니다.
@AutoConfigureMockMvc
MockMvc를 생성하고 자동으로 구성하는 어노테이션입니다. MockMvc는 애플리케이션을 서버에 배포하지 않고도 테스트용 MVC 환경을 만들어 요청 및 전송, 응답 기능을 제공하는 유틸리티 클래스입니다. 즉, 컨트롤러를 테스트할 때 사용되는 클래스입니다.
@WebMvcTest와 비슷하지만, 가장 큰 차이점은 컨트롤러 뿐 만 아니라 테스트 대상이 아닌 @Service나 @Repository가 붙은 객체들도 모두 메모리에 올립니다.
따라서 간단한 테스트라면 @WebMvcTest를, MockMVC를 보다 세밀하게 제어하고, 전체 애플리케이션 구성을 로드하고 MockMVC를 사용해야 한다면 @AutoConfigureMockMvc를 사용합니다.
@BeforeEach
앞서 설명했듯이, 테스트를 실행하기 전에 실행하는 메서드에 적용하는 어노테이션입니다. 여기서는 MockMvcSetUp() 메서드를 실행해 MockMvc를 설정해줍니다.
@AfterEach
테스트를 실행한 이후에 실행하는 메서드에 적용하는 어노테이션입니다. 여기서는 cleanUp()메소드를 실행해 member테이블에 있는 데이터를 모두 삭제해줍니다.
이렇게 작성하고 나면 테스트 코드의 작성이 완료된 것입니다.
이제 해당 로직을 테스트하는 코드를 이어서 작성하겠습니다.
@DisplayName("getAllMembers: 아티클 조회에 성공한다.")
@Test
public void getAllMembers() throws Exception {
// given
final String url = "/test";
Member savedMember = memberRepository.save(new Member(1L, "홍길동"));
// when
final ResultActions result = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON));
// then
result
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].id").value(savedMember.getId()))
.andExpect(jsonPath("$[0].name").value(savedMember.getName()));
}
perform() : 요청을 전송하는 역할을 합니다. 결과로 ResultActions 객체를 받으며, ResultActions 객체는 반환값ㅇ르 검증하고 확인하는 andExcept() 메서드를 제공합니다.
accept() : 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드입니다. JSON, XML 등 다양한 타입이 있지만, 여기서는 JSON을 받는다고 명시해두도록 합니다.
andExpect() : 응답을 검증합니다. TestController에서 만든 API는 응답으로 OK(200)을 반환하므로 이에 해당하는 메서드인 isOk를 사용해 응답 코드가 OK(200)인지 확인합니다.
jsonPath("$[0].${필드명}") : JSON 응답값의 값을 가져오는 역할을 하는 메서드입니다. 0번째 배열에 들어 있는 객체의 id, name값을 가져오고, 저장된 값과 같은지 확인합니다.
실제로 테스트를 실행해 보니 잘 작동하는 것을 확인할 수 있었습니다.

이렇게 오늘은 테스트 코드의 given-when-then 패턴에 대해서 알아보고, JUnit과 AssetJ를 사용해 간단한 테스트 코드를 작성해 보았습니다.
개발에 있어서 중요한 단계인 만큼, 연습을 많이 해서 익숙해져야 할 필요가 있어 보입니다.👍
읽어주셔서 감사합니다.🐱
📚Reference
Spring Boot Test(given-when-then)
Spring Test - 골든래빗