Post

테스트 더블

테스트 더블

테스트 더블은 xUnit의 저자 Gerard Meszaros가 만든 용어로, 스턴트 더블(스턴트 대역 배우를 지칭하는 용어)에서 아이디어를 얻은 말이라고 합니다. 실제 DOC 접근이 어렵고, 사용할 수 없는 경우에 사용되는 Test 객체입니다.
테스트 더블을 이용하면, 테스트 대상 코드를 격리하고 테스트 속도를 개선할 수 있습니다. 그리고 특수한 상황을 시뮬레이션 할 수 있습니다.

Dummy

실제로 사용되지는 않지만 파라미터 리스트를 채우기 위해 사용되는 객체를 말합니다. 구현을 제외한 인터페이스 또는 기본 클래스의 파생 객체로 객체만 전달될 뿐 사용되지 않습니다.
아래 코드에서 보면, DummyObject는 아무것도 없지만 @Test 파라미터를 채우기 위해 존재하는 객체로 이름만 존재하는 것을 알 수 있습니다.

1
2
3
4
5
6
7
8
9
public class DummyObject {}

public class DummyObjectTest {
    @Test
    public void testDummyObject() {
        DummyObject dummy = new DummyObject();
        // 테스트할 로직 작성
    }
}

Stub

Dummy 객체가 실제로 동작하는 것처럼 보이게 만들어 놓은 객체를 말합니다. 호출자를 실제 구현물로부터 격리시키는 목적으로 사용합니다. 테스트에서 호출된 요청에 대해 미리 준비해 둔 결과를 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface DatabaseService {
    String getData();
}

public class DatabaseServiceStub implements DatabaseService {

    @Override
    public String getData() {
        return "Stubbed data";
    }
}

public class MyClassTest {

    @Test
    public void testMethodWithStub() {
        DatabaseService databaseStub = new DatabaseServiceStub();
        MyClass myClass = new MyClass(databaseStub);
        // Call the method that uses the stubbed database service
        String result = myClass.processData();
        // Assert the result
        assertEquals("Stubbed data", result);
    }
}

Fake

in-memory test database가 대표적인 사례로 실제로 동작하진 않지만 정해진 결과값을 리턴하도록 하드코딩된 객체를 말합니다. 그렇기 때문에 구현은 가지고 있지만 실제 사용하는 객체처럼 보일 뿐, 실제 객체의 동작과는 차이가 있습니다.

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
public interface EmailService {
    void sendEmail(String to, String message);
}

public class FakeEmailService implements EmailService {
    private List<String> sentEmails = new ArrayList<>();

    @Override
    public void sendEmail(String to, String message) {
        // 이메일을 실제로 보내지 않고, 리스트에 추가한다.
        sentEmails.add(to + ": " + message);
    }

    public List<String> getSentEmails() {
        return sentEmails;
    }
}

public class FakeEmailServiceTest {
    @Test
    public void testFakeEmailService() {
        FakeEmailService fakeEmailService = new FakeEmailService();
        // 테스트할 로직 작성
    }
}

Spy

테스트에서 특정 객체가 사용되었는지 그 객체의 예상된 메서드가 정상적으로 호출되었는지 확인해야하는 상황이 생기는 경우에 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SpyList extends ArrayList<String> {
    private boolean addMethodCalled = false;

    @Override
    public boolean add(String element) {
        addMethodCalled = true;
        return super.add(element);
    }

    public boolean isAddMethodCalled() {
        return addMethodCalled;
    }
}

public class SpyListTest {
    @Test
    public void testSpyList() {
        SpyList spyList = new SpyList();
        // 테스트할 로직 작성
    }
}

Mock

어떤 동작을 했을 때, 어떤 결과를 주는지에 대해 프로그래밍된 객체를 말합니다. MockPaymentGateway는 실제 프로그램에서 사용되는 객체로 그 객체를 가지고 와서 테스트하기 때문에 실제 프로세스가 구현이 되어 잘 동작하는지 확인할 수 있습니다.

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
public interface PaymentGateway {
    boolean processPayment(double amount);
}

public class MockPaymentGateway implements PaymentGateway {
    private boolean processPaymentCalled = false;

    @Override
    public boolean processPayment(double amount) {
        processPaymentCalled = true;
        // 실제 결제 프로세스를 모킹하여 테스트에 사용한다.
        return true;
    }

    public boolean isProcessPaymentCalled() {
        return processPaymentCalled;
    }
}

public class MockPaymentGatewayTest {
    @Test
    public void testMockPaymentGateway() {
        MockPaymentGateway mockPaymentGateway = new MockPaymentGateway();
        // 테스트할 로직 작성
    }
}

그럼 언제 어떤 테스트 더블을 사용해야 할까?
쓰임에 따라 사용해야할 테스트 더블은 다른데, Mockist TDD의 경우에는 Mock만을 사용하고, Classicist TDD의 경우에는 Fake, Stub, Spy를 사용하는 것이 적절하고 경우에 따라서는 Mock도 사용할 수 있습니다. Mockist과 Classicist에 대해서는 아래 작성한 글을 참고해주세요.


TestDouble - Martin Flower
Test Double(테스트 더블)알아보기
TDD: Test Doubles in Unit Testing. Should we use Fakes? Stubs? Mocks?

This post is licensed under CC BY 4.0 by the author.