티스토리 뷰

단위 테스트는 코드 내의 단위(클래스, 메서드) 범위를 테스트하기 때문에 다른 객체나 외부 리소스에 의존성을 가지는 경우 테스트를 작성하기 까다롭다.

예를 들면 데이터베이스에 의존하는 경우 데이터베이스에 데이터가 준비되어있어야 한다던지, 테스트하고자하는 영역 이외에 신경써야할 부분이 많아진다.

또한 그렇게 테스트를 진행한다고 하더라도 데이터베이스 연결까지 포함하여 테스트하기 때문에 단위 테스트보다는 통합 테스트나 기능 테스트에 더욱 가깝다.

이러한 경우에 활용할 수 있는 두 가지 방법이 있는데 그것이 스텁과 모의 객체이다.

 

스텁 활용하기

스텁이란 테스트를 위한 테스트용 객체이다.

간단히 예를 들면 HTTP 통신을 위한 부분이 단위 테스트에 포함된다고 가정한다면, HTTP 통신을 담당하는 테스트 클래스를 따로 생성해서 테스트에 활용할 수 있다.

 

public class StockService {

    private StockClient stockClient;

    public StockService(StockClient stockClient) {
        this.stockClient = stockClient;
    }

    public double retrieveTotalStockPrice(List<Long> stockIds) {
        return stockIds.stream()
                .map(id -> stockClient.retrievePrice(id))
                .mapToDouble(price -> price)
                .sum();
    }
}
public interface StockClient {

    double retrievePrice(Long stockId);

}

위 코드에서 StockService의 retrieveTotalStockPrice(...) 메서드를 호출하면 전달한 모든 stockId에 대해 각각 API 통신을 일으켜 가격을 받아오고 합산한다.

그렇지만 StockClient가 아직 구현되지 않은 상태라고 가정하면 당장 가격 정보를 받아올 수 없으니 테스트가 어려울 것이다.

또한 외부 리소스를 함께 연동하여 테스트하기 때문에 단위 테스트는 아니다.

 

그렇다면 이 상황에서 StockClient를 임시로 구현하는 스텁 클래스를 만들고, 이를 활용해 StockService를 테스트해보자.

public class StockServiceTest {

    private final Map<Long, Double> STOCK_PRICES = Map.of(
            1L, 1000D,
            2L, 3000D,
            4L, 5000D,
            7L, 100D);

    @Test
    void testRetrieveTotalStockPrice() {
        StockService stockService = new StockService(new StubStockClient(STOCK_PRICES));
        List<Long> stockIds = List.of(1L, 4L);

        double totalPrice = stockService.retrieveTotalStockPrice(stockIds);

        Assertions.assertEquals(6000D, totalPrice);
    }

    private class StubStockClient implements StockClient {

        private Map<Long, Double> stockPrices;

        public StubStockClient(Map<Long, Double> stockPrices) {
            this.stockPrices = stockPrices;
        }

        @Override
        public double retrievePrice(Long stockId) {
            return stockPrices.get(stockId);
        }
    }
}

위와 같이 테스트 코드 아래에 StubStockClient 클래스를 구현했다.

이 클래스는 실제 통신을 일으키지 않고 생성자에 주어진 가격 정보를 가지고 적절한 값의 반환만 수행한다.

 

따라서 테스트하는 시점에서 주식 가격 정보를 스텁 객체에 주입하고 이 StubStockClient 객체를 다시 StockService 객체에 주입한다.

이렇게 StockService의 retrieveTotalStockPrice(...) 메서드의 외부 의존성과 상관없이 단위 테스트를 진행할 수 있다.

 

JaCoCo Report 생성 결과

결과적으로 StockClient가 구현되어있지 않은 상태임에도 StockService에 대한 테스트를 수행할 수 있었고 위와 같이 JaCoCo Report를 생성했다.

 

모의객체 활용하기

스텁은 굉장히 유용한 방법이기도 하지만, 스텁을 활용하기 위해서는 매번 특정 결과를 내는 테스트용 클래스를 생성해서 의존성을 주입해줘야한다.

또한 단위 테스트의 검증 범위에는 단순히 특정 입력에 대한 결과값의 검증 뿐아니라 실행되어야하는 로직이 실행되었는지도 포함되어야하나, 스텁은 이런 부분까지는 검증이 어렵다.

모의 객체를 활용하게된다면 단위 테스트 내에서 테스트 내의 특정 동작에 대해 제어가 가능하다.

 

Mockito 활용해보기

EasyMock이나 JMock에 비해 월등히 많이 사용되는 Mock Framework이기 때문에 Mockito를 활용하여 모의 객체를 알아볼 것이다.

Mock 프레임워크 트렌드

 

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>5.14.2</version>
    <scope>test</scope>
</dependency>
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
testImplementation("org.mockito:mockito-junit-jupiter:5.14.2")

Mockito를 사용하려면 위와 같이 의존성을 추가해야한다.

 

@ExtendWith(MockitoExtension.class)
public class TestStockServiceMockito {

    @Mock
    private StockClient mockStockClient;
    
    @Test
    public void testRetrieveTotalStockPriceOK() {
        List<Long> stockIds = List.of(1L, 4L);
        when(mockStockRepository.findById(1L)).thenReturn(1000D);
        when(mockStockRepository.findById(4L)).thenReturn(5000D);
        
        StockService stockService = new StockService(mockStockClient);
        double totalPrice = stockService.retrieveTotalStockPrice(stockIds);
        
        assertEquals(6000D, totalPrice);
        
        verify(mockStockClient, times(2)).retrievePrice();
    }
}

 

'공부 > 테스트' 카테고리의 다른 글

테스트 코드의 장점 활용하기  (0) 2025.04.16
테스트의 종류와 필요성  (0) 2025.04.13
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함