Spring Framework - jUnit, Mockito

h.Im·2024년 8월 30일

Springboot 기초

목록 보기
11/17
post-thumbnail

jUnit

JUnit은 자바 프로그래밍 언어에서 단위 테스트를 작성하고 실행하기 위한 오픈소스 프레임워크입니다. 애플리케이션의 개별 구성 요소(주로 메서드)를 테스트하여 구성 요소가 올바르게 동작하는지 검증하는 과정을 돕습니다.

주요 특징

  • 테스트 자동화
    테스트를 자동으로 수행해 준다는 점이 굉장히 중요합니다. 새로운 코드를 작성하거나 코드를 리팩토링 할 때, 코드 작성 전/후로 테스트 코드를 동작시킴으로써 작성한 코드가 올바른지 지속적으로 확인하기 편해집니다.
  • 어노테이션 기반
    @Test, @Before, @After, @BeforeAll, @AfterAll 등의 애너테이션을 사용하여 테스트 메서드, 초기화 작업, 후처리 작업 등을 정의할 수 있습니다.
  • 경량화
    JUnit은 간단하고 가벼워서 설정이 쉽고, 빠르게 테스트를 작성하고 실행할 수 있습니다.
  • 통합
    JUnit은 Maven, Gradle 같은 빌드 도구나 Jenkins와 같은 CI/CD 도구와 쉽게 통합되어 테스트를 지속적으로 실행할 수 있습니다. 아직 직접 경험해 본 적은 없으나 배포/성공 실패를 테스트 성공 여부로 결정할 수도 있겠네요.

그럼 몇 가지 어노테이션과 메소드를 정리해 보도록 하겠습니다.


@Test

@Test 어노테이션은 테스트 메소드를 지정하는 역할을 합니다. @Test 어노테이션이 붙은 메서드는 jUnit에서 테스트 메서드로 간주하고, 자동 실행이 가능하게 됩니다. @Test 메서드 내부에서는 assertEquals, assertTrue, assertFalse, assertThrows 같은 단언문을 사용하여 테스트 코드가 기대한 대로 동작하는지 확인합니다.

@Test 어노테이션이 붙은 메소드는 아래 캡처와 같이 초록색 테스트 실행 버튼이 노출됩니다(intelliJ).

@Test 메소드를 포함한 클래스 옆에도 노출되어, 클래스 내부의 테스트를 한 번에 동작하게 할 수도 있습니다.


@SpringBootTest

@SpringBootTest 어노테이션은 Spring Boot에서 통합 테스트를 수행할 때 사용하는 어노테이션입니다. 이 어노테이션은 테스트 클래스에서 Spring 애플리케이션 컨텍스트를 로드하고, 애플리케이션의 거의 모든 부분을 실제와 동일하게 테스트할 수 있도록 환경을 구성합니다.
단, 실제와 동일한 환경을 구성하는 것은 테스트가 격리적으로 수행되기는 어렵게 만듭니다.

@SpringBootTest 사용 시 실제 DB와의 상호작용이 발생할 수 있다는 의미이고, 실무 DB에는 민감 정보나 삭제되면 안되는 정보가 존재하기 때문에 실무 DB 데이터를 테스트에 사용하는 것은 위험할 수 있습니다.


Mockito

Mockito는 테스트가 격리적으로 수행될 수 있게 도와주는 프레임워크입니다. @SpringBootTest를 통합 테스트에 사용한다면, Mockito는 단위 테스트에 좀 더 적합합니다.
@SpringBootTest와 Mockito는 아래와 같은 차이점이 존재합니다.

  • 의존성 관리
    • SpringBootTest: 애플리케이션 컨텍스트에 등록된 실제 빈을 사용하여, 모든 의존성이 함께 테스트됩니다.
    • Mockito: 의존성을 모킹하여, 테스트하고자 하는 클래스의 실제 동작만을 테스트합니다. 외부 시스템이나 데이터베이스와의 연결 없이, 가상의 객체를 사용해 테스트할 수 있습니다.
  • 속도
    • SpringBootTest: 실제 애플리케이션 컨텍스트를 로드하므로, 테스트 실행 시간이 상대적으로 길어질 수 있습니다.
    • Mockito: 경량 단위 테스트로, 컨텍스트를 로드하지 않기 때문에 테스트 실행이 빠릅니다.

Mockito를 이용한 단위 테스트 예제 코드를 살펴보겠습니다.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;

// 서비스 클래스
class MyService {
    private final MyRepository myRepository;

    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }

    public String getDataById(int id) {
        return myRepository.findDataById(id);
    }
}

// 리포지토리 인터페이스
interface MyRepository {
    String findDataById(int id);
}

// 테스트 클래스
@ExtendWith(MockitoExtension.class)
public class MyServiceTest {

    @Mock
    private MyRepository myRepository;

    @InjectMocks
    private MyService myService;

    @Test
    public void testGetDataById() {
        // given
        int id = 1;
        String expectedData = "Mocked Data";
        when(myRepository.findDataById(id)).thenReturn(expectedData);

        // when
        String actualData = myService.getDataById(id);

        // then
        assertEquals(expectedData, actualData);
    }
}
  1. @ExtendWith(MockitoExtension.class): jUnit과 Mockito를 통합하고, Mockito를 초기화하며 Mock 및 @InjectMocks 어노테이션을 처리합니다.
  2. @Mock: MyRepository 인터페이스를 모킹합니다. 실제 MyRepository 구현체를 사용하는 대신, 가상의(Mock) 객체를 사용하여 테스트할 수 있습니다. 실제 Repository를 사용했다면 @Autowired 같은 어노테이션을 사용했을 것입니다.
  3. @InjectMocks: MyService 클래스에 모킹된 MyRepository를 주입합니다. MyService 내부의 myRepository 변수에 Mock 리포지토리가 주입되는 것입니다.

given-when-then 패턴

given-when-then은 테스트 코드를 작성할 때 흔히 사용되는 패턴으로, 코드의 가독성을 높이고 테스트의 구조를 명확하게 하기 위한 방법입니다. 테스트의 단계를 논리적으로 분리해서, 테스트 작성자의 의도를 분명하게 표현하는 것을 도와줍니다.

given

  • 테스트할 데이터와 초기 상태를 설정
  • 의존성 객체들을 모킹하고, 모킹된 메서드의 반환값을 정의
  • 테스트를 위해 필요한 모든 사전 조건을 설정

when

  • 테스트할 메서드나 기능을 호출

then

  • 단언문을 사용하여 실제 결과와 기대한 결과가 일치하는지 확인
  • 필요시, 모킹된 객체가 특정 메서드를 예상한 횟수만큼 호출했는지, 호출 순서가 올바른지 등도 검증 가능

0개의 댓글