테스트 코드 개념 익히기
유지보수에도 매우 좋고, 코드 수정 시 기존 기능이 제대로 작동하지 않을까봐 걱정하지 않아도 된다는 장점이 있는
테스트 코드 공부를 해야한다.
테스트 코드란?
- 테스트 코드에도 다양한 패턴이 있다.
- 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(saveId).get();
assertThat(savedMenu.getName()).isEqualTo(name);
assertThat(saveMenu.getPrice()).isEqualTo(price);
}
Given: 메뉴를 저장힉 위해 준비하는 과정인
When : 메뉴를 저장하는
Then: 메뉴가 잘 추가되었는지 검증하는
- 이중에 JUnit 과 AssertJ를 가장 많이 사용한다.
JUnit 이란?
- JUnit은 자바 언어를 위한 단위 테스트 프레임 워크이다.
- 단위 테스트란, 작성한 코드가 의도대로 작동하는지 작은 단위로 검증하는 것을 의미
- JUnit을 사용하면 단위 테스트를 작성하고 테스트를 하는데 도움을 준다.
- 사용법이 간단해 쉽게 익힐 수 있다.
- 테스트 결과가 직관적이라 좋다.
- 구체적인 JUnit의 특징은 다음과 같다.
Junit의 특징
- 테스트 방식을 구분할 수 있는 애너테이션을 제공
- @Test do너테이션으로 메서드를 호출할 때마다 새 인스턴스를 생성, 독립 테스트 가능
- 예상 결과를 검증하는 어설션 메서드 제공
- 사용 방법이 단순, 테스트 코드 작성 시간이 적음
- 자동 실행, 자체 결과를 확인하고 즉각적인 피드백을 제공
Junit으로 단위 테스트 코드 만들기
[src -> test -> java] 폴더에 JUnitTest.java 파일 생성
public class JUnitTest {
@DisplayName("1 + 2smㄴ 3이다.") // 테스트 이름
@Test // 테스트 메서드
public void junitTest(){
int a = 1;
int b = 2;
int sum = 3;
Assertions.assertEquals(a + b, sum); // 값이 같은지 확인
}
}
- @DisplayName 애너테이션은 테스트 이름을 명시
- @Test 애너테이션을 붙인 메서드는 테스트를 수행하는 메서드
- JUnit은 테스트끼리 영향을 주지 않도록 각 테스트를 실행할 때마다 테스트를 위한 실행 객체를 만들고 테스트가 종료되면 실행 객체를 삭제한다.
- assertEquals() 메서드의 첫 번째 인수에는 기대하는 값, 두번째 인수에는 실제로 검증할 값을 넣어준다.
JUnitCycleTest 테스트 파일의 사이클을 확인하자.
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("test3");
}
@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이 아니어야한다.
- 각 테스트 케이스를 종료하기 전 매번 실행
@BeforeAll -> @BeforeEach -> @Test_1 -> @Test_2 -> @Test_3 -> @AfterEach
생명주기로 테스트가 진행
AssertJ로 검증문 가독성 높이기
- Assertion은 기댓값과 실제 비교값을 명시하지 않으므로 비교 대상이 헷갈린다.
- 가독성이 안좋다. Assertion
Assertions.assertEquals(a + b, sum);
- 가독성이 좋은 AssertJ
assertThat(a + b).isEqualTo(sum);
메서드설명
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보다 작은지 검증 |
테스트 코드 작성
// TestControllerTest.java
@SpringBootTest // 테스트용 애플리 케이션 컨택스트 생성
@AutoContigureMockMvc // MockMvc 생성
class TestControllerTest {
@Autowired
protected MockMvb mockMvc;
@Autowired
private WebApplicationContext context; //웹 환경과 통합, 서블릿 기반의 컴포넌트 스캔
@Autowired
private MemberRepository memberRepository;
@BeforeEach // 테스트 실행 전 실행하는 메서드
public void mockMvcSetUp(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(context) // MockMVC 설정
.build();
}
@AfterEach // 테스트 실행 후 실행하는 메서드
public void cleanUp(){
// 테스트를 실행한 이후에 실행하는 애너테이션
// 테스트종료후에 member 테이블에 있는 데이터들을 모두 삭제
memberRepository.deleteAll();
}
}
- @SpringBootTest
- 메인 애플리케이션 클래스에 추가하는 애너테이션인 @SpringBootApplication이 있는 클래스를 찾고
- 클래스에 포함되어 있는 빈을 찾은 다음
- 테스트용 애플리 케이션 컨텍스트라는 것을 만든다.
- AutoConfigureMockMvb
- MockMvb를 생성하고 자동으로 구성하는 애너테이션
- MockMvb는 어플리케이션을 서버에 배포하지 않고도
- 테스트롤 MVC 환경을 만들어 요청 및 전송, 응답 기능을 제공하는 유틸리티 클래스입니다.
- 컨트롤러를 테스트할 때 사용되는 클래스
- @BeforeEach
- 테스트를 실행하기 전에 실행하는 메서드에 적용하는 애너테이션
- MockMvcSetUp() 메서드를 실행해 MockMvc를 설정해줍니다.
- AfterEach
- 테스트를 실행한 이후에 실행하는 메서드에 적용하는 애너테이션.
- cleanUp() 메서드를 실행해 member 테이블에 있는 데이터들을 모두 삭제
TestController의 로직을 테스트하는 코드를 작성
// TestControllerTest.java
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 생성
@AutoConfigureMockMvc // MockMvb 생성
class TestControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
private WebApplicationContext context; //웹 환경과 통합, 서블릿 기반의 컴포넌트 스캔
@Autowired
private MemberRepository memberRepository; // Member entity와 상호작용할수 있는 Repository연결
@BeforeEach // 테스트 실행 전 실행하는 메서드, MockMVC 설정
public void mockMvcSetUp(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(context) // MockMVC 설정
.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) // 블로그글 추가 API에 요청을 보낸다.
.accept(MediaType.APPLICATION_JSON) // 요청타입은. JSON이다.
);
// then
result
.andExpect(status().isOk()) // 응답 코드가 200인지 확인
// 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다.
.andExpect(jsonPath("$[0].id").value(savedMember.getId()))
.andExpect(jsonPath("$[0].name").value(savedMember.getName()));
}
}
테스트 방법론내용
Given | 멤버를 저장합니다. |
When | 멤버 리스트를 조회하는 API를 호출합니다. |
Then | 응답 코드가 200 OK이고, 반환받은 값 중에 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다. |
MockMVC에 대한 메서드 설명
// MockMVC에 대한 메서드 설명
final ResultActions result = mockMvc.perform(get(url) // 블로그글 추가 API에 요청을 보낸다.
.accept(MediaType.APPLICATION_JSON) // 요청타입은. JSON이다.
);
result
.andExpect(status().isOk()) // 응답 코드가 200인지 확인
// 0번째 요소의 id와 name이 저장된 값과 같은지 확인합니다.
.andExpect(jsonPath("$[0].id").value(savedMember.getId()))
.andExpect(jsonPath("$[0].name").value(savedMember.getName()));
- Perform() 메서드는
- 요청을 전송하는 역할을 하는 메서드
- ResultActions 객체를 받으며
- ResultActions 객체는 반환값을 검증하고 확인하는 andExpect() 메서드를 제공
- Accept() 메서드는
- 요청을 보낼 때 무슨 타입으로 응답을 받을지 결정하는 메서드입니다.
- JSON, XML 들 다양한 타입이 있지만
- 여기서는 JSON을 받는다고 명시하자.
- andExpect() 메서드는
- 응답을 검증한다.
- TestController에서 만든 API는 응답으로 OK(200)을 반환하므로 이에 해당하는 메서드인
- isOk를 사용해 응답 코드가 OK(200)인지 확인합니다.
- jsonPath("$[0].${필드명}") ( OR , jsonPath("$.${필드명}") )은
- JSON 응닶값의 값을 가져오는 역할을 하는 메서드입니다.
- 0번째 배열에 들어있는 객체의 id, name값을 가져오고
- 저장된 값과 같은지 확인합니다.
- JSON 응닶값의 값을 가져오는 역할을 하는 메서드입니다.
테스트 프레임워크에서 HTTP응답 코드를 검증하는 데 사용하는 메서드
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번대 응답 코드인지 검증 |
4장 핵심요약
- 테스트 코드를 작성하면 코드의 기능이 제대로 작동한다는 것을 검증할 수 있다.
- 테스트코드는 보통 테스트를 준비하는 given
- 테스트를 실제로 진행하는 when
- 테스트 결과를 검증하는 then
- JUnit은 단위 테스를 할 때 사용하는 자바 테스트 프레임워크입니다. - 테스트 에너테이션의 흐름은
- @BeforeAll 애너테이션으로 설정한 메서드가 실행되고
- 그 이후에는 테스트 케이스 개수만큼 @BeforeEach -> @Test -> @AfterEach의 생명주기를 가지고 실행합니다.
- 모든 테스트가 완료되면 마지막으로 @AfterAll 애너테이션으로 설정한 메서드가 실행되고 종료됩니다.
- AssertJ는
- JUnit과 함께 사용해 검증문의 가독성을 확 높여주는 라이브러리입니다.
'개인공부 > 스프링 부트 3 백엔드 개발자 되기' 카테고리의 다른 글
스프링 부트 - 6장 레벨2 스프링 부트 3로 블로그 제대로 만들기 (0) | 2023.07.04 |
---|---|
스프링 부트 - 5장_데이터베이스 조작이 편해지는 ORM (0) | 2023.06.29 |
스프링 부트 - 3장_스프링부트3 구조 이해하기 (0) | 2023.06.25 |
스프링 부트 - 2장_3_스프링부트3 둘러보기 (0) | 2023.06.25 |
스프링 부트 - 2장_2 스프링 부트 3시작하기 (0) | 2023.06.23 |