테스트 코드는 작성된 코드가 의도대로 잘 동작하고 예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드입니다. 보통 스프링 부트 강의와 서적에 테스트 코드도 함께 내용이 담겨 있지만, 턱없이 부족하다고 느꼈습니다.😂
개발자에게 반드시 필요한 테스트 케이스를 프로젝트에 채택한다면, 유지보수에도 매우 좋고, 코드 수정 시 기존 기능이 제대로 작동하지 않을까 봐 걱정하지 않아도 된다는 장점이 있기 때문에 테스트 코드에 대한 내용을 잘 정리하여 프로젝트에 성공적으로 반영하는 것이 목표입니다.✨
테스트 코드는 다양한 패턴이 존재합니다. 주로 인프런과 서적으로 학습하면서 자주 사용한 패턴인 given-when-then
패턴을 채택할 것입니다. given-when-then
패턴은 테스트 코드를 세 단계로 구분하여 작성하는 방식으로 의미는 다음과 같습니다.
종류 | 설명 |
---|---|
given | 테스트 실행을 준비하는 단계 |
when | 테스트를 진행하는 단계 |
then | 테스트 결과를 검증하는 단계 |
예를 들어 새로운 메뉴를 저장하는 코드를 테스트한다고 가정했을 때 given-when-then
패턴의 테스트 코드는 다음과 같습니다.
@Displayname("새로운 메뉴를 저장한다.")
@Test
public void saveMenuTest() {
// given: 메뉴를 저장하기 위한 준비 과정
final String name = "아메리카노";
final int price = 2000;
final Menu americano = new Menu(name, price);
// when : 실제로 메뉴를 저장
final long savedId = menuService.save(americano);
// then : 메뉴가 잘 추가되었는지 검증
final Menu savedMenu = menuService.findById(savedId).get();
assertThat(savedMenu.getName()).isEqualTo(name);
assertThat(savedMenu.getPrice).isEqualTo(price);
}
spring-boot-starter-test
안에 테스트를 위한 다양한 도구가 모여 있습니다. 스프링 부트 스타터 테스트 목록은 다음과 같습니다.
JUnit은 자바 언어를 사용하는 소프트웨어의 단위 테스트를 위한 테스팅 프레임워크입니다. 주로 자바 언어로 작성된 코드의 품질을 보장하고 유지보수성을 높이기 위해 사용됩니다. JUnit은 테스트 케이스를 작성하고 실행하는 데 도움이 되는 강력한 도구로 자동화된 테스트 환경을 제공합니다. JUnit의 특징은 다음과 같습니다.
@Test
애너테이션으로 메서드를 호출할 때마다 새로운 인스턴스를 생성. 독립 테스트가 가능JUnit으로 단위 테스트 코드를 작성합니다. 다음 예제 코드는 간단한 사칙 연산을 활용한 테스트 코드입니다.
public class JUnitTest {
@DisplayName("1 + 2는 3이다.") // 테스트 이름
@Test // 테스트 메서드
public void junitTest() {
int a = 1;
int b = 2;
int sum = 3;
Assertion.assertEquals(a + b, sum); // 값이 동일한지 확인
}
}
JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 생성하고 테스트가 종료되면 실행 객체를 삭제합니다. junitTest()
메서드에 작성한 테스트 코드는 단순하게 JUnit에서 제공하는 검증 메서드인 assertEquals()
로 a + b
와 sum
의 값이 같은지 확인합니다. 따라서 assertEquals()
메서드의 첫 번째 인수는 기대하는 값
, 두 번째 인수는 검증할 값
을 넣어 줍니다.
아래에 @BeforeAll
, @BeforeEach
, @AfterAll
, @AfterEach
애너테이션을 사용하는 간단한 JUnit 테스트 코드 예제를 제시하겠습니다. 이 예제는 간단한 계산기 클래스를 테스트하는 코드입니다.
import org.junit.jupiter.api.*;
public class CalculatorTest {
@BeforeAll
static void setUpClass() {
// BeforeAll : 테스트 클래스 수준에서 한 번만 실행되는 초기화 작업
System.out.println("Before all tests");
}
@BeforeEach
void setUp() {
// BeforeEach : 각 테스트 메서드 전에 실행되는 초기화 작업
System.out.println("Before each test");
}
@Test
void testAddition() {
int result = Calculator.add(3, 5);
Assertions.assertEquals(8, result);
}
@Test
void testSubtraction() {
int result = Calculator.subtract(8, 3);
Assertions.assertEquals(5, result);
}
@AfterEach
void tearDown() {
// AfterEach : 각 테스트 메서드 후에 실행되는 정리 작업
System.out.println("After each test");
}
@AfterAll
static void tearDownClass() {
// AfterAll : 테스트 클래스 수준에서 한 번만 실행되는 정리 작업
System.out.println("After all tests");
}
}
위의 예제에서:
@BeforeAll
static
선언 필수)@BeforeEach
static
선언 불가능)@AfterEach
각 테스트 메서드 후에 실행되는 정리 메서드를 정의합니다. (static
선언 불가능)
테스트 이후에 특정 데이터를 삭제해야 하는 경우 사용합니다.
@AfterAll
테스트 클래스 수준에서 한 번만 실행되는 정리 메서드를 정의합니다. (static
선언 필수)
데이터베이스 연결을 종료할 때나 공통적으로 사용하는 자원을 해제할 때 사용할 수 있습니다.
@Test
는 각 테스트 메서드를 정의합니다.
이러한 애너테이션을 사용하면 테스트의 전체 수명 주기 동안 특정한 초기화 및 정리 작업을 수행할 수 있습니다.
AssertJ는 JUnit과 함께 사용해 검증문의 가독성을 높여주는 라이브러리입니다. 예를 들어 앞서 작성한 테스트 코드의 Assertion은 기댓값과 실제 비교값을 명시하지 않으므로 비교 대상이 헷갈립니다. 예를 들어 다음 코드를 보면 기댓값과 비교값이 잘 구분되지 않습니다.
Assertion.assertEquals(a + b, sum);
AssertJ를 사용하면 다음과 같습니다.
assertThat(a + b).isEqualTo(sum);
이 경우 a와 b를 더한 값이 sum과 같아야 한다는 의미로 명확하게 읽히기 때문에 코드를 읽는 사람이 헷갈리지 않습니다. AssertJ에는 값이 같은지 비교하는 isEqualTo(), isNotEqualTo()
메서드 말고도 다양한 메서드도 제공합니다. AssertJ에서 제공하는 자주 사용되는 메서드는 다음과 같습니다.
메서드 이름 | 설명 |
---|---|
isEqualTo(A) | A 값과 같은지 검증 |
isNotEqualTo(A) | A 값과 다른지 검증 |
contains(A) | A 값을 포함하는지 검증 |
doesNotContain(A) | A 값을 포함하지 않는지 검증 |
startWith(A) | 접두사가 A인지 검증 |
endWith(A) | 접미사가 A인지 검증 |
isEmpty() | 비어있는 값인지 검증 |
isNotEmpty() | 비어있지 않은 값인지 검증 |
isPositive() | 양수인지 검증 |
isNegative() | 음수인지 검증 |
isGreaterThan(1) | 1보다 큰 값인지 검증 |
isLessThan(1) | 1보다 작은 값인지 검증 |
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 개발
@AutoConfigureMockMvc // MockMvc 생성
class TestControllerTest {
@Autowired protected MockMvc mockMvc;
@Autowired WebApplicationContext context;
@Autowired MemberRepository memberRepository;
@BeforeEach // 테스트 실행 전 실행하는 메서드
public void mockMvcSetUp() {
this.mockMvc = MockBuilders.webAppContextSetup(context).build();
}
@AfterEach // 테스트 실행 후 실행하는 메서드
public void CleanUp() {
memberRepository.deleteAll();
}
}
@SpringBootTest
@SpringBootTest
애너테이션은 메인 애플리케이션 클래스에 추가하는 애너테이션인 @SpringBootApplication
이 있는 클래스를 습니다. 그리고 해당 클래스에 포함되어 있는 빈을 찾은 다음 테스트용 애플리케이션 컨텍스트라는 것을 생성합니다.@AutoCOnfigureMockMvc
@AutoCOnfigureMockMvc
는 MockMvc를 생성하고 자동으로 구성하는 애너테이션입니다. MockMvc는 애플리케이션을 서버에 배포하지 않고도 테스트용 MVC 환경을 만들어 요청 및 전송, 응답 가능을 제공하는 유틸리티 클래스입니다. 즉, 컨트롤러를 테스트할 때 사용되는 클래스입니다.@BeforeEach
@BeforeEach
는 테스트를 실행하기 전에 실행하는 메서드에 적용하는 애너테이션입니다. 여기서는 MockMvcSetUp()
메서드를 실행해 MockMvc를 설정해줍니다.@AfterEach
@AfterEach
는 테스트를 실행한 이후에 실행하는 메서드에 적용하는 애너테이션입니다. 여기서는 cleanUp()
메서드를 실행해 member
테이블에 있는 데이터들을 모두 삭제해줍니다.@SpringBootTest // 테스트용 애플리케이션 컨텍스트 개발
@AutoConfigureMockMvc // MockMvc 생성
class TestControllerTest {
// ... 생략(위의 BeforeXXX, AfterXXX 설정하기 코드) ...
@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) // 1.
.accept(MediaType.APPLICATION_JSON)); // 2.
// then
result
.andExpect(status().isOk()) // 3.
// 4. 응답의 0번째 값이 DB에서 저장한 값과 같은지 확인
.andExpect(josnPath("$[0].id").value(savedMember.getId()))
.andExpect(jsonPath("$[0].name").value(savedMember.getName()));
}
}
위의 given-when-then
패턴의 의미는 다음과 같습니다.
종류 | 설명 |
---|---|
given | 멤버를 저장합니다. |
when | 멤버 리스트를 조회하는 API를 호출합니다. |
then | 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다. |
1.
perform()
메서드는 요청을 전송하는 역할을 수행하는 메서드입니다. 결과로 ResultActions
객체를 받으며, ResultActions
객체는 반환값을 검증하고 확인하는 andExpect()
메서드를 제공해줍니다. andExpect()
에 관한 내용은 3.
에서 정리합니다.
2.
accept()
메서드는 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드입니다. JSON, XML 등 다양한 타입이 있지만, 여기에서는 JSON을 받는다고 명시하도록 합니다.
3.
andExpect()
메서드는 응답을 검증합니다. TestController
에서 만든 API는 응답으로 OK(200)을 반환하므로 이에 해당하는 메서드인 isOk()
메서드를 사용해 응답 코드가 OK(200)인지 확인합니다.
4.
jsonPath("$[0].필드명")
은 JSON 응답값의 값을 가져오는 역할을 하는 메서드입니다. 0번째 배열에 들어있는 객체의 id, name값을 가져오고, 저장된 값과 같은지 확인합니다.
HTTP 주요 응답 코드는 다음과 같습니다.
코드 | 매핑 메서드 | 설명 |
---|---|---|
200 OK | isOk() | HTTP 응답 코드가 200 OK인지 검증 |
201 Created | isCreated() | HTTP 응답 코드가 201 Created인지 검증 |
400 Bad Request | isBadRequest() | HTTP 응답 코드가 400 Bad Request인지 검증 |
403 Forbidden | isForbidden() | HTTP 응답 코드가 403 Forbidden인지 검증 |
404 Not Found | isNotFound() | HTTP 응답 코드가 404 Not Found인지 검증 |
400 번대 응답 코드 | is4xxClientError() | HTTP 응답 코드가 400 번대 응답 코드인지 검증 |
500 Interal Server Error | isInteralServerError() | HTTP 응답 코드가 500 Interal Server Error인지 검증 |
500번대 응답 코드 | is5xxServerError() | HTTP 응답 코드가 500번대 응답 코드인지 검증 |