0. 테스트 코드란?
테스트 코드는 작성한 코드가 의도대로 잘 동작하고
예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드이다.
□ 테스트 코드를 사용하는 이유
- 버그 방지 및 품질 향상
- 유지보수에 매우 좋음
- 리팩토리 용이성
테스트 코드 패턴
테스트 코드에도 다양한 패턴이 있다.
우리가 다루고자 하는 패턴은 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 saveId = menuService.save(americano)
// then : 메뉴가 잘 추가되었는지 검증
final Menu savedMEnu = menuService.findById(savedId).get();
assertThat(savedMenu.getName()).isEqualTo(name);
assertThat(savedMenu.getPrice()).isEqualTo(price);
}
▶given-when-then 패턴의 예시코드
코드를 보면 세 부분으로 나누어져 있다.
- 메뉴를 저장하기 위해 준비하는 과정인 given 절
- 실제로 메뉴를 저장하는 when 절
- 메뉴가 잘 추가되었는지 검증하는 then 절
1. 스프링 부트 3와 테스트
스프링 부트는 애플리케이션을 테스트하기 위한 도구와 애너테이션을 제공한다.
spring-boot-stater-test 스타터에 테스트를 위한 도구가 모여있다.
- JUnit : 자바 프로그래밍 언어용 단위 테스트 프레임 워크
- Spring Test & Spring Boot Test: 스프링 부트 애플리케이션을 위한 통합 테스트 지원
- AssertJ : 검증문인 어설션을 작성하는 데 사용되는 라이브러리
- Hamcrest
- Mockito
- JSONassert
- JsonPath
이 중에서 JUnit과 AssertJ를 가장 많이 사용한다.
JUnit
JUnit은 자바 언어를 위한 단위 테스트 프레임워크
단위테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미한다.
이때 단위는 보통 메서드가 된다.
JUnit의 특징
- 테스트 방식을 구분할 수 있는 애너테이션을 제공
- @Test 애너테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트 가능
- 예상 결과를 검증하는 어설션 메서드 제공
- 사용 방법이 단순, 테스트 코드 작성 시간이 적음
- 자동 실행, 자체 결과를 확인하고 즉각적인 피드백을 제공
JUnit으로 단위테스트 코드 만들기
1. 코드 작성
위와 같이 src -> test -> java 폴더에 JUnitTest.java 파일을 생성하고,
다음과 같이 코드를 작성한다.
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(sum, a+b); // 값이 같은지 확인
}
}
- @DisplayName
해당 애너테이션은 테스트 이름을 명시한다. - @Test
해당 애너테이션을 붙인 메서드는 테스트를 수행하는 메서드가 된다.
JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고
테스트가 종료되면 실행 객체를 삭제한다.
코드설명:
JUnit에서 제공하는 검증 메서드인 assertEquals()로 a+b와 sum의 값이 같은지 확인한다.
첫 번째 인수에는 기대하는 값, 두 번째 인수에는 실제로 검증할 값을 넣어준다.
2. 코드 검증
JunitTest 왼쪽에 있는 실행버튼을 눌러 테스트를 확인한다.
그러면 위와 같이 성공 여부, 테스트 케이스 이름, 테스트 실행 시간 정보를 확인할 수 있다.
3. 테스트가 실패되는 케이스
public class JUnitTest {
@DisplayName("1 + 2 = 3이다") // 테스트 이름
@Test // 테스트 메서드
public void junitTest(){
int a = 1;
int b = 2;
int sum = 3;
Assertions.assertEquals(sum, a+b); // 값이 같은지 확인
}
@DisplayName("1 + 3 = 4이다")
@Test
public void JunitFailedTest(){
int a = 1;
int b = 3;
int sum = 3;
Assertions.assertEquals(sum, a+b);
}
}
JunitFailedTest()의 코드를 작성한 후 위와 같은 방식으로 test를 실행해 보면
하나의 테스트 케이스가 실패되므로 전체 테스트가 실패된 것으로 결과가 나온다.
4. 자주 사용하는 JUnit 애너테이션 알아보기
import org.junit.jupiter.api.*;
public class JUnitCycleTest {
@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("@AfterAll");
}
@AfterAll // 전체 테스트를 마치고 종료하기 전에 1회 실행하므로 메서드는 static으로 선언
static void afterAll(){
System.out.println("@AfterAll");
}
@AfterEach // 테스트 케이스를 종료하기 전마다 실행
public void afterEach(){
System.out.println("@AfterEach");
}
}
- @BeforeAll
전체 테스트를 시작하기 전에 처음으로 한 번만 실행한다.
예를 들어, 데이터베이스를 연결해야 하거나 테스트 환경을 초기화할 때 사용된다
이 애너테이션은 전체 테스트 실행 주기에서 한 번만 호출되어야 하기 때문에 메서드를 static으로 선언해야 한다. - @BeforeEach
테스트 케이스를 시작하기 전에 매번 실행한다.
예를 들어, 테스트 메서드에서 사용하는 객체를 초기화하거나 테스트에 필요한 값을 미리 넣을 떄 사용할 수 있다.
각 인스턴스에 대해 메서드를 호출해야 하므로 메서드는 static이 아니어야 한다. - @AfterAll
전체 테스트를 마치고 종료하기 전에 한 번만 실행한다.
예를 들어, 데이터베이스 연결을 종료할 때나 공통적으로 사용하는 자원을 해제할 때 사용할 수 있다.
전체 테스트 실행 주기에서 한 번만 호출되어야 하므로 메서드를 static으로 선언해야 한다. - @AfterEach
각 테스트 케이스를 종료하기 전 매번 실행한다.
예를 들어, 테스트 이후에 특정 데이터를 삭제해야 하는 경우 사용한다.
@BeforeEach 애너테이션과 마찬가지로 메서드는 static이 아니어야 한다.
애너테이션을 중심으로 JUnit의 실행 흐름을 살펴보면 위와 같다.
@BeforeEach부터 @AfterEach까지 테스트 개수만큼 반복된 결과물을 볼 수 있다.
실행을 시켜보면,
1. @BeforeAll 애너테이션으로 설정한 메서드 실행
2. @BeforeEach -> @Test -> @AfterEach의 생명주기로 테스트 실행
3. 모든 테스트 케이스가 끝나면 @AfterAll 애너테이션으로 설정한 메서드 실행
위와 같은 흐름을 살펴볼 수 있다.
5. AssertJ로 검증문 가독성 높이기
AssertJ는 JUnit과 함께 사용해 검증문의 가독성을 확 높여주는 라이브러리이다.
앞서 다룬 코드는
Assertions.assertEquals(sum, a+b);
위의 형식대로 작성하였는데,
이와 같이 작성하면 기댓값과 실제 비교값을 명시하지 않으므로 비교 대상이 헷갈린다.
따라서, 가독성을 높이기 위해서는
assertThat(a + b).isEqualTo(sum);
위와 같이 작성하면 a와 b를 더한 값이 sum과 같아야 한다는 의미로 명확하게 읽히기 때문에
코드를 읽는 사람이 헷갈리지 않는다.
AssertJ에서는 값이 같은지 비교하는 위의 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) | 1보다 큰 값인지 검증 |
isLessThan(1) | 1보다 작은 값인지 검증 |
2. 제대로 테스트 코드 작성해 보기
2-1. 테스트 코드 생성하기
우리가 테스트하고자 하는 코드로 이동하여
TestController class에 마우스 커서를 올려놓고 alt + Enter을 누르면 [Create Test] 버튼이 나온다.
확인 버튼을 누르면
위와 같이 TestControllerTest.java 파일이 생성됨을 확인할 수 있다.
2-2. Test코드 작성하기
@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 환경을 만들어 요청 및 전송, 응답 기능을 제공하는 유틸리티 클래스이다. 즉, 컨트롤러를 테스트할 때 사용되는 클래스이다. - @BeforeEach
테스트를 실행하기 전에 실행하는 메서드에 적용하는 애너테이션.
여기서는 MockMvc를 설정해 준다. - @AfterEach
테스트를 실행한 이후에 실행하는 메서드에 적용하는 애너테이션.
여기서는 cleanUp() 메서드를 실행해 member 테이블에 있는 데이터들을 모두 삭제한다.
2-3. 로직 테스트하는 코드 작성
TestController의 로직을 테스트하는 코드를 작성할 것이다.
@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();
}
@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())
//응답의 0번째 값이 DB에 저장한 값과 같은지 확인
.andExpect(jsonPath("$[0].id").value(savedMember.getId()))
.andExpect(jsonPath("$[0].name").value(savedMember.getName()));
}
}
Given | 멤버를 저장한다. |
When | 멤버 리스트를 조회하는 API를 호출한다. |
Then | 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 id와 name이 저장된 값과 같은지 확인한다. |
- perform() 메서드는 요청을 전송하는 역할을 하는 메서드이다.
결과로 ResultActions 객체를 받으며, ResultActions 객체는 반환값을 검증하고 확인하는 andExpect() 메서드를 제공해 준다. - accept() 메서드는 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드이다.
JSON, XML 등 다양한 타입이 있지만, 여기에서는 JSON을 받는다고 명시한다. - andExpect() 메서드는 응답을 검증한다.
TestController에서 만든 API는 응답으로 OK(200)을 반환하므로 이에 해당하는 메서드인 isOk를 사용해 OK(200)인지 확인한다. - jsonPath("$[0].${필드명}")은 JSON 응답값의 값을 가져오는 역할을 하는 메서드이다.
0번째 배열에 들어있는 객체의 id, name값을 가져오고, 저장된 값과 같은지 확인한다.
2-4. 작동 확인하기
'JAVA > SpringBoot 3' 카테고리의 다른 글
[스프링 부트 3] 블로그 만들기 - 1 (블로그 글 작성 API 구현) (4) | 2024.08.07 |
---|---|
[스프링 부트 3] ORM, JPA, 하이버네이트 (0) | 2024.07.30 |
[스프링 부트 3] 구조 이해하기 (프레젠테이션, 비즈니스, 퍼시스턴스 계층) (0) | 2024.07.25 |
[스프링 부트 3] 초기 세팅 (IntelliJ) & Hello World 출력하기 (1) | 2024.07.24 |
[스프링 부트] 기본 개념 (IoC, DI, 빈, 컨테이너, AOP, PSA) (2) | 2024.07.22 |