저번 프로젝트를 할 때부터 테스트 코드 공부는 꼭 필요하다고 생각했습니다.
제가 만든 API를 검사할 때 인텔리제이를 실행시키고 포스트맨으로 API 호출을 하고 받아온 JSON을 확인하는 형식이었습니다.
만든 API를 검사할 때마다 프로젝트를 재실행해야 한다는 것도 귀찮았고 포스트맨에 URL을 치는 것도 여러 번 치니 너무 귀찮았습니다. 그래서 테스트 코드 공부는 꼭 필요하다고 생각했습니다.
https://product.kyobobook.co.kr/detail/S000201766024
이 책에서 테스트 코드에 대한 설명이 있어 구매해 공부를 해봤습니다.
TDD란?
TDD는 "Test-Driven Development"의 약자로, 테스트 주도 개발을 의미합니다. TDD는 소프트웨어 개발 방법론 중 하나로, 개발자가 애플리케이션의 동작을 정의하는 테스트를 먼저 작성한 후에 해당 테스트를 통과하기 위한 코드를 작성하는 방식입니다.
TDD의 단계
- 테스트 작성 (Test Writing) : 개발자는 기능 또는 유닛 테스트를 작성합니다. 이 단계에서는 해당 기능 또는 모듈이 어떤 동작을 해야 하는지 정의하고, 그에 대한 예상 결과를 담은 테스트 케이스를 작성합니다.
- 테스트 실패 (Test Failure) : 작성된 테스트 케이스를 실행하여 테스트를 실패시킵니다. 이 시점에서는 아직 구현되지 않은 코드이므로, 테스트는 실패해야 합니다.
- 코드 작성 (Code Writing) : 테스트를 통과시키기 위한 최소한의 코드를 작성합니다. 이 단계에서는 테스트를 통과하기 위한 로직을 구현합니다.
- 테스트 실행 (Test Execution) : 작성된 코드를 실행하여 테스트를 다시 실행합니다. 이 시점에서는 작성된 코드가 테스트를 통과해야 합니다.
- 리팩토링 (Refactoring) : 코드 작성 단계에서 작성한 코드를 개선하고, 중복 코드를 제거하며, 가독성을 높이는 등의 리팩토링 작업을 수행합니다. 리팩토링은 테스트를 통과하는 동안 기능을 변경하지 않으며, 코드의 품질을 향상하는데 중점을 둡니다.
스프링에서 TDD
스프링 프로젝트를 만들면 자동으로 test 폴더를 만들어줍니다. 여기에 package를 만들어 tdd를 작성하면 됩니다.
given-when-then
테스트 코드는 보통 given-when-then으로 만듭니다.
저는 인텔리제이에 템플릿을 설정해 놔서 tdd를 칠 경우 자동으로 given-when-then이 만들어집니다.
- given은 테스트 실행을 준비하는 단계입니다.
- when은 테스트를 진행하는 단계입니다.
- then은 테스트 결과를 검증하는 단계입니다.
spring-boot-startet-test
스프링 부트는 spring-boot-starter-test를 지원해 줍니다. 여러 도구가 있지만 아마 제일 유명한 건 JUnit과 AssertJ 그리고 Mockito 일 것 같습니다.
- JUnit : 자바용 단위 테스트 프레임워크.
- Spring Test & Spring Boot Test : 스프링 프레임워크용 테스트 지원 및 통합 테스트를 위한 모듈.
- AssertJ : 가독성이 좋은 테스트 단언문 라이브러리.
- Hamcrest : 유연한 매처(matcher) 라이브러리로, 테스트 단언문을 좀 더 가독성 있게 작성할 수 있게 도와줌.
- Mockito : 자바용 모킹 프레임워크로, 가짜 객체를 생성하여 의존성을 분리하고 테스트 중의 동작을 제어하거나 결과를 조작함.
- JSONassert : JSON 형식의 데이터를 테스트하는 데 사용되는 라이브러리.
- JsonPath : JSON 데이터에서 특정 경로에 있는 값을 추출하기 위한 쿼리 라이브러리.
JUnit을 사용한 단위 테스트
JUnit은 자바 개발자들 사이에서 가장 널리 사용되는 단위 테스트 프레임워크입니다.
JUnit을 사용하여 단위 테스트를 작성하면 다음과 같은 이점이 있습니다.
- 자동화된 테스트 실행 : JUnit은 테스트를 자동으로 실행하고 결과를 확인하는 기능을 제공합니다.
- 테스트 케이스 구조화 : JUnit은 애너테이션을 사용하여 테스트 케이스를 구조화할 수 있는 기능을 제공합니다. @Test 애너테이션을 사용하여 개별 테스트 메서드를 정의하고, @Before, @After, @BeforeClass, @AfterClass 등의 애너테이션을 사용하여 테스트 실행 전후에 필요한 설정을 수행할 수 있습니다.
- 단언문(assertions) 지원 : JUnit은 다양한 단언문 메서드를 제공하여 예상 결과와 실제 결과를 비교하고 테스트를 검증할 수 있습니다. 이를 통해 테스트 케이스의 예상 동작과 실제 동작 사이의 차이를 파악할 수 있습니다.
- 테스트 실행 순서 관리 : JUnit은 테스트 메서드의 실행 순서를 보장하지 않습니다. 이는 테스트 간의 의존성을 제거하고 독립적인 단위 테스트를 작성할 수 있도록 합니다. 테스트 메서드 간의 실행 순서가 중요한 경우에는 추가적인 설정을 통해 제어할 수 있습니다.
- 다양한 확장 기능 : JUnit은 다양한 확장 기능을 제공하여 테스트 환경을 커스터마이징하고 테스트의 추가적인 기능을 활용할 수 있습니다. 예를 들어, @RunWith 애너테이션을 사용하여 커스텀 테스트 러너를 등록하거나, @Rule 애너테이션을 사용하여 테스트를 감싸는 규칙을 정의할 수 있습니다.
JUnit을 실행과정
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 애너테이션
- 테스트 케이스를 시작하기 전에 매번 실행합니다.
- 메서드에 사용하는 객체를 초기화하거나 테스트에 필요한 값을 미리 넣을 때 사용합니다.
- @AfterAll 애너테이션
- 전체 테스트를 마치고 종료하기 전에 한 번만 실행합니다.
- 데이터베이스 연결을 종료하거나 공통으로 사용하는 자원을 해제할 때 사용합니다.
- 전체 실행 주기에 한 번만 호출되어야 하므로 메서드를 static으로 선언해야 합니다.
- @AfterEach 애너테이션
- 각 테스트 케이스를 종료하기 전 매번 실행합니다.
- 특정 데이터를 삭제해야 하는 경우 사용합니다.
위의 코드를 실행하면 이런 결과가 나옵니다. 애너테이션이 설명한 대로 실행한다는 것을 알 수 있습니다.
AssertJ로 검증문 가독성 높이기
Assertions.assertEquals(a + b, sum);
위의 코드를 AssertJ를 사용하면 가독성이 좋게 변경할 수 있습니다.
import static org.assertj.core.api.Assertions.assertThat;
assertThat(a+b).isEqualTo(sum);
AssertJ가 지원해주는 메서드
- 동등성 검증
- isEqualTo: 두 값이 동일한지 확인합니다.
- isNotEqualTo: 두 값이 다른지 확인합니다.
- 참/거짓 검증
- isTrue: 값이 참인지 확인합니다.
- isFalse: 값이 거짓인지 확인합니다.
- null 검증
- isNull: 값이 null인지 확인합니다.
- isNotNull: 값이 null이 아닌지 확인합니다.
- 타입 검증
- isInstanceOf: 값이 특정 클래스의 인스턴스인지 확인합니다.
- isNotInstanceOf: 값이 특정 클래스의 인스턴스가 아닌지 확인합니다.
- 문자열 검증
- contains: 문자열이 특정 문자열을 포함하는지 확인합니다.
- startsWith: 문자열이 특정 문자열로 시작하는지 확인합니다.
- endsWith: 문자열이 특정 문자열로 끝나는지 확인합니다.
- 컬렉션 검증
- contains: 컬렉션이 특정 요소를 포함하는지 확인합니다.
- doesNotContain: 컬렉션의 값이 포함하지 않는지 검증
- containsExactly: 컬렉션이 순서와 요소 수를 포함하여 정확하게 일치하는지 확인합니다.
- hasSize: 컬렉션이 특정 크기인지 확인합니다.
- 예외 검증
- isInstanceOf: 발생한 예외가 특정 클래스의 인스턴스인지 확인합니다.
- hasMessage: 발생한 예외의 메시지를 확인합니다.
HTTP 주요 응답 코드
- 200 OK: 요청이 성공적으로 처리되었음을 나타냅니다. 일반적으로 성공 응답에 사용됩니다. isOk()
- 201 Created: 요청이 성공적으로 처리되어 새로운 리소스가 생성되었음을 나타냅니다. 주로 POST 요청 후 리소스 생성에 사용됩니다. isCreated()
- 400 Bad Request: 요청이 잘못되었거나 부적절한 구문으로 구성되었음을 나타냅니다. isBadRequest()
- 403 Forbidden: 요청된 리소스에 대한 액세스가 거부되었음을 나타냅니다. 인증은 성공하지만, 권한이 없는 경우에 사용됩니다. isForbidden()
- 404 Not Found: 요청한 리소스를 찾을 수 없음을 나타냅니다. isNotFound()
- 400번대 응답 코드: HTTP 응답 코드가 400번대 응답 코드인지 검증. is4xxClientError()
- 500 Internal Server Error: 서버 내부의 오류로 인해 요청을 처리할 수 없음을 나타냅니다. isInternalServerError()
- 500번대 응답 코드 : HTTP 응답 코드가 500번대 응답 코드인지 검증. is5xxServerError()
테스트 코드 실습하기
- MemberController를 Alt-Enter를 누른 뒤 Create Test를 하면 자동으로 test가 만들어집니다.
@SpringBootTest // 테스트용 애플리케이션 컨텍스트 생성
@AutoConfigureMockMvc // MockMvc 생성
class MemberControllerTest {
@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 = "/insert";
final String surl = "/select";
// when
final ResultActions result1 = mockMvc.perform(post(url)
.param("name","홍길동")
.accept(MediaType.APPLICATION_JSON));
final ResultActions result2 = mockMvc.perform(get(surl)
.accept(MediaType.APPLICATION_JSON));
//then
result2
.andExpect(status().isOk())
.andDo(print())
.andExpect(jsonPath("$[0].id").value(1L))
.andExpect(jsonPath("$[0].name").value("홍길동"));
}
}
- @SpringBootTest는 스프링 기반 애플리케이션의 통합 테스트를 작성할 때 사용되는 애너테이션입니다. 이 애너테이션을 사용하면 테스트 컨텍스트(TestContext)를 구성하고 애플리케이션의 컴포넌트들과 상호작용할 수 있는 환경을 제공합니다.
- @AutoConfigureMockMvc는 스프링 MVC 테스트를 위한 애너테이션입니다. 이 애너테이션을 사용하면 MockMvc 객체를 자동으로 구성하여 컨트롤러 테스트를 수행할 수 있습니다. 주요한 특징은 다음과 같습니다.
- @AutoConfigureMockMvc 애너테이션은 스프링 MVC 테스트를 위해 MockMvc 객체를 자동으로 설정합니다.
- MockMvc는 컨트롤러의 동작을 테스트하고, 요청과 응답을 모의(Mock)하는 데 사용됩니다.
- 테스트 메서드에서 MockMvc 객체를 주입받아 사용할 수 있습니다.
- MockMvc를 사용하여 HTTP 요청을 생성하고 컨트롤러의 동작을 호출한 뒤, 응답을 검증할 수 있습니다.
- @BeforeEach에서 MockMvc를 설정해줍니다.
- @AfterEach에서 테스트를 실행한 이후에 member 테이블에 있는 데이터들을 모두 삭제해줍니다.
- perform() 메서드는 요청을 전송하는 역할을 하는 메서드입니다.
- 결과로 ResultActions 객체를 받습니다.
- param()에는 parameter설정을 해줍니다.
- accept(MediaType.APPLICATION_JSON)은 JSON 타입으로 받습니다.
- result2
- andExpect() 메서드는 응답을 검증합니다. API응답으로 OK을 반환하면 isOK를 통해 검증합니다.
- jsonPath("$[0].${필드명}")은 JSON 응답값의 값을 가져오는 역할을 하는 메서드입니다.
'토이프로젝트 > 나만의 프로젝트' 카테고리의 다른 글
토큰 기반 인증으로 로그인 업그레이드 하기(JWT) (0) | 2023.06.28 |
---|---|
스프링 시큐리티로 회원가입, 로그인 구현하기 (0) | 2023.06.27 |
Docker로 CI/CD 구축하기 (0) | 2023.06.20 |
데이터베이스 정규화 하기 (2) | 2023.04.29 |
OAuth2.0 이란? (2) | 2023.02.07 |