윤개발

Test Doubles(테스트 더블) — Dummy, Fakes, Mocks, Spy and Stubs 본문

테스트

Test Doubles(테스트 더블) — Dummy, Fakes, Mocks, Spy and Stubs

DEV_SJ 2021. 7. 2. 16:46

자동화된 테스트를 작성할 경우에 테스트들이 의존성을 가지고 같이 실행되어야 하는 경우가 많이 있는데요.

예를 들어 사용자를 조회하는 로직을 테스트한다고 한다면 UserService에서 UserRepository, 그리고 DB까지 연결되어있습니다.  UserService만 테스트하고 싶어도 이어지는 연결로 인해 독립적인 테스트가 힘들어집니다. 또한 아직 UserRepository가 구현되지 않은 경우에도 테스트가 힘들어지죠.

 

이런 경우에 주로 실제 객체가 아닌 단순한 객체를 이용하여 테스트하게 되는데 이를 테스트 더블이라고 합니다.

용어 자체는 제라드 메스자로스(Gerard Meszaros)가 만든 용어로,  스턴트 더블(영화 촬영에서 말하는 스턴트 대역 배우)에서 아이디어를 얻어서 만든 용어입니다. 

 

테스트 더블은 실제 객체가 아닌 객체로 코드를 그 외의 모든 코드에서 떼어 놓을 수가 있습니다.

UserService만 테스트가 가능해지는 것입니다. 이번 포스팅에서는 테스트 더블의 종류에 대해 알아보겠습니다.

들어가기 전에 위 그림의 객체들을 알아볼 것이며 오른쪽으로 갈수록 테스트 생성이 어려워지므로 적절한 테스트 객체를 선택하는 것이 중요합니다.


Dummy

Dummy는 가장 기본적인 테스트 더블입니다. 객체가 필요하지만 내부 기능이 필요하지는 않을 때 사용하게 됩니다.

예를 들어 테스트 중에 함수 파라미터로는 전달되지만 내부에서 사용되지 않는 경우입니다.

또 아래와 같은 경우도 Dummy Test로 볼 수가 있습니다.

public interface AccountDao {
    void showMember();
}
public class AccountDaoDummy implements AccountDao{
    @Override
    public void showMember() {
        // dummy
    }
}

AccountDaoDummy는 아무런 기능을 하지 않고 반환 값도 없습니다. AccountDao를 사용하는 Client 입장에서는 Dummy를 호출하기만 하면 되는 테스트이므로 내부 동작과는 상관없이 테스트는 성공하게 됩니다.


Fakes

Fakes는 실제로 사용된 객체는 아니지만 같은 동작을 하는 구현된 객체입니다.

Fakes는 원래 객체의 단순화된 버전입니다. 동작의 구현은 되어있지만 프로덕션에서 사용할 수는 없는 객체입니다. 아래 사진으로 이해해봅시다.

LoginService를 테스트하기 위해서는 AccountDAO를 이용해야 합니다.

하지만 AccountDao가 아직 구현되지 않았거나  독립적인 테스트를 하고싶다면 Fake 객체를 사용하며 됩니다.

FakeAccountDAO객체는 임의의 HashMap 객체를 생성하고 마치 진짜인 것처럼 LoginService가 보낸 메시지에 반응합니다. 

public class FakeAccountRepository implements AccountRepository {
       
       Map<User, Account> accounts = new HashMap<>();
       
       public FakeAccountRepository() {
              this.accounts.put(new User("john@bmail.com"), new UserAccount());
              this.accounts.put(new User("boby@bmail.com"), new AdminAccount());
       }
       
       String getPasswordHash(User user) {
              return accounts.get(user).getPasswordHash();
       }
}

이와 같은 가짜 구현을 통해 프로토타이핑을 할 수 있고 실제 데이터베이스 설계를 연기하면서 테스트를 신속하게 실행할 수 있어집니다. 


Stub

stub은 Dummy 데이터가 실제로 동작하도록 만들어둔 객체입니다.

미리 정의된 데이터를 보유하고 테스트 호출시에 응답하도록 사용되는 객체입니다.

테스트를 위해 프로그래밍된 항목이며 이외의 항목에는 응답하지 않습니다.

averageGrades 메서드 호출에 응답하기 위해 데이터베이스에서 데이터를 가져와야 하는 상황입니다.

실제 객체 대신 Stub을 도입하고 어떤 데이터를 반환해야 하는지 정의 후 리턴하고 있습니다.

    @Before
    public void setUp() throws Exception {
        gradebook = mock(Gradebook.class);
        student = new Student();
    }

    @Test
    public void calculates_grades_average_for_student() {
        when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook
        double averageGrades = new GradesService(gradebook).averageGrades(student);
        assertThat(averageGrades).isEqualTo(8.0);
    }

when을 이용하여 반환할 데이터를 미리 정의한 후 테스트하는 코드입니다.


Spy

스파이는 Stub의 역할을 하면서 약간의 정보를 더 기록합니다.

예상된 메서드가 잘 호출되었는지, 몇 번이나 호출되었는지 등으로 사용하게 됩니다.

예를 들어 이메일을 몇 번 발송했는지 기록하는 테스트 용도로 가능합니다.

public class MailingService {
    private int sendMailCount = 0;
    private Collection<Mail> mails = new ArrayList<>();

    public void sendMail(Mail mail) {
        sendMailCount++;
        mails.add(mail);
    }

    public long getSendMailCount() {
        return sendMailCount;
    }
}

sendMailCount를 이용해 메일의 발송 횟수를 더 기록하는 코드입니다.


Mock

Mock은 행위를 검증하기 위해 가짜 객체를 만들고 테스트하는 방법입니다. 위에서 본 Stub은 상태를 검증할 때 사용합니다.

행위 기반 테스트는 복잡도나 정확성 등이 상태 기반 테스트보다 어려운 부분이 많기 때문에 상태기반 테스트가 가능하다면 만들지 않는 것이 좋습니다. 코드를 통해 알아보겠습니다.

 

mockito 코드 Sample

    @Test
    public void mockTest(){
        List mockedList = mock(List.class);
	
    	// 행위 추가
        mockedList.add("one");
        mockedList.clear();

		// 행위 검증
        verify(mockedList).add("one");
        verify(mockedList).clear();
    }

Mock은 다음과 같이 행위가 일어났는지를 검증합니다. Mock에 대한 내용은 하나의 포스팅 분량이므로 다음 포스팅에서 따로 다루도록 하겠습니다.

 

마무리

테스트를 진행한다면 위의 항목들 중에서 어떤 테스트가 필요할지 결정하고, 결정한 테스트가 경계를 벗어나는지 보며 가능한 최소한의 테스트를 선택해야 합니다. 순서대로 Dummy -> Stub -> Fake -> Spy -> Mock 입니다.


 

출처 및 참고

https://martinfowler.com/bliki/TestDouble.html

 

bliki: TestDouble

Test Double is generic term for fakes, mocks, stubs, dummies and spies.

martinfowler.com

https://martinfowler.com/articles/mocksArentStubs.html

 

Mocks Aren't Stubs

Explaining the difference between Mock Objects and Stubs (together with other forms of Test Double). Also the difference between classical and mockist styles of unit testing.

martinfowler.com


https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da

 

Test Doubles — Fakes, Mocks and Stubs.

In automated testing it is common to use objects that look and behave like their production equivalents, but are actually simplified. This reduces complexity, allows to verify code independently from…

blog.pragmatists.com

https://woowacourse.github.io/tecoble/post/2020-09-19-what-is-test-double/

 

Test Double을 알아보자

테스트 더블(Test Double)이란? xUnit Test Patterns의 저자인 제라드 메스자로스(Gerard Meszaros…

woowacourse.github.io

https://brunch.co.kr/@tilltue/55

 

Test Doubles 정리

Dummy, Fake, Stub, Spy, Mock | 테스트 더블이란? 실제 객체를 대신해서 테스팅에서 사용하는 모든 방법을 일컬여 호칭하는 것이다. (영화 촬영시 위험한 역활을 대신하는 스턴트 더블에서 비롯되었다.)

brunch.co.kr

https://www.crocus.co.kr/1555

 

[Mockito] Mock 개념(Mock Object)

단위 테스트를 하기 위해서는 한번에 메서드 하나만을 실행해 보는 것인데 이러한 메서드가 다른 네트워크, 데이터베이스 등등 제어하기 어려운 것들에 의존하고 있다면 어떻게 단위 테스트를

www.crocus.co.kr

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html

 

Mockito - mockito-core 3.11.2 javadoc

Latest version of org.mockito:mockito-core https://javadoc.io/doc/org.mockito/mockito-core Current version 3.11.2 https://javadoc.io/doc/org.mockito/mockito-core/3.11.2 package-list path (used for javadoc generation -link option) https://javadoc.io/doc/org

javadoc.io

 

'테스트' 카테고리의 다른 글

TDD - 테스트 주도 개발 by 켄트 백  (0) 2021.07.02
AssertJ  (0) 2021.07.01
JUnit 5 테스트란 무엇이며 어떻게 사용할까?  (0) 2021.06.02
Comments