[JUnit] Stub

반응형

이번글에서는 JUnit의 Stub에 대해 알아보도록 하겠습니다.

1. Stub란?

테스트 용도로 하드 코딩한 값을 반환하는 구현체를 의미합니다.

Stub은 주로 external service를 사용하는 code를 테스트할 때 사용합니다. 여기서 external service는 database, web service 등을 의미합니다.

우리는 databse와 connect 하거나 web service를 call 할 수 있는지에대해 test하고 싶은 것이 아닙니다. 따라서, 이와 같은 부분들은 하드코딩으로 대체하고, 우리가 test 하고자하는 business logic에 집중하고자 Stub을 사용합니다.

Stub을 사용하지 않을 경우 위와 같은 external service가 down 된다면 우리가 작성한 code는 문제가 없음에도 불구하고, test에서 에러가 발생할 수 있다는 문제점 또한 존재합니다.

image.png

구글 API

예를 들어 구글 지도 API를 사용하는 business logic이 있을 경우, 우리는 구글이 제대로 데이터를 던져주는지 아닌지에는 관심이 없습니다. ( 왜냐면 구글은.. 갓 이기 때문에 😅) 만약 API 호출할때마다 일정비용을 지불해야된다면 더욱이 Stub을 사용해야 합니다.

image.png

따라서 이러한 구글 API를 호출하는 부분은 호출했다고 치고 정적으로 하드코딩한 값을 return 하도록하는 Stub을 사용합니다.

2. Locator Code

이전 글과 동일하게 ISBN Number를 예를 들어보겠습니다.

우리는 book의 location을 관리하는 새로운 buiness logic이 필요해졌습니다. 이를 위해 StockManager라는 클래스를 사용하고자 합니다. 이때 StockManager는 book의 location number를 생성하기위해 Amazon에서 book의 저자와 타이틀명을 획득해와야 한다고 가정하겠습니다.

image.png

이 후 StockManager는 획득한 저자와 타이틀명에 isbn number를 조합해 아래와 같이 location code를 생성합니다.

image.png

3. Fail

자 이제 동일하게 Fail 하는 테스트 작성부터 시작해서 StockManager class를 생성해보겠습니다.

test의 목적은 StockManager에 isbn number를 넘기면 제대로된 location code를 반환하는가 입니다.

StockManagementTest

public class StockManagementTest {

    @Test
    public void canGetCorrectLocatorCode(){
       fail("Not yet implemented");
    }
}

테스트를 실행시켜 fail을 확인합니다.

image.png

4. Pass

이제 위에서 실패한 테스트를 통과시킬 차례입니다.

아래와 같이 test code를 변경합니다. 아직 StockManager와 getLocationCode 메서드를 생성하지 않았으므로 빨간색으로 표시됩니다. 'Alt+Enter'를 눌러서 클래스와 메서드를 자동생성해줍니다.

image.png

예시로 사용할 "014077396" ibsn number를 넣으면, 결과값으로 "7396J4"를 넘겨줘야합니다. (저자 : J Stein, 타이틀 : Of Mice and Men)

IDE가 자동으로 생성해준 StockManager는 아래와 같습니다.

StockManager

public class StockManager {
    public String getLocatorCode(String isbn) {
        return null;
    }
}

getLocationCode 메서드에서는 원래대로라면 interface를 통해 Amazon API를 호출해 저자와 타이틀 정보를 가져와야합니다. 하지만 우리는 해당 부분을 테스트하는 것에는 관심이 없으므로 하드코딩 처리하는게 목적입니다.

Amazon API를 호출하는 Interface는 아래와 같으며, 호출한 API로 획득해오는 Class는 Book이라고 가정하겠습니다.

ExternalISBNDataService

public interface ExternalISBNDataService {
    public Book lookup(String isbn);
}

Book

@Getter
@Setter
@AllArgsConstructor
public class Book {
    private String isbn;
    private String title;
    private String author;
}

전체적인 그림은 아래와 같습니다.

image.png

기본적인 구조는 완성되었으므로 StockManager에서 ExternalISBNDataService를 사용할 수 있도록 아래와 같이 변경합니다.

StockManager

public class StockManager {

    private ExternalISBNDataService service;

    public void setService(ExternalISBNDataService service){
        this.service = service;
    }

    public String getLocatorCode(String isbn) {
        return "7396J4";
    }
}

일반적인 TDD 프로세스라면 다음으로 진행해야 될 것은 ExternalISBNDataService의 lookup() 메서드를 구현한 class를 작성해야 하지만, 우리는 외부 API는 신경쓰지 않는 테스트를 작성할 것이므로 제외합니다. 이는 향후 하드코딩으로 대체할 것 입니다.

테스트 해보겠습니다.

image.png

😎 테스트에 Pass 했습니다. 다음으로 넘어갑시다!

5. Refactoring

이제 위에서 작성한 말도안되는 getLocatorCode 메서드를 refactoring 할 차례입니다.

먼저 메서드를 아래와 같이 변경합니다.

StockManager

public class StockManager {

    private ExternalISBNDataService service;

    public void setService(ExternalISBNDataService service){
        this.service = service;
    }

    public String getLocatorCode(String isbn) {
       // call service
       Book book = service.lookup(isbn);

       // logic 
       StringBuilder locator = new StringBuilder();
       locator.append(isbn.substring(isbn.length() - 4));
       locator.append(book.getAuthor().substring(0,1));
       locator.append(book.getTitle().split(" ").length);

       return locator.toString();
    }

}

테스트 해보겠습니다.

image.png

당연하게도 실패했습니다. 왜냐하면 ExternalISBNDataService의 lookup() 메서드를 구현하지 않았기 때문입니다. 이제 우리는 이 lookup() 메서드를 대체하는 하드코딩값을 사용해 테스트를 진행할 것입니다.

현재는 테스트를 위해서만 code를 작성중이기 때문에, ExternalISBNDataService Interface를 구현한 class가 존재하지 않습니다. 하지만 실제로는 이를 구현한 class가 존재할 것이며 따라서 lookup() 메서드가 구현되어 있을 것 입니다.

우리는 구현되어있는 lookup() 메서드를 사용하고 싶지 않으므로, lookup() 메서드를 override 해서 하드코딩 값을 return 하도록 변경하면 됩니다. 이를 Stub이라고 합니다.

전체 흐름

image.png

이제 Stub을 만들어보겠습니다. test code를 아래와 같이 변경합니다.

StockManagementTest

public class StockManagementTest {

    @Test
    public void canGetCorrectLocatorCode(){

        // Stub
        ExternalISBNDataService service = new ExternalISBNDataService() {
            @Override
            public Book lookup(String isbn) {
                return new Book(isbn, "Of Mice And Men", "J. Streinbeck");
            }
        };

        // set service
        StockManager stockManager = new StockManager();
        stockManager.setService(service);

        // do
        String isbn = "014077396";
        String locatorCode = stockManager.getLocatorCode(isbn);

        // assert
        assertEquals("7396J4", locatorCode);
    }
}

위의 코드를 살펴보면 ExternalISBNDataService의 lookup 메서드를 override 한 것을 확인할 수 있습니다. 또한, 메서드를 override를 한 service를 StockManager의 service 필드에 주입했습니다. 이를 통해 결과적으로 canGetCorrectLocatorCode() 테스트에서는 하드코딩된 Stub로 Amazon API를 호출하는 로직을 대체하게 됩니다.

테스트 해보겠습니다.

image.png

짠 😎 통과했습니다.

이제 여러분은 Stub를 사용할 수 있게되었습니다. 다음글에서는 behavior를 테스트 할 수 있게 도와주는 Mockito 라이브러리를 살펴보도록 하겠습니다.


참고 자료 : https://www.udemy.com/course/practical-test-driven-development-for-java-programmers/


추천서적

 

자바와 JUnit을 활용한 실용주의 단위 테스트

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음


반응형

'JUnit' 카테고리의 다른 글

[JUnit] Bad Test  (0) 2020.08.20
[JUnit] Mock & Mockito -2  (0) 2020.08.20
[JUnit] Mock & Mockito -1  (0) 2020.08.20
[JUnit] TDD -2  (0) 2020.08.17
[JUnit] TDD -1  (0) 2020.08.17

댓글

Designed by JB FACTORY