1. 테스트 코드 개념 익히기
◉ 테스트 코드 : 작성한 코드가 의도대로 잘 동작하고 예상치 못한 문제가 없는지 확인할 목적으로 작성하는 코드
1) 테스트 코드란?
▪︎ 테스트 코드 : test 디렉터리에서 작업
▪︎ 테스트 코드의 다양한 패턴 중 given-when-then 패턴 이용
→ 테스트 코드를 세 단계로 구분해 작성하는 방식
① given : 테스트 실행을 준비하는 단계
② when : 테스트를 진행하는 단계
③ then : 테스트 결과를 검증하는 단계
2. 스프링 부트 3와 테스트
◉ 스프링 부트는 애플리케이션을 테스트하기 위한 도구와 애너테이션을 제공함
▪︎ spring-boot-starter-test 스타터의 테스트 도구 목록
• JUnit : 자바 프로그래밍 언어용 단위 테스트 프레임 워크
• Spring Test & Spring Boot Test : 스프링 부트 애플리케이션을 위한 통합 테스트 지원
• AssertJ : 검증문인 어셜션을 작성하는데 사용되는 라이브러리
• Hamcrest : 표현식을 보다 이해하기 쉽게 만드는데 사용되는 Matcher 라이브러리
• Mockito : 테스트에 사용할 가짜 객체인 목 객체를 쉽게 만들고, 관리하고, 검증할 수 있게 지원하는 테스트 프레임 워크
• JSONassert : JSON용 어설션 라이브러리
• JsonPath : JSON 데이터에서 특정 데이터를 선택하고 검색하기 위한 라이브러리
1) JUnit이란?
▪︎ JUnit : 자바 언어를 위한 단위 테스트 프레임 워크
- 단위 테스트 : 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미 ( 보통 단위로 메서드가 됨 )
▪︎ JUnit특징
❶ 테스트 방식을 구분할 수 있는 애너테이션을 제공
❷ @Test 애너테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트 가능
❸ 예상 결과를 검증하는 어셜션 메서드 제공
❹ 사용 방법이 단순, 테스트 코드 작성 시간이 적음
❺ 자동 실행, 자체 결과를 확인하고 즉각적인 피드백을 제공
⇒ 테스트끼리 영향을 주지 않도록, 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고 테스트 종료 시 실행 객체를 삭제함
◉ Junit으로 단위 테스트 코드 만들기
① src/test/java 폴더에 'JUnitTest.java' 파일 생성
package org.choongang;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
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);
}
// 테스트가 실패한 경우
@DisplayName("1 + 3는 4이다")
@Test
public void junitFailedTest() {
int a = 1;
int b = 3;
int sum = 3;
// 예측값과 계산값이 달라 실패하는 케이스
Assertions.assertEquals(a + b, sum);
}
}
▪︎ @DisplayName : 테스트 이름 명시
▪︎ @Test : 테스트를 수행하는 메서드임을 알려줌
▪︎ junitTest( ) 메서드에 작성한 테스트 코드 설명
- Assertions 클래스 : JUnit에서 제공하는 검증 메서드인 다양한 종류의 assert 메서드가 존재
→ 그 중 assertEquals( )로 a + b와 sum의 값이 같은지 확인
( 첫 번째 인수 : 기대하는 값, 두 번째 인수 : 실제로 검증할 값 입력 )
② 실제로 테스트 코드가 잘 동작하는지 확인해보자
※ IntelliJ의 경우, unit 단위로 테스트 가능 / 콘솔창에 테스트 결과 출력
이클립스의 경우, 메서드 클릭 후 오른쪽 클릭 - Run As - JUnit Test 실행 가능
▪︎ 테스트 결과, 성공 여부 및 테스트 케이스의 이름, 테스트 실행 시간 정보 확인 가능
→ JUnit Test 창에서 색으로 표시됨 ( 성공 : 녹색 / 실패 : 갈색 / 에러 : 빨간색 )
단, 테스트 케이스가 하나라도 실패 시, 전체 테스트를 실패한 것으로 보여줌
③ 자주 사용하는 JUnit 애너테이션에 대해 알아보자
- JUnit : 각 테스트에 대해 객체를 만들어 독립적으로 실행
- 테스트 : 애너테이션에 따라 실행 순서가 정해짐
▪︎ JUnitCycleTest.java 생성
package org.choongang;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class JUnitCycleTest {
@BeforeAll
// 전체 테스트를 시작하기 전에 1회 실행 → 메서드는 static으로 선언
static void beforeAll() {
System.out.println("@BeforeAll - 전체 테스트 중 제일 처음 실행");
}
@BeforeEach
// 테스트 케이스를 시작하기 전마다 실행
public void beforeEach() {
System.out.println("@BeforeEach - 이 메서드 실행 후, test 메서드 실행");
}
@Test
public void test1() {
System.out.println("test1 실행 중...");
}
@Test
public void test2() {
System.out.println("test2 실행 중...");
}
@Test
public void test3() {
System.out.println("test3 실행 중...");
}
@AfterEach
// 테스트 케이스를 종료하기 전마다 실행
public void afterEach() {
System.out.println("@AfterEach - test 메서드 실행 후, 이 메서드 실행");
}
@AfterAll
// 전체 테스트를 마치고 종료하기 전에 1회 실행 → 메서드는 static으로 선언
static void afterAll() {
System.out.println("@AfterAll - 전체 테스트 중 제일 마지막에 실행");
}
}
▪︎ 실행하기 전, 다양한 애너테이션 설명
❶ @BeforeAll
- 전체 테스트를 시작하기 전에 처음으로 한 번만 실행
- 데이터베이스를 연결해야 하거나 테스트 환경을 초기화할 때 사용
- 전체 테스트 주기에서 한번만 호출되어야 함 → stati으로 선언
❷ @BeforeEach
- 테스트 케이스를 시작하기 전에 매번 실행
- 테스트 메서드에서 사용하는 객체를 초기화하거나 테스트에 필요한 값을 미리 넣을 때 사용
- 각 인스턴스에 대해 메서드를 호출해야 하므로 static으로 선언하지 않음
❸ @AfterAll
- 전체 테스트를 마치고 종료하기 전에 한 번만 실행
- 데이터베이스 연결을 종료할 때나 공통적으로 사용하는 자원을 해제할 때 사용
- 전체 테스트 주기에서 한번만 호출되어야 함 → stati으로 선언
❹ @AfterEach
- 각 테스트 케이스를 종료하기 전 매번 실행
- 테스트 이후에 특정 데이터를 삭제해야 하는 경우 사용
- @BeforeEach와 마찬가지로 static으로 선언하지 않음
④ 테스트 코드를 실행해서 출력 결과를 살펴보자
▪︎ 애너테이션을 중심으로 JUnit의 실행 흐름
@BeforeEach → @BeforeEach → @Test → @AfterEach → @AfterAll
클래스 레벨 설정 메서드 레벨 설정 메서드 레벨 설정 클래스 레벨 설정
⎣ 테스트의 개수만큼 반복 ⎦
⇒ @Test 로 선언된 테스트 메서드만 확인됨 / 콘솔창에서는 전체 확인 가능
◉ AssertJ로 검증문 가독성 높이기
▪︎ AssertJ : JUnit과 함께 사용해 검증문의 가독성을 확 높여주는 라이브러리
( JUnit : 자바 프로그래밍 언어용 단위 테스트 프레임워크 )
▪︎ 앞에서 작성한 테스트 코드의 Assertion은 기대값과 실제값을 명시하지 않으므로, 비교 대상이 헷갈리는 경우가 있음
// 기댓값과 비교값이 잘 구분되지 않는 Assertion 예
Assertions.assertEquals( a + b, sum );
▪︎ 대규모 프로젝트에서는 더 명확한 코드를 이용해 실수를 줄여 가독성을 높여야 함
이럴 경우, AssertJ를 적용해보자
// 가독성이 좋은 AssertJ 예
assertThat( a + b ).isEqualTo( sum );
→ a와 b를 더한 값이 sum과 같아야 한다는 의미로 명확하게 읽힘
▪︎ AssertJ의 다양한 메서드
메서드 이름 | 설명 |
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보다 작은 값인지 검증 |
3. 제대로 테스트 코드 작성해보기
① TestController.java 파일에 대한 test 파일 생성
▪︎ 이클립스 : 해당 컨트롤러 - 오른쪽 클릭 - New - Other - test 입력 후, JUnit Test Case 클릭
→ 컨트롤러와 동일한 이름에 Test가 붙은 java 파일 생성
▪︎ TestControllerTest.java
package org.choongang.controller;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.choongang.domain.Member;
import org.choongang.domain.MemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
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.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 생성
@AutoConfigureMockMvc // MockMvc 생성
class LayerControllerTest {
@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
- 테스트를 실행하기 전에 실행하는 메서드에 적용하는 애너테이션
→ 여기서는 MockMvcSetUp( ) 메서드를 실행해 MockMvc를 설정
❹ @AfterEach
- 테스트를 실행한 이후에 실행하는 메서드에 적용하는 애너테이션
→ 여기서는 cleanUp( ) 메서드를 실행해 member 테이블에 있는 데이터들을 모두 삭제함
② TestController의 로직을 테스트하는 코드를 작성해보자
▪︎ TestControllerTest.java 즉, 같은 파일의 @AfterEach 이후에 코드 추가 작성
// ------------------- 테스트메서드 -------------------
@DisplayName("getAllMembers : 아티클 조회에 성공한다.")
@Test
public void getAllMembers() throws Exception{
// given : 멤버를 저장
final String url = "/hello";
Member savedMember = memberRepository.save(new Member(1L, "홍길동"));
// when : 멤버 리스트를 조회하는 API를 호출
final ResultActions result = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON));
// then : 응답코드가 200 OK이고, 반환 받은 값 중에 0번째 요소의 id와 name이 저장된 값과 같은지 확인
result
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].id").value(savedMember.getId()))
.andExpect(jsonPath("$[0].name").value(savedMember.getName()));
}
▪︎ Given - When -Then 패턴 적용
① given : 멤버 저장
- url 및 모델변수인 memberRepository에 새로운 멤버 생성해 저장 ( 테스트용 데이터 )
② when : 멤버 리스트를 조회하는 API를 호출
- mockMvc의 perform( ) 메서드 : 요청을 전송하는 역할 → 결과로 ResultActions 객체 반환
- accept( ) 메서드 : 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드
→ JSON, XML 등 다양한 타입 존재
③ then : 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 Id와 name이 저장된 값과 같은지 확인
- 위에서 perform( ) 메서드의 결과, ResultActions 객체 반환
→ ResultActions 객체의 andExpect( ) 메서드 : 응답을 검증
→ LayerController에서 만든 API는 응답으로 OK(200)을 반환하므로, 이에 해당하는 메서드인 isOk를 사용해 응답 코드가 OK(200)인지 확인
- 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 Internal Server Error | isInternalServerError() | HTTP 응답 코드가 500 Internal Server Error인지 검증 |
500번대 응답 코드 | is5xxServerError() | HTTP 응답 코드가 500번대 응답 코드인지 검증 |
- 테스트 코드를 작성하면 코드의 기능이 제대로 작동한다는 것을 검증 가능
( 보통 테스트를 준비하는 given / 테스트를 실제로 진행하는 when / 테스트 결과를 검증하는 then )
- JUnit : 단위 테스트를 할 때 사용하는 자바 테스트 프레임 워크
@BeforeAll 애너테이션으로 설정한 메서드가 실행되고,
그 이후에는 테스트 케이스의 개수만큼 '@BeforeEach → @Test → @AfterEach'의 생명주기를 가지고 실행함
모든 테스트가 완료되면 마지막으로 @AfterAll 애너테이션으로 설정한 메서드가 실행되고 종료됨
- AssertJ : JUnit과 함께 사용해 검증문의 가독성을 확 높여주는 라이브러리
'백엔드 > Spring Boot' 카테고리의 다른 글
5. 데이터베이스 조작이 편해지는 ORM (2) - JPA와 하이버네이트, 스프링 데이터와 스프링 데이터 JPA (0) | 2023.07.18 |
---|---|
5. 데이터베이스 조작이 편해지는 ORM (1) - 데이터베이스, ORM (0) | 2023.07.17 |
3. 스프링 부트 3 구조 이해하기 (0) | 2023.07.12 |
2. 스프링 부트 시작하기 (2) - 스프링 부트 둘러보고 코드 이해하기 (0) | 2023.07.11 |
2. 스프링 부트 시작하기 (1) - 스프링 콘셉트 공부 (0) | 2023.07.10 |