레이어별 테스트 코드 작성 방법(작성중)

HeoSeungYeon·2022년 1월 13일
7

Spring Study

목록 보기
3/7
post-thumbnail
💡 테스트 코드는 프로젝트를 개발 시, 수정(리팩토링)이 이루어질 때 자동화된 코드 검증을 통해 수동 테스트에 대한 비용을 크게 절감해줄 수 있는 방법이라고 생각합니다. 이번 포스팅에서는 Spring Boot 서버 개발 시, Unit 테스트 코드 작성에 대한 일련의 작성 방법에 대한 내용들을 정리해보는 시간을 가져보겠습니다 🧐

A : Spring Boot의 Unit test


테스트 의존성


스프링부트에선 크게 2가지 모듈이 웹 애플리케이션 테스트를 위한 기능을 제공해줍니다.

  • spring-boot-test : 핵심 기능 제공
  • spring-boot-test-autoconfigure : 테스트를 위한 자동 설정

spring boot 프로젝트에서 spring-boot-starter-test 라는 dependency를 적용하면 다음과 같은 의존성을 자동으로 설정해 줍니다.

  • JUnit 5
  • spring-boot-test
  • AssertJ, Hamcrest, Mockito, JSONassert, JsonPath

Unit Test 을 작성하려면?


Unit Test는 통합테스트가 아니므로 @SpringBootTest 어노테이션을 통해 스프링을 실행시키지 않고, 테스트하려는 메서드에 필요한 부분만 최소한으로 사용하여 테스트를 실행하여야 합니다.

그렇기 하기 위해선 Mockito 라는 것이 필요합니다. Mockito는 가짜 객체를 의미하는 Mock을 생성하여 의존성을 최소화시켜줄 수 있는 테스트 프레임워크 입니다. Mockito 를 사용하면 테스트 대상에서 필요로 하는 객체들을 임의로 생성하여 원하는 시나리오대로 테스트를 쉽게 구성할 수 있습니다.

Mockito 사용 방법

  1. 의존성 주입(DI)

    의존성 주입이 필요한 Mock 객체를 만들기 위해선 3가지 어노테이션이 주로 사용됩니다.

    • @Mock : Mock 객체를 만들어서 반환해주는 어노테이션
    • @InjectMocks : @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입 시켜주는 어노테이션
    • @Spy : Stub하지 않은 메소드들을 그대로 사용하는 어노테이션

    ReviewService를 테스트 한다고 했을 때, ReviewRepository 객체를 주입받아야 한다면, ReviewRespository를 @Mock 어노테이션으로 선언하고, ReviewService를 @InjectsMock 어노테이션으로 선언함으로써 테스트를 할 수 있습니다.

  2. Stub

    Mock 객체로 만들어진 객체를 통해 시나리오에 맞는 원하는 결과를 만들어 놓는 행위를 Stub이라고 합니다. Stub을 하기 위해서 Mockito에서 주로 사용되는 메서드는 다음과 같습니다.

    • doReturn(): Mock 객체가 특정한 값을 반환해야 하는 경우
    • doNothing(): Mock 객체가 아무 것도 반환하지 않는 경우(void)
    • doThrow(): Mock 객체가 예외를 발생시키는 경우

    ReviewRepository의 메서드 호출 결과로 예외를 발생시키고 싶은 경우엔 doThrow() 메서드를 사용하여 테스트를 진행할 수 있습니다.

  3. Junit5 에서의 Mockito 확장

    Junit5를 활용한 Unit 테스트 코드를 작성하는 클래스에서 Mockito를 사용하기 위해선 @ExtendWith(MockitoExtension.class) 코드를 통해 Mockito 사용을 확장하여야 합니다.

    Junit4 의 경우 @RunWith 어노테이션을 통해 Mockito 를 사용할 수 있었지만, Spring Boot 버전이 업그레이드됨에 따라 Junit5를 지원하여 @ExtendWith 어노테이션을 통해 Mockito를 사용할 수 있습니다. @RunWith의 차이점으로는 메타 어노테이션으로 사용할 수 있어 @SpringBootTest 어노테이션 내부엔 @ExtendWith을 사용하는 것을 알 수 있습니다.

Unit 테스트를 작성하기 위해 필요한 기본적인 개념 및 도구에 대해서 알아봤습니다. 다음 글부터 기본 원칙 및 한계점을 알아보고 레이어별 테스트 코드를 작성해보도록 하겠습니다!

Unit 테스트 코드 기본 원칙


Unit 테스트 코드 작성 시, 5가지 원칙은 다음과 같습니다.

F - fast : 테스트 코드는 빠르게 실행되어야 한다.

I - Independent : 독립적으로 실행되어야 한다.

R - Repeatable : 반복 가능해야 한다.

S - Self Validating : 스스로 테스트 검증 가능해야 한다

T - Timely : 프로덕션 코드 직전에 작성되어야 한다.(TDD)

질이 좋은 테스트 코드와 목적에 맞는 테스트 코드를 작성하기 위해선 위 규칙을 준수하면 좋지만, 반드시 위 규칙에 얽매여서 헤맬 필요없이 자신의 테스트 목적에 맞게 코드를 작성하는 것이 더욱 중요하다고 생각합니다.

Unit test 의 한계


Unit Test(단위 테스트)는 개발자가 구현한 일련의 메서드들을 독립적인 입장에서 시나리오에 대한 검증을 하기 위해선 반드시 필요한 테스트입니다.

하지만 Spring Boot를 사용하여 웹 어플리케이션 서버를 구축하였다면 보다 넓은 관점에서의 시나리오를 검증하는 것 또한 서버에 대한 신뢰성을 더 높이는 방법입니다. 그렇게 하기 위해선 ‘통합 테스트’라는 과정이 필요합니다. 통합 테스트에 대해서는 다음 포스팅에서 다뤄 보도록 해보겠습니다 😒

Unit test에서의 Given, When, Then


일반적으로 테스트 코드를 작성할 때 Given - When - Then (준비, 실행, 검증) 패턴을 활용하면 더 쉽게 코드를 작성할 수 있습니다.

  • Given : 준비
    • mock 객체를 정의하거나, mockito를 이용하여 stubbing 하는 과정
  • When : 실행
    • 실제로 호출되어 실행되는 과정
  • Then : 검증
    • 기댓 값과 비교하거나, 예상대로 메서드가 호출되었는지 검증

Mockito vs BDDMockito

Mockito를 사용하여 Given - When - Then 패턴을 적용한 코드를 작성할 때 살짝 거슬리는 메서드가 있습니다. 바로 Mockito.when() 메서드인데, 해당 메서드는 given(준비) 과정에 어울리는 메서드입니다.

위 불편함을 해소하기 위해 나온 클래스가 있는데 바로 BDDMockito 클래스입니다.

BDDMockito는 Mockito 클래스를 상속하여 Mockito와 기능 측면에선 별 다른 부분이 없고, 위에 언급되었던 메서드 이름에서의 직관성을 높여주기 위해 메서드 이름을 변경해준 클래스입니다.

그 예로, Mockito.when()given()으로 변경하였고 Mockito.verify()when().should() 로 변경하였습니다.

(개인적으로 verify()when().should() 보다 더 직관성이 높아 보입니다 😅)

만약 Given - When - Then 패턴을 적용한 코드의 직관성을 높이기 위해선 BDDMockito 클래스를 사용하는 것도 좋은 방법 중 하나일 것 같습니다.

import static org.mockito.BDDMockito.*;

B : Layer 별 Test 코드 작성


1. Controller Layer Test


💡 Controller Layer 에서는 사용자의 요청을 통해 반환 값을 클라이언트에 전달하는 Layer 입니다. 보통 로직에 대한 부분은 Service에서, 데이터의 CRUD에 대한 부분은 Repository에서 담당하므로 Controller에서는 많은 로직이 담기기 보단, **요청을 전달받고 서비스 메서드 반환 값을 전달**하는 코드가 대부분입니다. 그렇기에 Unit 테스트로 Controller 테스트를 작성하는 것보다 통합 테스트로 작성하는게 더 효율적인 테스트 방식일 수 있습니다.

2. Service Layer Test


💡 Service Layer 에서는 데이터의 CRUD를 위한 Repsotiory 객체를 사용하여 트랜잭션을 관리하는 Layer입니다. Repository의 메서드들에 대한 동작 검증은 해당 레이어 테스트 코드에서 이루어졌으므로, Service 메서드 내부 코드들이 정상적으로 호출되는지 검증하면 됩니다. 보통 Repository를 Mock하여 Service 메서드를 검증합니다.

3. Repository Layer Test


💡 JPA를 사용하는 경우, 저장 및 조회에 대한 테스트는 @DataJpaTest 어노테이션을 주로 사용합니다. Repository는 데이터에 대한 조작 및 조회(CRUD)를 담당하는 Layer이므로, 메서드 호출 시 행위에 대한 올바른 결과값이 도출되었는지(값 검증)을 테스트합니다.

4. Domain Layer Test


💡 Domain 내부의 로직(메서드)은 JUnit, AssertJ, Hamcrest와 같은 테스트 도구 외 다른 테스트 도구들의 도움 없이 테스트를 수행할 수 있습니다.

참고문서


Writing Your F.I.R.S.T Unit Tests - DZone Java

스프링부트 테스트

Given-When-Then Pattern

[junit5] Mock을 이용한 단위 테스트 (@InjectMocks 과 @Mock 차이)

dev-tips/Spring-Boot-레이어별-테스트.md at master · HomoEfficio/dev-tips

profile
안녕하세요~! 백엔드 개발자 허승연 입니다 :)

0개의 댓글