Mock 객체에 대하여(The Little Mocker)

Clean Code 의 밥 아저씨.
글을 너무 재밌게 쓰시는 분이죠.
오래전에 블로그에 남기신 Mocking 에 대한 글을 번역해 봅니다.
이 글을 보면서 딱딱한 기술적인 내용을 이렇게 재밌게도 설명할 수 있구나하고 감탄을 했습니다.


다음은 mocking 에 대해 나눈 대화입니다.

이게 뭔가요?

interface Authorizer {
    public Boolean authorize(String username, String password);
}

인터페이스지.

그럼 이것은요?

public class DummyAuthorizer implements Authorizer {
    public Boolean authorize(String username, String password) {
        return null;
    }
}

그건 더미(Dummy)라고 하지.

Dummy 는 뭐하는건데요?

어떤 식으로 쓰일지 전혀 신경안써도 되는 곳으로 전달할 때 사용하지.

예를 들면요?

테스트를 할 때 인자(argument)를 전달해야 하는 상황인데, 그 인자는 전달해도 절대 실제로는 사용되지는 않을 것이라는걸 네가 알고 있을 때 사용하지.

실제 코드로 설명해주실 수 있나요?

물론이지.

public class System {
    public System(Authorizer authorizer) {
        this.authorizer = authorizer;
    }

    public int loginCount() {
        //returns number of logged in users.
    }
}

@Test
public void newlyCreatedSystem_hasNoLoggedInUsers() {
    System system = new System(new DummyAuthorizer());
    assertThat(system.loginCount(), is(0));
}

아... 알겠어요.
System 객체를 생성하기 위해 생성자의 인자로 Authorizer 를 전달해야 하는군요.
Authorizer 의 authorize 메소드가 절대 호출될 일이 없구요.
이 테스트에서는 아무도 login 을 안할거니까요.

그렇지.

그래서, DummyAuthorizer 의 authorize 메소드가 null 을 리턴해도 error 가 아닌 것이네요.

맞아. 사실 그게 Dummy 가 리턴할 수 있는 최선이라고 할 수 있지.

왜 그렇죠?

왜냐하면, 누군가가 그 Dummy 를 사용하려고 하면 NullPointerException 을 만날 것이거든.

아... 나는 Dummy 가 사용되지 않기를 바라는데, 누군가가 사용하는 상황 말이군요.

그렇지! 그게 바로 dummy 를 사용하는 이유지.

근데, 그게 mock 이란거 아닌가요?
저는 지금까지 이런 테스트 객체를 mock 이라고 부르는줄 알았는데요.

그건 맞아. 근데, 그건 일종의 은어(Slang)일 뿐이야.

은어라구요?

그래.
"mock"이라는 단어는 종종 테스트에서 사용되는 모든 종류의 객체들을 일컬을 때 비공식적으로 사용되긴 하지.

그럼 이런 테스트용 객체들을 부르는 공식적인 이름이 있나요?

있지. "Test Doubles" 라고 부르지.

영화에 나오는 "Stunt Doubles"와 같은 의미인가요?
(역자: 영화의 스턴트맨같은 대역을 말함)

맞아.

그럼, "mock" 이라는 말은 그냥 사람들이 은어처럼 말하는 비공식적인 단어라는거죠?

꼭 그런건 아니고 "mock" 이라는 단어도 공식적인 의미를 가지고 있긴 해.
다만, 우리가 비공식적으로 말할 때는 mock 을 Test Double 과 동의어처럼 사용하는거지.

왜 이렇게 두 개의 단어를 모두 사용하게 된건가요?
그냥 Mock 대신에 Test Double 이란 말을 사용하지 않구요.

히스토리가 있어.

히스토리요?

그래.
오랜 옛날에 아주 똑똑한 사람들이 논문을 하나 썼는데, 거기에서 "Mock 객체"를 처음 소개하고 그 용어를 만들었지.
많은 사람들이 그 논문을 읽었고 그 용어를 사용하기 시작했어.
그런데, 그 논문을 읽어보지 않은 또 다른 사람들은 그 용어를 듣고는 더 넓은 의미로 사용하기 시작했어.
그 사람들은 더 나아가서 그 단어를 "동사"로까지 변화시켜 버렸지.
이런 식으로 말이야.
"Let's mock that object out."
"We've got a lot of mocking to do"

언어에서는 그런 일들이 자주 일어나죠.

그래 맞아. 특히 단어가 한음절일 때 발음하기가 쉬우니까 그런 일들이 많이 일어나지.

네. 정말 말하기가 더 쉬운 것 같아요.
"Let's make a test double for that" 대신에 "Let's mock that." 라고 말하는 것이요.

맞아. 항상 구어체가 대세인건 피할 수가 없지.

네, 하지만 우리가 정확히 말해야하는 상황에서는...

그렇지. 공식적인 언어를 사용하는게 좋지.

자 그럼, "Mock"이라는건 대체 뭔가요?

그걸 말하기 전에, 다른 종류의 "Test Doubles"들을 먼저 좀 살펴보자.

어떤 것들이 있을까요?

먼저 Stub 에 대해 살펴보도록 하자.

Stub 은 뭔데요?

이런게 Stub 이지.

public class AcceptingAuthorizerStub implements Authorizer {
    public Boolean authorize(String username, String password) {
        return true;
    }
}

이건 true 를 리턴하네요.

맞아.

왜 그렇죠?

네가 시스템의 어떤 부분을 테스트하려는데, 그 테스트에서는 이미 로그인은 정상적으로 되었다는 가정이 필요하다고 생각해봐.

내가 이미 로그인을 했다.

너는 이미 로그인 로직은 테스트를 완료해서 정상적으로 동작한다는걸 알고 있어.
이 상황에서 로그인을 다시 테스트할 필요는 없지 않겠어?

필요가 없다는게 너무 쉬워서 말인가요?

아니, 추가적인 시간이 걸린다는거지.
그리고, 뭔가 부가적인 셋업 과정도 필요할 수 있고.
만약 로그인 로직에 버그라도 생기면 너의 테스트는 깨지게 되는거야.
불필요한 coupling 이 생기는거지.

흠... 일단 그 말에 동의한다고 하죠. 그 다음은요?

넌 단지 테스트할 때 AcceptingAuthorizerStub 을 끼워넣으면 되는거야.

그러면 아무 검사없이 그냥 사용자를 인가(authorize)하게 되겠네요.

그렇지.

만약 인가되지 않은 사용자를 다루는 시스템 테스트를 하려고 할 때는 false 를 리턴하는 stub 을 사용하면 되는거구요.

그렇지 그렇지.

알겠어요. 그 밖에 또 무엇이 있나요?

다음은 이런게 있어.

public class AcceptingAuthorizerSpy implements Authorizer {
    public boolean authorizeWasCalled = false;

    public Boolean authorize(String username, String password) {
        authorizeWasCalled = true;
        return true;
    }
}

웬지 이건 Spy 라고 부를 것 같네요.

맞아.

이건 왜 사용하는건가요?

이것은 시스템에서 authorize 메소드를 호출했는지 안했는지 네가 확인을 하기 위해 사용하지.

아... 알겠어요.
stub 처럼 끼워넣은 후 테스트 막판에 authorizeWasCalled 변수를 검사해서 authorize 메소드가 호출됐었는지를 확인하는데 사용하면 되겠네요.

정확히 이해했네.

그니깐 Spy 는 호출자를 감시하는군요.
이런 방법을 쓰면 모든 종류의 것들을 감시하고 기록해둘 수 있겠어요.

당연히 그렇지. 예를 들면, 호출 횟수도 알 수 있어.

그렇네요. 또는 각 호출 시마다 인자들의 리스트를 저장해둘 수도 있겠어요.

그래. Spy 를 이용하면 네가 테스트하는 알고리즘의 내부 동작을 들여다볼 수가 있지.

그게 바로 coupling 같은데요.

그렇지. 맞아. 그래서 조심해야해.
Spy 를 많이 사용할수록 테스트가 어플리케이션 구현과 점점 엮이게 되는거야.
그렇게 되면 망가지기 쉬운 테스트가 되어버리지.

망가지기 쉬운 테스트라는게 무슨 말이예요?

원래는 특정 원인에 의해 테스트에 문제가 생기면 안되는 상황에서, 그 원인에 의해 테스트에 문제가 생기는 것을 말해.

시스템의 코드를 좀 고쳤는데 일부 테스트에 문제가 생기는거요.

그래, 하지만 테스트의 설계를 잘하면 그런 문제는 최소화할 수 있지.
Spy 도 문제없이 잘 작동할 수 있어.

알겠어요. 이해됐어요.
또 어떤 test double 들이 있나요?

두 개가 더 있어. 첫번째는 아래 코드를 좀 보자.

public class AcceptingAuthorizerVerificationMock implements Authorizer {
    public boolean authorizeWasCalled = false;

    public Boolean authorize(String username, String password) {
        authorizeWasCalled = true;
        return true;
    }

    public boolean verify() {
        return authorizedWasCalled;
    }
}

이게 바로 mock 이네요.

그래. 진정한 Mock 이지.

"진정한"이라구요?

그래. 이게 바로 공식적인 "mock 객체"라는거야. 그 단어의 원래 뜻 말야.

알겠어요.
그런데, assertion 부분을 (진정한 mock 의) verify 메소드쪽으로 옮기신 것 같네요?

맞아. Mock 은 자신이 무엇을 테스트하는지를 알거든.

그게 다예요? 그냥 assertion 을 mock 에 넣은게 전부?

그렇지는 않지.
말한대로 assertion 이 mock 안으로 옮겨간건 맞는데, 여기서는 mock 이 활동(behavior)을 테스트하고 있는거야.

활동이요?

그래.
mock 은 함수의 리턴값에 그리 관심이 없어.
그보다는 무슨 함수가 호출되었는지, 어떤 인자들과 함께 호출되었는지, 그리고 언제, 어떻게 호출되었는지에 더 관심이 있지.

그럼 mock 은 언제나 spy 인건가요?

그래, 맞아.
mock 은 테스트되는 모듈의 활동을 spy 하는거야.
그래서, mock 은 테스트 대상의 활동이 어때야 하는지를 이미 알고 있지.

흠... 그런 활동의 검사부분을 mock 안으로 넣어버리는건 coupling 인 것처럼 보이는데요.

그래, 맞아.

근데 왜 그렇게 해요?

그렇게 하면 mocking 을 위한 도구를 작성하기가 훨씬 쉽거든.

mocking 도구요?

그래, JMock 또는 EasyMock, Mockito 같은 것 말야.
이런 도구들은 mock 객체가 필요할 때 쉽게 만들 수 있게 도와주지.

뭔가 복잡해 보이네요.

그렇지 않아.
마틴 파울러가 작성한 유명한 논문에 이게 잘 설명되어 있어.

책도 있을 것 같은데요? 그쵸?

물론 있지.
Growing Object Oriented Software, Guided by Tests 라는 대단한 책이 있지. mock 을 이용한 설계 철학에 대한 책이야.

오케이. 이젠 끝난건가요?
아직 한가지 test double 이 더 남아있다고 하셨는데...

그래, 하나 더 있어. Fake.

public class AcceptingAuthorizerFake implements Authorizer {
    public Boolean authorize(String username, String password) {
        return username.equals("Bob");
    }
}

좀 괴상하네요. "Bob" 이라는 이름을 가진 사람은 누구나 인가(authorize)되겠네요.

그래,
Fake 는 업무 관련 활동(behavior)을 가지는거야.
다른 데이터를 fake 에 주게 되면 fake 가 다른 방식으로 활동하도록 유도할 수 있어.

마치 simulator 처럼 보이네요.

그래 맞아. simulator 는 fake 라고 할 수 있지.

fake 는 stub 은 아닌거죠?

아니야.
fake 는 업무 관련 활동(business behavior)을 가져야 하거든. stub 은 그렇지 않지.
지금까지 살펴본 어떤 test double 도 실제 업무 관련 활동로직은 가지지 않아.

fake 는 다른 test double 들과는 근본적으로 다른 level 이군요.

정말 그래.
우리가 Mock 은 일종의 spy 이고, spy 는 일종의 stub 이고, stub 은 일종의 dummy 라고 할 수는 있어.
그런데, fake 는 다른 것들의 한 종류라고 말할 수가 없어.
fake 는 완전히 다른 종류의 test double 이야.

fake 는 복잡해질 수 있겠네요.

엄청나게 복잡해질 수도 있지.
너무 복잡해서 그를 위한 unit test 가 필요하기도 해.
fake 가 극단적으로는 실제 시스템(real system)이 되는거지.

흠...

흠...
나는 fake 를 자주 작성하지는 않는다.
사실은 30 년 넘는 시간동안 한번도 작성한 적이 없어.

와아... 그럼 뭘 작성하세요? 다른 test double 들을 모두 사용하시나요?

주로 stub 과 spy 를 사용하지.
그리고, 내가 직접 작성하는 편이지 mocking 도구를 자주 사용하지는 않아.

Dummy 도 사용하세요?

아주 드물게 사용하지.

mock 은요? 자주 사용하세요?

내가 mocking 도구를 사용할 때만 사용하고 있어.

하지만, mocking 도구는 사용하지 않으신다면서요?

맞아. 거의 사용하지는 않아.

왜 사용 안하세요?

왜냐하면, stub 과 spy 로 작성하는게 엄청 쉽기 때문이야.
IDE 를 사용하면 아주 쉽게 할 수 있지.
그냥 내가 interface 를 알려주면 IDE 가 그걸 구현하도록 할 수 있거든.
자 이걸 봐.
IDE 가 dummy 를 주면, 내가 약간 변경하고 stub 이나 spy 로 변환하는거지. mocking 도구가 거의 필요없지.

그럼, 그냥 단지 편의성 면에서의 선택 문제?

그렇지.
난 mocking 도구의 이상한 syntax 가 싫고, 또 내 셋업 환경이 복잡해져서 싫은 것도 있고.
대부분의 경우 내가 직접 작성하는 test double 들이 더 간단하다는걸 알고 있거든.

알겠어요.
대화를 나눠주셔서 감사합니다.

별말씀을.

You may also like...

5 1 vote
Article Rating
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
1
0
Would love your thoughts, please comment.x
()
x