Mock을 이용한 단위 테스트 TDD(Mock객체)

최근 관심사가 DDD(Domain Driven Design) 과 "단위 테스트에 Mock 객체 적용하기" 이다. Mock 객체 적용하기도 어떤 라이브러리로 어떻게 작성할까 하는 구현 수준의 문제 보다는 이것을 활용할 경우 설계에 어떤 영향을 미칠까 하는 것을 알고 싶은 것이다.

실제 프로젝트에 적용해서 머릿속의 이론과 몸으로 느끼는 실제(정확히는 손가락과 눈이겠지?)로 터득하는 것이 좋겠으나 프로그래밍과 좀 거리를 두고 있는 상황이다보니 일단 관련된 글들을 간단하게 정리해 보았다.

정의
Mock Object 페이지 중간 정도에는 Mock Object 를 한 마디로 정의하는 문구가 나온다.

Mock Object 는 검사하고자 하는 코드와 맞물려 동작하는 객체들을 대신하여 동작하기 위해 만들어진 객체이다. 검사하고자 하는 코드는 Mock Object 의 메서드를 부를 수 있고, 이 때 Mock Object는 미리 정의된 결과 값을 전달한다. MockObject는 자신에게 전달된 인자를 검사할 수 있으며, 이를 테스트 코드로 전달할 수도 있다.

A MockObject is an object created to stand-in for an object that your code will be collaborating with. Your code can call methods on the MockObject, which will deliver canned results. The MockObject can also examine the arguments passed to it, or can deliver the arguments to your test code to be examined.


Mock Object 과 함께 자주 언급되는 녀석으로는 Stub을 들 수 있다. 둘의 차이점을 알고자 한다면, MSDN Magazine의 Test Double의 연속성 살펴보기 페이지를 보면 도움이 될 것이다. 이 페이지는 테스트를 위한 가짜 객체들(Test Double)의 유형과, 각 유형의 특징을 설명하고 있다. Martin Fowler의 글 Mocks Aren't Stubs에도 잘 나와있다. 둘 다 내용은 비슷하지만 Martin Fowler 글의 설명이 조금 더 간략하기에 옮겨 적는다.

- Stub 은 테스트 과정에서 일어나는 호출에 대해 지정된 답변을 제공하고, 그 밖의 테스트를 위해 별도로 프로그래밍 되지 않은 질의에 대해서는 대게 아무런 대응을 하지 않는다. 또한 Stub은 email gateway stub 이 '보낸' 메시지를 기억하거나, '보낸' 메일 개수를 저장하는 것과 같이, 호출된 내용에 대한 정보를 기록할 수 있다.
- Mock은 Mock 객체가 수신할 것으로 예상되는 호출들을 예측하여 미리 프로그래밍한 객체이다.

- Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it 'sent', or maybe only how many messages it 'sent'.
- Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.


예시
흔히들 Stub 과 비교가 많이 되니 Stub 코드 한벌과 비교를 하면 Stub 과 Mock 의 차이를 알 수 있다. 위에 언급한 MSDN 매거진에 잘 나와있으니 참고하면 된다. Stub 코드는 생략하고 Mock Object를 이용한 코드를 살펴본다. 여러 라이브러리가 있고, 라이브러리에 따라 구현된 코드는 다르겠지만 개념적으로 아주 다르지는 않다고 본다.Mockito를 이용해 사용된 예를 보자. 코드는 Mockito API 문서에서 따왔다.

1. Behavior verify

//Let's import Mockito statically so that the code looks clearer
import static org.mockito.Mockito.*;

//mock creation
List mockedList = mock(List.class);

//using mock object
mockedList.add("one");
mockedList.clear();

//verification
verify(mockedList).add("one");
verify(mockedList).clear();


2. Stubbing

//You can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);

//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

//following prints "first"
System.out.println(mockedList.get(0));

//following throws runtime exception
System.out.println(mockedList.get(1));

//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));

//Although it is possible to verify a stubbed invocation, usually it's just redundant
//If your code cares what get(0) returns then something else breaks (often before even verify() gets executed).
//If your code doesn't care what get(0) returns then it should not be stubbed. Not convinced? See here.
verify(mockedList).get(0);


언제 Mock Object를 써야하나?
정의를 말하기 전에 필요성이 먼저 나와야 흐름이 맞지만, 일단 있는 개념을 알고 가자는 측면에서 정리하는 것이니 용서해주세요.
c2 wiki 의 중간 부분에 Mock Object 를 이용하는 이유가 정리되어 있으니 살펴보자. 같은 페이지에 이에 대한 비판적인 논의가 오가고 있으니 너무 믿지는 말자. 내가 많이 알고 있어서 비판할 수 있는 역량이 된다면 정리해서 올리면 좋겠지만 나도 잘 모르니 일단 그대로 옮기고 본다. 다음과 같은 상황에서 Mock Object를 사용하면 수월하게 테스트의 목적을 달성할 수 있다고 한다.
1. 실제 객체가 비-결정적인(즉, 그때 그때 주변 여건에 따라 다른) 행위를 보인다.
2. 실제 객체에 대한 초기 설정이 까다롭다. (DB 나 네트웍 커넥션 등이 이에 해당하겠지.)
3. 실제 객체의 행위를 재현하기가 어렵다. (특정 네트웍 에러 등. ) -> 1번 이유와 비슷한 듯.
4. 실제 객체 행위가 느리다. (역시 DB 작업이나 네트웍 통신등이 해당할 듯.)
5. 실제 객체가 UI 를 가지고 있거나, UI 그 자체이다.
6. 테스트 과정에서 객체에 질의를 던져야 할 필요가 있는데, 실제 객체는 해당 질의를 처리하지 않는다. 예) 콜백이 호출되었는 가? (이 경우 Mock Object는 실제 객체보다 "더 많은" 기능을 가지게 된다. 이는 Stub가 일반적으로 실제 객체보다 "훨씬 적은" 기능을 갖는 것과 반대이다.)
7. 실제 객체는 대부분 "정상 동작"을 하지만, 아주 가끔 "예외 동작"을 한다. 단위 테스트를 통해서 특정 객체의 "정상 동작"/"예외 동작" 여부와 관계없이 시스템의 나머지 부분은 정상적인 동작을 하는 것을 확인하고자 한다. -> 2번 이유와 비슷한 듯.
8. 아직 실제 객체를 만들지 않았다.

1. real object has non-deterministic behavior
2. real object is difficult to set up
3. real object has behavior that is hard to cause (e.g., network error) (similar to 1)
4. real object is slow
5. real object has (or is) a UI
6. test needs to query the object, but the queries are not available in the real object (e.g., "was this callback called?") (This needs a MockObject that has *more* stuff than the real object; the other cases usually require a stub that is far smaller than the real object).
7. real object acts "normal" most of the time, but once a month (or even less often) it does something "exceptional". We want UnitTests to make sure the rest of the system does the RightThing whether or not the object is acting "normal" or "exceptional". (Is this the same as #2 ?)
8. real object does not yet exist


위의 내용은 real object 대신하여 Test Double 을 쓰면 도움이 되는 경우를 이야기하는 것이지, 많은 Test Double 의 유형 중에서 Mock Object 를 선택하는 장점을 이야기하는 것은 아니라고 보인다. 그럼 Test Double 중 Mock 을 쓰는 이유는 무엇일까? 다시 MSDN 매거진의 내용을 인용한다.

Test Double 유형장점단점
Dummy만들기가 매우 쉽습니다.용도가 제한적입니다.
Stub만들기가 쉽습니다.유연성에 제약이 있습니다. 단위 테스트에서 결과를 명확하게 제공하지 않습니다. 멤버가 올바르게 호출되었는지 여부를 확인하는 기능이 없습니다.
Spy멤버가 올바르게 호출되었는지 확인할 수 있습니다.유연성에 제약이 있습니다. 단위 테스트에서 결과를 명확하게 제공하지 않습니다.
Fake다양한 시나리오에 사용할 수 있는 거의 완전한 구현을 제공합니다.만들기가 어렵습니다. 그 자체에 대한 단위 테스트가 필요할 정도로 복잡할 수 있습니다.
MockTest Double을 효율적으로 만들 수 있습니다. 멤버가 올바르게 호출되었는지 확인할 수 있습니다. 단위 테스트에서 결과를 명확하게 제공합니다.익히기가 어렵습니다.


"Test Double을 효율적으로 만들 수 있습니다" 라는 문구가 눈에 띄인다. 별도로 spy 를 만들어야 가능한 내용에 대해서도 확인 가능하고, java의 경우 특정 interface 를 구현하는 stub을 만들어야 할 경우 줄줄이 따라 붙는 꼴보기 싫은 구현 안된 메서드들의 목록을 보지 않아도 되고. 그러나 "익히기가 어렵습니다" 라는, 학습곡선이라는 무시무시한 장벽이 보인다. 하아, 그러나 저 고지만 넘으면 신천지가 보인다면야! 근데 정말 신천지가 보일까? 다 좋은 걸까? 테스트 할 때만 좋은 그런 물건인가?

Mock Object 활용을 통한 테스트 방식의 전환: 상태 기반에서 행위 기반으로
Mock Object 및 Library 적용을 통한 변화 중 가장 큰 것이 테스트 방식의 전환이 아닐까 생각한다.
Stub 을 활용한 "상태 기반" 테스트 방식은 Stub 만들고, 테스트 대상 메서드 등을 실행하고, 마지막에 원하는 상태가 되었는 지를 검증하는 구조이다. 즉, "영숙이가 철수한테 1000만원을 이체를 한다" 라면 "영숙이 잔고 2000만원, 철수 잔고 500만원" 이라는 초기 상태를 설정하고, 뭔가 중간에 쿵짝쿵짝 한 다음, 마지막에 "영숙이 잔고 1000만원, 철수 잔고 1500만원" 이라는 최종 상태가 우리가 원하는 상태인지를 확인하는 것이다.

이와 달리 "행위 기반" 테스트 방식은 "영숙이가 철수한테 1000만원을 이체를 한다" 라면 "영숙이 계좌에서 1000만원 빼" , "철수 계좌에 1000만원 넣어" 라는 중간 행위가 제대로 수행되었는지를 확인한다. 이 차이는 테스트 작성의 개념을 완전히 바꿔 놓는 것이므로 아주 큰 변화라고 생각하는데, 생각과 달리 이런 저런 글들에서 별로 다루질 않네.

Martin Fowler는 TDD 실천자를 전통주의자와 Mock 신봉자 두 부류로 나누어 이야기 하고 있다.

전통적 TDD 스타일은 가능한 실제 객체를 사용하고, 실제 객체를 사용하기 어려운 경우에만 Test Double 을 사용한다. 따라서 전통적 TDD 주의자는 Data warehouse엔 실제 객체를, mail service에는 Test Double을 사용할 것이다. 이런 류의 Test Double은 그리 큰 문제가 되지 않는다.

Mock 신봉자의 TDD 스타일은 주목할 만한 행위를 가진 객체들에 대해 항상 mock 을 사용한다. 이 경우 Data warehouse 나 mail service 모두에 Test Double을 사용한다.

비록 많은 mock 프레임웍이 Mock 신봉자들의 테스트를 고려하여 설계되었지만, 많은 전통주의자들도 Test Double 생성에 유용하게 사용하고 있다.

The classical TDD style is to use real objects if possible and a double if it's awkward to use the real thing. So a classical TDDer would use a real warehouse and a double for the mail service. The kind of double doesn't really matter that much.

A mockist TDD practitioner, however, will always use a mock for any object with interesting behavior. In this case for both the warehouse and the mail service.

Although the various mock frameworks were designed with mockist testing in mind, many classicists find them useful for creating doubles.


Mock Object 기반의 "행위 기반" 검증을 위주로 한다면, 일관성을 위해 Mock Object를 적극적으로 활용해야 한다. Stub 이나 실제 객체로는 이런 행위 기반 검증을 할 수 없기 때문에 (한다면야 못할게 뭐가 있겠냐만, 하기가 매우 귀찮아서 실질적으로는 거의 불가능하다고 생각함) 검증할 대상은 일단 Mock 으로 간다고 생각을 해야 할 것이다.

위의 글에서와 같이 전통주의자들이 Mock 을 쓰면 안된다는 것은 아니다. Test Double 이 필요할 땐 써야지. 다만 "상태 기반" 검증을 할 것이냐, "행위 기반" 검증을 할 것이냐는 확실히 선을 그어야 한다고 생각한다. 전략마저 섞어쓰는 것은 문제를 야기할 것으로 생각한다. (딱히 무슨 문제인지는 모르겠다만.) 그리고 Martin Folwer 가 굳이 두 파를 나눈 것도 사용 기술이 아닌, 이러한 전략적 구분에서 출발한다고 생각한다.

Mock Object가 설계에 미치는 영향
Mock Object 를 사용할 경우 단위 테스트 코드 이외에 소프트웨어 자체에 어떤 영향이 있는 지에 대해 알아보자. Martin Fowler 의 글, MSDN 매거진의 글(Using Mocks And Tests To Design Role-Based Objects), c2 wiki 의 글 정도가 검색에 걸렸다. 정리하면 다음과 같다.

- Role-Based Object 로 설계를 이끈다.
- Law of Demeter 를 준수하도록 도와준다.
- BDD(Behavior Driven Development) 를 실행한다.


위의 내용은 모두 TDD(Test Driven Development;테스트 주도 개발) 를 실천하고 있다는 전제 하의 내용이다. 당연한 전제이다. 이미 프로그램의 개발이 끝난 상태라면 Mock 을 쓰던 Stub 을 쓰던 테스트 코드 이외에 영향을 줄 수 없다. 테스트가 구현/설계에 영향을 미치려면 당연히 TDD 를 실천해야 한다. (윽, 이게 정말 어려운 것인데 말이지.) 그리고 각각의 항목에 대해서는 이런 저런 의견들이 분분하니 비판적으로 받아야들여야 한다. 그런데 비판적으로 받아들이기 위해선 알아야 할 게 너무나 많네. ^^;

각각의 항목에 대해서 살펴보자.

1. Role-Based Object로 설계를 이끈다.

MSDN 매거진의 글에서 따온 내용이다. 대략 내용은 TDD와 병행하여 Mock Object 를 사용할 경우, Role-Based Object 를 알아내기 좋다는 것으로 이해하였다.
TDD 체계 하에서 활용할 때 Mock Object는 시스템 내부에서 객체 내부의 구조 보다는 객체가 어떻게 연관되는 지를 강조함으로써 객체의 역할을 식별하는데 도움을 줄 수 있을 것이고, 이를 통해 좋은 객체 지향 설계를 하는 데 도움을 줄 수 있을 것이다. 일반적인 외부 의존 관계로부터 시스템을 격리하는 용도를 넘어서 설계를 개선하는 용도로 Mock Object를 활용하는 것은 상당히 흥미로운 일이다.

Within the realm of Test-Driven Development (TDD), mock objects can help you discover the roles that objects should play within a system, emphasizing how the objects relate to each rather than their internal structure. This technique can be employed to support good object-oriented design. Using mock objects as an aid to design turns out to be much more interesting than the common practice of using them simply to isolate a system from external dependencies.


Martin Fowler 의 글에서는 다음의 내용이 나온다. 내가 이해하기론 Martin Fowler 의 글이나 MSDN 의 글이나 의미하는 바는 동일하다고 보인다.
Mock 신봉자들은 role interface 를 선호하며, Mock Object 를 사용하여 테스트를 작성할 경우 role interface 기반의 설계를 하도록 돕는다고 얘기한다. 그 이유는 각각의 collaboration 이 별도의 mock으로 작성되고, 이러한 mock 들이 role interface로 바뀌어지기 때문이다. 위에 나온 예제 중 보고서 생성을 위해 string buffer를 사용하는 부분에서 Mock 신봉자들은 해당 도메인에서 의미를 갖는 특정한 역할(role)을 만들어 낼 것이다. 그리고 아마 이 역할은 string buffer 의 형태로 구현될 것이다.

Mockists favor role interfaces and assert that using this style of testing encourages more role interfaces, since each collaboration is mocked separately and is thus more likely to be turned into a role interface. So in my example above using a string buffer for generating a report, a mockist would be more likely to invent a particular role that makes sense in that domain, which may be implemented by a string buffer.


휘유, Role Based Object는 무엇이고, Role Interface는 또 무엇일까. Role Interface 는 Martin Folwer 의 글 Role Interface에 설명이 되어있다.

소프트웨어 요소 중 interface 는 두 가지 형태를 띈다. 하나의 형태는 Header Interface 요, 나머지는 Role Interface 다. Role Interface는 제공자와 소비자 간의 특정한 상호작용에 주시하여 정의된다. 제공자 부품은 일반적으로 몇개의 Role Interface를 구현한다. 그리고 이 Role Interface 하나 하나는 상호작용의 패턴을 나타낸다. 이와 반대되는 것이 Header Interface로, 제공자 부품은 하나의 인터페이스만을 가진다.

Interfaces for software elements take two forms, what I call role interface and HeaderInterface. A role interface is defined by looking at a specific interaction between suppliers and consumers. A supplier component will usually implement several role interfaces, one for each of these patterns of interaction. This contrasts to a header interface, where the supplier will only have a single interface.


Role Interface에 대한 자세한 내용은 Martin Fowler의 페이지를 참고하고, 왜 Mock Object 사용할 경우 Role Inteface(Role Based Object) 위주의 설계를 하기가 좋은 지는 MSDN 의 글을 참고합시다.

2. Law of Demeter 를 준수하도록 도와준다.

일단 Law Of Demeter 가 뭔지를 알아야 겠지. 다음은 wikipedia의 설명이다.
기능에 Law of Demeter를 적용할 경우, 객체 O의 메서드 M 는 다음과 같은 유형의 객체의 메서드 만을 호출할 수 있다.
1. 자기 자신 객체 O
2. 메서드 M의 인자로 넘어온 객체
3. 메서드 M 내부에서 생성된 객체
4. 객체 O 가 직접 포함하고 있는 부품 객체

More formally, the Law of Demeter for functions requires that a method M of an object O may only invoke the methods of the following kinds of objects:

1. O itself
2. M's parameters
3. any objects created/instantiated within M
4. O's direct component objects


한 마디로 얘기하면, timer.getTime() 해야지, getTimeManager().getKoreanTimer().getTime() 하지 말라는 소리다. 그럼 Mock Object 는 어떻게 이것을 돕지? 요기서 약간 헤매게 된다. Jay Flower 블로그의 글 (The Law of Demeter and Testability)에는 다음의 대목이 나온다.
흥미롭게도 Tim Mackinnon 과 Steve Freeman, 그리고 Philip Craig 는 그들의 글 "Endo-Testing Unit Testing with Mock Objects"에서 "...Mock Object 를 활용하여 작성된 코드는 Law Of Demeter 를 준수하는 경향을 보였다. 단위 테스트는 별다른 외부적 정책 없이도 오로지 지역 객체와 인자만을 참조하는 도메인 코드를 작성하는 방향으로 우리를 인도하였다" 라는 관점을 보였다. 그러나 난 Law Of Demeter 를 준수하도록 이끈 것이 Mock Object 라고 확신하지 못하겠다. Martin Folwer는 상호작용 기반의 테스트를 수행한 것이 개발자가 Law of Demeter를 좇게 만든 것임을 알았다고 말하고 있다.

Interestingly Endo-Testing Unit Testing with Mock Objects by Tim Mackinnon, Steve Freeman and, Philip Craig presents the view that “…code developed with Mock Objects tends to conform to the Law of Demeter, as an emergent property. The unit tests push us towards writing domain code that refers only to local objects and parameters, without an explicit policy to do so.” I am not so sure that use of mocks naturally leads to conformance to the Law of Demeter. Martin Fowler noticed that developers that perform interaction based testing tend to follow the Law of Demeter (Mocks Aren’t Stubs):


흠, 어찌되었건 (Mock Object가 직접 도움을 준 것이던, Mock Object를 활용한 상호작용 기반의 테스트를 수행한 것이던) 결과적으로 Law of Demeter 를 지키는 코드를 만들게 되는 것은 맞다고 보인다. 그러나 Martin Fowler 는 자신의 글에서 다음과 같이 충고하고 있다. 즉, Law of Demeter 를 극단적으로 끌고 가지 말라는 소리다.

Mock을 신봉하는 테스터들은 '열차 사고', 즉 getThis().getThat().getTheOther() 형태의 메서드 연쇄를 피해야한다고 이야기한다. 메서드 연쇄의 기피는 Law of Demeter 의 준수로 알려져 있다. 메서드 연쇄는 나쁜 냄새를 풍기지만, 반대로 전달자 메서드들로 무거워 진 중간자 객체 또한 나쁜 냄새를 풍긴다.

Mockist testers do talk more about avoiding 'train wrecks' - method chains of style of getThis().getThat().getTheOther(). Avoiding method chains is also known as following the Law of Demeter. While method chains are a smell, the opposite problem of middle men objects bloated with forwarding methods is also a smell. (I've always felt I'd be more comfortable with the Law of Demeter if it were called the Suggestion of Demeter.)


3. BDD(Behavior Driven Development) 를 실행한다.

휴, 이건 BDD 에 대한 지식이 얕아서 그냥 다음 슬라이드로 넘겨버려야겠다.



Martin Folwer 는 BDD에 대해 다음과 같이 이야기한다.
An important offshoot of the mockist style is that of Behavior Driven Development (BDD). BDD was originally developed by my colleague Dan North as a technique to better help people learn Test Driven Development by focusing on how TDD operates as a design technique. This led to renaming tests as behaviors to better explore where TDD helps with thinking about what an object needs to do. BDD takes a mockist approach, but it expands on this, both with its naming styles, and with its desire to integrate analysis within its technique. I won't go into this more here, as the only relevance to this article is that BDD is another variation on TDD that tends to use mockist testing. I'll leave it to you to follow the link for more information



Mock Object 을 사용해 볼까?
일단은 기존의 "전통주의자"적 태도를 유지하면서 Test Double 작성 용도로 Mock Object를 살짝 살짝 사용해보는 것에서 출발하는 것이 좋을 것이다. 하지만 이 경우 위에서 언급한 Mock Object 를 활용할 경우의 설계적 잇점을 얻지 못할 것이다. 아마도 "전통주의자" 적인 입장이 아닌 온전한 "Mock 신봉자" 의 입장에서 적극적으로 Mock Object 를 활용하는 상황을 가정할 때 저러한 설계적 잇점을 얻을 수 있는 것이 아닐까. BDD 는 너무 멀리 나간 듯 하지만, Role Interface 와 Law Of Demeter 준수 측면에서 Mock Object 를 활용한 설계 개선은 상당히 매력적이다. 그러나 아직 "Mock 신봉자" 로 위치 전환하기엔 주저하게 되네. 손쉬운 Stub 대용품 생성 이상으로 활용하기엔 심리적인 장벽이 상당하다.

그리고 과연 "행위 기반" 검증이 과연 맞는 것인가? 아직 내 머릿속은 "상태 기반" 검증 수준을 넘어가지 못하고 있다. 개념적으로도 옳은 것인가? 계좌 이체에 대한 검증은 "누구 계좌에서 돈을 빼내고 누구 계좌에 돈을 넣고" 를 보는 것인가, "결과적으로 누구 계좌엔 얼마, 누구 계좌엔 얼마" 를 보는 것인가. 하아... 이미 "상태 기반" 검증이 머릿속에 굳건히 자리박고 있어서 쉽지 않을 것 가고, 기를 쓰고 "행위 기반" 검증을 머릿속에 박아넣을 경우 어떤 이익이 따르는 것인지도 모르겠다.

내가 내린 결론은 다음과 같다.1. 일단 TDD 먼저, 2. 전통주의자 + Mock , 3. 그 이후의 일은 나중에 고민. 헛, 너무 썰렁한 결론이 나 버렸네.

관련 글들

트랙백

이 글과 관련된 글 쓰기 (트랙백 보내기)
TrackbackURL : http://kingori.egloos.com/tb/4169398 [도움말]
  • TDD : Mock 에 대한 자세한 설명 2009/06/23 12:35 #

    Mock을 이용한 단위 테스트 TDD 분야에서 Mock에 대해 아주 자세하고, 실용적인 글입니다. 꼭 한번씩 보세요.... more

  • 작은아이의 생각 2009/06/29 10:54 #

    Mock을 이용한 단위 테스트 WoC 참가했을 때, 멘토님께 배우고 해봤는데.. 지금은 다 까먹었어 -_-... more

덧글

  • kenu 2009/06/23 12:10 # 삭제 답글

    TDD도 계파가 있다는 것을 처음 알게되었습니다.
    잘 읽었습니다. ^^ 장문의 글 대단하십니다.
  • 오리대마왕 2009/06/23 14:36 #

    휴 제가 봐도 정리가 안되는데 잘 읽으셨다니 다행이에요~ :)
  • 박성철 2009/06/23 14:33 # 답글

    역시 낮잠을 자고 읽으니 머리에 쏙쏙 들어오네요.
    잘 읽었습니다. ^^
    그런데 읽으면서 다른 분들이 오해할 만한 것이 두 가지 보입니다.

    전통적 TDD에서 Unit Test는 실상 흔히 말하는 격리된 Unit Test가 아니라 어느 정도 통합 테스트까지 확장하는 경향이 있습니다. 전통TDD주의자는 아마도 이런 느슨한 Unit Test를 하면서 TDD를 실천하는 사람일 겁니다. 반대로 Mock 신봉자로 칭하는 사람들은 완전히 격리하는 엄격한 Unit Test를 수행하는 사람이겠지요. 차라리 엄격한 unit test 신봉자로 부르는 것이 나을 것 같습니다.

    Mock 신봉자라고는 호칭이 때문에 또 다른 오해가 생길 수 있어 보이는데요.
    Mock 신봉자라고 하면 easymock이나 mockito 같은 도구를 사용하여 mock 객체를 만드는 사람으로 이해할 사람이 많다고 생각됩니다. 물론 이런 도구를 적당하게 사용하면 많이 편하지만 mock은 직접 작성할 수도 있습니다.
    저는 일반적으로 제가 작성한 코드의 test double이 필요할 경우에는 직접 stub나 mock을 만들고 외부에서 가져온 코드가 테스트에 필요할 때에는 mock tool을 사용하는 편입니다.
    이 글은 mock 도구를 사용하느냐 안하느냐로 읽으면 안되고 격리된 unit test를 할 것이냐 아니면 tddbe 책에서 말하는 느슨한 unit test 수준에서 멈출 것이냐 정도의 차이에서 격리된 unit test를 수행하면 얻을 수 있는 이점을 정리한 것으로 보입니다.
    그런데 Mockist라는 표현 때문에 Mock tool을 쓸 것이냐 말 것이냐로 읽혀질 수도 있어 보이네요. 하도 그렇게 생각하시는 분들을 많이만 나서... -_-);;
  • 오리대마왕 2009/06/23 14:52 #

    좋은 의견 고맙습니다. 3가지를 말씀하시는 것으로 정리가 되네요.

    1) 전통주의자: 말씀하시는 바가 맞다고 봅니다. 즉, 특정 layer에 집중하여 테스트 하지 않고, 하나의 기능 줄기를 전반적으로 테스트 하는 경향이 있다고 하네요. 이는 곧 "잘 게 쪼갠 통합 테스트"와 비슷한 모양이 되겠지요. 따라서 언급하신 "통합 테스트까지 확장하는 경향이 있다"는 언급이 맞습니다.

    2) Mock 신봉자: unit test 대상을 매우 한정하는 경향이 있기 때문에 (나머지는 다 mock 처리) "엄격한 unit test 주의자" 라고 말씀하신 것이 일견 맞겠지요. 그리고 Mock 활용 자체는 말씀하신 것 같이 동적 mock (라이브러리 사용하겠죠), 정적 mock (직접 만드는 모양이 해당하겠습니다) 등등 또다른 엄청 큰 세계가 펼쳐지기 때문에 tool 에 집착하는 사람들이라고 이해하면 안되겠지요. 딱 요점을 찝어주셨네요 그러나 "엄격한" 으로 한정지을 수는 없다고 봅니다. mock 이 stub 과 다른 부분은 원래의 real object 를 "확장" 할 수도 있다는 것과 함께 behavior verification 이 가능하다는 것입니다. 이 부분에서 stub을 빡시게 만들어서 "엄격하게 unit test를 실천하는 사람"과 mockist 가 다르게 구분지어 질 것 같네요. 과연 그게 무슨 의미가 있느냐 까지는 저도 모르겠습니다만, mock object 의 특징이 뭍혀지는 것 같아 "엄격하냐 아니냐" 가 전통주의자와 mock 신봉자를 구분짓는 유일한 기준은 아닌 것 같아요.

    3) "격리된 unit test를 수행하면 얻을 수 있는 이점을 정리한 것" 부분에는 약간 다른 의견입니다. "격리된 unit test" 에 초점을 맞추면 Mock 의 behavior based testing 부분이 무시되는 것 같은데, 저는 이 부분이 상당히 중요하다고 생각합니다. "격리된 unit test" 는 빡신 stub 생성으로도 가능하다고 보이거든요. 그런데 과연 이런 "확장" 과 "behavior verification" 이 저런 설계상의 이점을 끌어냈는가? ==> 요건 더 공부를 해야 겠어요. 아직 모르겠습니다. ㅎㅎㅎ

    제가 이 글을 쓴 동기는 전통적 TDD냐 Mock 신봉자냐 구분보다는 "과연 Mock 을 사용할 경우 설계상에 어떤 영향이 있을까~" 하는 마지막 부분인데요, 정리하다보니 지쳐서 정작 궁금한 부분에 대해선 슬쩍 넘긴 감이 있네요. 으하하하하하
  • 박성철 2009/06/23 15:46 # 답글

    저는 오히려 마지막 부분에서 많이 도움이 되었습니다.
    TDD가 좋은 설계로 이어지는 이유를 이렇게 정리해주시니 좀 산만하고 애매하게 알고 있던 것을 더 확실히 전할 수 있겠네요.

    그런데 굳이 이 글에서 Mock과 Stub를 구별할 필요가 있나 싶습니다. 물론 명확히 할 때는 이 둘을 구분하는 것이 맞지만 또 일반적으로 mock이라고 말할 때는 stub과 mock을 통칭하기 때문이지요. 전통 TDD 주의자와 Mock 주의자를 구별하는 문맥에서 Mock과 Stub를 구별할 필요는 없어보입니다.

  • 오리대마왕 2009/06/23 15:56 #

    제가 mock 에서 눈여겨 본 것은 1. behavior based verification , 2. stub 과 달리 기능 확장이 가능 하다는 2가지 입니다. 이 2가지 특징이 mock 기반 testing 이 stub 기반 testing 과 구별되는 특징이라고 생각합니다.

    제가 정리가 안되는 것은 "이 mock의 두가지 특징이 낳은 결과가 무엇인가"가 전체 문맥에서 잘 파악이 안된다는 것입니다. 두 가지 특징이 bdd 나 role-based interface, law-of-demeter 와 강하게 연결된다면 mock 과 stub 을 엄격히 구분해야 한다고 봅니다. 그렇지 않다면? 말씀하신 것과 같이 두 부류 구별없이 그냥 TDD 실천자 정도로 묶고 가도 무방하겠지요.

    따라서 성철님의 "구별할 필요가 없어보인다"는 말씀에 제 의견을 명확히 밝히질 못하겠네요. 공부를 더 해 봐야 하겠습니다. 그런데 MSDN 의 글이 제목에서 부터 명확히 "Mock Object 를 이용한 Role-based Object 설계" 인 것으로 보아서는 저런 특성이 어느 정도 녹아있다고 보여집니다.

    결론: 전 아직 잘 모르겠습니다. 공부 더 해봐야겠어요. (이런 무책임한!)
  • 박성철 2009/06/23 18:44 # 답글

    Mocks Aren't Stubs를 다시 읽어봤는데 확실히 제가 넘겨 짚었네요. 예전에도 읽었고 자주 참고하던 글인데 왜 이런 내용을 몰랐을까요. behavior based verification에 대해서 심각하게 생각하지 않았나 봅니다. 읽고 싶은 부분만 읽고... ㅠ.ㅠ

    그리고 role-based Object나 role interface 부분도 말씀하신 Mock의 behavior based verification과 연관이 확실히 있습니다. Mock을 쓰면 구현할 로직보다는 객체 간의 상호작용에 관심이 가기 때문에...

    demeter 법칙도 당연합니다. 흔히 저쪽에 무엇이 구현되어 있는지 알고 있기 때문에 demeter 법칙을 위반하곤 합니다. 그런데 role-based Object처럼 구현보다 객체 간의 상호작용에 신경 쓰면서 설계를 하다 보면 자연히 메세징으로 문제를 해결하려고 할 것이고 두 객체를 건너뛰는 demeter 법칙 위반은 많이 줄어들겠죠.

    좀 창피하네요. 알지도 못하면서 그동안 mock과 stub이 다르다고 가르치듯 말하고 다녔으니... 덕분에 또 하나 깨우쳤습니다.

핑백

덧글

댓글 입력 영역