<TDD>테스트 코드 작성하기 -(3)

윤재열·2022년 12월 26일
0

Spring

목록 보기
70/72
post-custom-banner

서비스 객체의 테스트

  • 이번에는 서비스 레이어에 해당하는 ProductService 객체를 테스트합니다.
  • 앞에서도 언급했듯이 예제에서는 DAO의 역할이 명확하게 드러나지 않기 때문에 DAO 객체는 생략합니다.
  • 먼저 getProduct() 메서드에 대해 코드를 작성해봅니다.
  • 단위 테스트를 수행할 클래스는 test 패키지의 service/impl 패키지를 생성하고 ProductServiceTest.java 파일을 생성합니다.

GET 방식의 테스트

package codej.todo_list.demo.service.impl;


import codej.todo_list.demo.todo.dto.ProductResponseDto;
import codej.todo_list.demo.todo.entity.ProductEntity;
import codej.todo_list.demo.todo.repository.ProductRepository;
import codej.todo_list.demo.todo.service.ProductServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.verify;
import static org.mockito.AdditionalAnswers.returnsFirstArg;


public class ProductServiceTest {
    
    private ProductRepository productRepository = Mockito.mock(ProductRepository.class);
    private ProductServiceImpl productService;
    
    @BeforeEach
    public void setUpTest() {
        productService = new ProductServiceImpl(productRepository);
    }
    
    @Test
    void getProductTest() {
        ProductEntity givenProduct = new ProductEntity();
        givenProduct.setPno(123L);
        givenProduct.setName("펜");
        givenProduct.setPrice(1000);
        givenProduct.setStock(5555);
        
        Mockito.when(productRepository.findById(123L))
                .thenReturn(Optional.of(givenProduct));

        ProductResponseDto productResponseDto = productService.getProduct(123L);


        assertEquals(productResponseDto.getPno(),givenProduct.getPno());
        assertEquals(productResponseDto.getName(),givenProduct.getName());
        assertEquals(productResponseDto.getPrice(),givenProduct.getPrice());
        assertEquals(productResponseDto.getStock(),givenProduct.getStock());
        
        verify(productRepository).findById(123L);
        
    }
}

  • 단위 테스트를 위해서는 외부 요인을 모두 배제하도록 코드를 작성해야 합니다.
    • 이번 예제에서는 @SpringBootTest,@WebMvcTest 등의 @....Test어노테이션이 선언되어 있지 않습니다.
  • Mockito의 mock() 메서드를 통해 Mock객체로 ProductRepository를 주입받았습니다.
  • 이 객체를 기반으로 각 테스트 전에 @BeforEach를 선언하여 ProductService 객체를 초기화 해줍니다.
  • 테스트 코드는 Given-When-Then 패턴을 기반으로 작성하였습니다.
  • Given 구문에 해당하는 ProductEntity 객체를 생성하고 ProductRepository의 동작에 대한 결과값 리턴을 설정해줍니다.
  • 테스트에서 리턴받은 ProductResponseDto 객체에 대해서 Assertion을 통해 값을 검증함으로써 테스트의 목적을 달성하는지 확인한 후에 검증 보완을 위해 verify() 메서드로 부가 검증을 시도해줍니다.

POST 방식의 테스트

  @Test
    void saveProductTest() {
        Mockito.when(productRepository.save(any(ProductEntity.class)))
                .then(returnsFirstArg());

        ProductResponseDto productResponseDto = productService.setProduct(new ProductDto("펜", 4000, 55));

        assertEquals(productResponseDto.getName(),"펜");
        assertEquals(productResponseDto.getPrice(),4000);
        assertEquals(productResponseDto.getStock(),55);

        verify(productRepository).save(any());
    }
}
  • 이 예제에서 살펴볼 내용은 any() 입니다.

    • any()는 Mockito의 ArgumentMatchers 에서 제공하는 메서드로서 Mock 객체의 동작을 정의하거나 검증하는 단계에서 조건으로 특정 매개변수의 전달을 설정하지 않고 메서드의 실행만을 확인하거나 좀 더 큰 범위의 클래스 객체를 매개변수로 전달받는 등의 상황에 사용합니다.
  • any(Product.class)로 동작을 설정 하였는데 , 일반적으로 given()으로 정의된 Mock 객체의 메서드 동작 감지는 매개변수의 비교를 통해 이뤄지나 래퍼런스 변수의 비교는 주소값으로 이루어지기 때문에 any()를 사용하여 클래스만 정의하는 경우도 있습니다.

지금까지 소개한 테스트는 Mock 객체를 활용한 방식이였습니다. 큰 차이는 없지만 Mock 객체를 직접 생성하지않고 @MockBean 어노테이션을 사용해 스프링 컨테이너에 Mock객체를 주입받는 방식을 소개하겠습니다.

package codej.todo_list.demo.service.impl;

import codej.todo_list.demo.todo.repository.ProductRepository;
import codej.todo_list.demo.todo.service.ProductService;
import codej.todo_list.demo.todo.service.ProductServiceImpl;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@Import({ProductServiceImpl.class})
public class ProductServiceTest2 {

    @MockBean
    ProductRepository productRepository;

    @Autowired
    ProductService productService;

    
}
  • 동작을 설정하는 ProductRepository에 대한 초기화 작업을 어떻게 진행하는지를 비교하기 위한 코드입니다.

  • 위의 예제에서는 Mockito를 통해 Repository를 Mock 객체로 대체하는 작업을 수행하고 서비스 객체를 직접 초기화했습니다.

  • 반면 이번 예제에서는 스프링에서 제공하는 테스트 어노테이션을 통해 Mock 객체를 생성하고 의존성을 주입 받고 있습니다.

    • 둘의 차이라면 스프링의 기능에 의존하느냐 의존하지 않느냐에 차이 뿐입니다.
    • 두 예제 모두 Mock 객체를 활용한 테스트 방식인 것은 동일하나 @MockBean을 사용하는 방식은 스프링에 Mock 객체를 등록해서 주입받는 형식이며 Mockito.mock()을 사용하는 방식은 스프링 빈에 등록하지 않고 직접 객체를 초기화해서 주입받는 형식입니다.
    • 둘다 테스트 속도에는 큰 차이는 없지만 아무래도 스프링을 사용하지 않는 Mock 객체를 직접 생성하는 방식이 더 빠르게 동작합니다.
  • 스프링에서 객체를 주입받기 위해 @ExtendWith(SpringExtension.class)를 사용해 JUnit5의 테스트에서 스프링 테스트 컨텍스트를 사용하도록 설정합니다.

    • 그리고 @Autowired 어노테이션으로 주입받는 ProductService를 주입받기 위해 클래스를 @Import 어노테이션을 사용합니다.
profile
블로그 이전합니다! https://jyyoun1022.tistory.com/
post-custom-banner

0개의 댓글