C언어에서 Mock Object 효과 내기

314 views
Skip to first unread message

June Kim

unread,
Dec 14, 2008, 12:08:50 AM12/14/08
to xp...@googlegroups.com
몇 년 전부터 c언어에서 mock object 효과 내기로 고민을 좀 했습니다. 예를 들면 아래에서 "run-time +
compile-time" 같은 것이 제가 만든 해법 중 하나이고요(만든지는 4년 정도 된 듯).

xper 위키 같은 곳에 정리해서 올릴 예정인데, 일단 의견 모으는 차원에서 간략히 여기 정리해 씁니다.

== run-time polymorphism ==
* 함수 포인터 사용
- cgreen 스타일 http://www.lastcraft.com/mock_callbacks.php
- naive object struct 사용(함수의 첫번째 인자 self를 struct로 해서 오브젝트를 흉내냄) : Io
언어 인터프리터 구현이 이런 식으로 잘 되어 있음

== compile-time polymorphism ==
* DEFINE/IFDEF 등을 사용해서 컴파일 옵션에 따라 production code와 mock code의 다른 버전이 만들어지게 함

== link-time polymorphism ==
* 오브젝트 파일을 production용, mock용으로 짝으로 만듦. make all, make test 등으로 해서
빌드할 때 서로 다른 오브젝트 파일이 링크되도록.
* 주의점: link-time 경우, 어떤 테스트에서는 이 함수를 mock으로, 어떤 테스트에서는 이 함수를 real로
선택적으로 하는 것이 불가능하다. (예를 들어, unit-level test와 integration/system-level
test가 섞여 있는 경우). 이 때 방법은 그것들을 분리해서 서로 다른 실행파일로 만들고 각 실행파일을 실행시키고 그
결과들을 aggregate/parsing해서 report해야 한다.

== hybrid ==
=== run-time + compile-time ===
* function copying/modifying : 테스트 코드 중에서 커멘트 속에 일정한 DSL(python의
EDSL)로 mock하고 싶은 함수를 기술하면 자동으로 production 코드에서 해당 함수를 복사해 오되, 그 중 내가
원하는 함수 호출에 대해서는 인자값에서 함수 포인터를 받도록 함.
예를 들어, write_multiple_char('=',80)하면 화면에 =가 80가 찍혀야 한다면, wmc는
write_char('=')를 80번 호출해야 한다. wmc를 테스트하고 싶다면 다음과 같이 한다.

// f=TargetFunction("blah.c","write_multiple_char") <-- blah.c는 wmc가 정의되어 있는 파일
// f.addMockCall("write_char")
// f.copy() <-- code generation을 거치면 이 자리에
_MOCK_write_multiple_char라는 새로운 함수 정의가 들어온다

void mock_write_char(char c)
{
mock_write_char_called++;
}

void test_wmc_calls_wc()
{
_MOCK_write_multiple_char('=',80, mock_write_char);
assert_equal(80,mock_write_char_called);
}

=== compile-time + link-time ===
* CMock 스타일 (http://cmock.sf.net) : 자동으로 expect 함수와 가짜 함수들을 만들어 주고, 링크할 때 바꿔치기

예를 들어 위 wmc 경우, write_char가 선언된 헤더 파일이 있다면, 자동으로 그 헤더 파일을 읽어서 다음 함수들을 생성한다.

write_char
write_char_Expect

이 때 테스트는 다음과 같다.

void test_wmc_calls_wc()
{
int i;
for(i=0;i++;i<80)
write_char_Expect('=');
write_multiple_char('=',80);
}

Jooyung Han

unread,
Dec 14, 2008, 7:56:53 PM12/14/08
to xp...@googlegroups.com
CMock 방법은 매력적이긴 하지만, 빌드과정에 추가적인 스텝이 추가되어 조금 꺼려지네요.

반면 Cgreen의 mock implementation 방법은 굉장히 효용가치가 높다고 보입니다. 어떻게든 seam을 만들어
mock을 넣을수는 있지만 매번 record-play 를 구현하기가 귀찮은 상황이었는데, Cgreen을 이용하면 간단히 해결될
것 같습니다.

레거시 코드의 경우, 쉽게 seam을 만드는 방법으로 function to function pointer 리팩터링이
효과적입니다. http://www.odd-e.com/blog/2007/12/function-to-function-pointer-r.html


2008/12/14 June Kim <june...@gmail.com>:

--
Jooyung Han

June Kim

unread,
Dec 14, 2008, 9:25:58 PM12/14/08
to xp...@googlegroups.com
function pointer를 쓰면(DI를 써서 호출하려는 함수에게 fp를 argument로 매번 전달하건, 혹은
global로 둬서 전체 바꿔치기 하건 -- bas vodde의 예) 싫어하는 개발자들이 많더군요.

1. 가독성이 떨어진다. (이건 global로 두고 마치 함수 쓰듯 하는 걸 쓰면 된다고 하면 해결 가능)
2. 속도 문제 (보통 mock하는 대상이 low-level이기 쉬운데, 이런 것들은 performance-critical한 경우가 있음.)
3. 왠지 싫다

2번이나 3번을 해결하려면 결국은 compile-time 혹은 link-time polymorphism을 섞어쓰게 되더군요.

2008/12/15 Jooyung Han <jooyu...@gmail.com>:

강석천

unread,
Dec 15, 2008, 9:16:53 PM12/15/08
to xper
link time polymorphism 을 선호하는 경우들을 생각해보면

* 기존 코드가 'interface' 의 개념이 없던 경우 - public interface 에 대한 개념을 고민하고 추후 바
꿔 쓴다는 개념이 없이 모노리틱한 디자인인 경우

* interface 추출조차 힘든 가운데 최대한 legacy code 를 덜 건드리고 테스트를 하고 싶은 경우 - 이게 좀
큰 것 같습니다.

* 주변에의 C/C++ 시스템 프로그래밍 개발자들의 경우 OO 디자인 고민하는 것보다 이게 더 친숙한 듯 합니다. 물론
'link time polymorphism' 이라는 용어는 사용하지 않습니다. ;)


On 12월15일, 오전11시25분, "June Kim" <junea...@gmail.com> wrote:
> function pointer를 쓰면(DI를 써서 호출하려는 함수에게 fp를 argument로 매번 전달하건, 혹은
> global로 둬서 전체 바꿔치기 하건 -- bas vodde의 예) 싫어하는 개발자들이 많더군요.
>
> 1. 가독성이 떨어진다. (이건 global로 두고 마치 함수 쓰듯 하는 걸 쓰면 된다고 하면 해결 가능)
> 2. 속도 문제 (보통 mock하는 대상이 low-level이기 쉬운데, 이런 것들은 performance-critical한 경우가 있음.)
> 3. 왠지 싫다
>
> 2번이나 3번을 해결하려면 결국은 compile-time 혹은 link-time polymorphism을 섞어쓰게 되더군요.
>

> 2008/12/15 Jooyung Han <jooyung....@gmail.com>:


>
> > CMock 방법은 매력적이긴 하지만, 빌드과정에 추가적인 스텝이 추가되어 조금 꺼려지네요.
>
> > 반면 Cgreen의 mock implementation 방법은 굉장히 효용가치가 높다고 보입니다. 어떻게든 seam을 만들어
> > mock을 넣을수는 있지만 매번 record-play 를 구현하기가 귀찮은 상황이었는데, Cgreen을 이용하면 간단히 해결될
> > 것 같습니다.
>
> > 레거시 코드의 경우, 쉽게 seam을 만드는 방법으로 function to function pointer 리팩터링이
> > 효과적입니다.http://www.odd-e.com/blog/2007/12/function-to-function-pointer-r.html
>

> > 2008/12/14 June Kim <junea...@gmail.com>:


> >> 몇 년 전부터 c언어에서 mock object 효과 내기로 고민을 좀 했습니다. 예를 들면 아래에서 "run-time +
> >> compile-time" 같은 것이 제가 만든 해법 중 하나이고요(만든지는 4년 정도 된 듯).
>
> >> xper 위키 같은 곳에 정리해서 올릴 예정인데, 일단 의견 모으는 차원에서 간략히 여기 정리해 씁니다.
>
> >> == run-time polymorphism ==
> >> * 함수 포인터 사용

> >> - cgreen 스타일http://www.lastcraft.com/mock_callbacks.php

Jooyung Han

unread,
Dec 18, 2008, 3:14:43 AM12/18/08
to xp...@googlegroups.com
김창준 님이 글을 올려주신 것을 계기로 저희 팀 위키에 간단히 정리해본 내용입니다.

--- Mock in C --
보통은 Mock 객체라고 많이 부르지만, C에서는 Mock 함수라고 부를 수 있을 것이다.

Mock은 조금 더 똑똑한 Test double 이다..

Test double이 등장할 수 있는 곳은 다음과 같으므로, mock 함수가 등장할 수 있는 곳도 마찬가지다.

* Link-time substitution
* Run-time substitution
** function pointers
** virtual table

어떻게 똑똑할까?

* record and play 방식을 지원한다.
** record: 이렇게 호출될 것이다. 이런 값을 return할 것이다... 라는 것을 setup 단계에 지정해두고,
(이것들을 expectation이라 부른다.)
** play: exercise 단계에서 Function under test 를 호출했을때, 위와 같이 동작해준다.
* self-verification 을 수행한다.
** record 한 것과 다르게 사용될 경우, fail로 간주한다.
** 예를 들어, 호출될 것으로 예상했는데 불리지 않는다거나, 지정된 값과 다른 값으로 호출한다거나 하는 경우이다.

이만큼 똑똑한 녀석이 필요없을 때는, 보통 다음과 같이 간단하게 구현할 수 있다.

* call count 를 기록한다.
* call sequence 를 록한다. (string append)
* last call param을 저장한다.
* ...

Mock의 동작을 직접 구현하지 않도록 지원하는 라이브러리가 있다.

* CMock - header 파일로부터 해당 interface api 들에 대한 mock 함수 및 expect 함수를 자동
생성하여 Link time polymorphism을 사용할 수 있도록 도와준다. Make를 잘 구성해야 한다.
* Cgreen - 함수명과 파라미터 명을 기준으로 expectation을 쉽게 설정할 수 있도록 구현되어 있다.

위 각각의 경우, 직접 mock의 record and play 기능을 구현하지 않고도, 똑똑한 mock을 쉽게 얻을 수 있다.

단점은,

* CMock - ruby 스크립트를 사용하고, link-time 에 특화되어 있다. Make구성이 어렵다.
* Cgreen - 현재까지 intptr_t 호환 타입만 지원하고 있다.

그러나 이들로부터 다음의 장점을 취할 수 있다.

* CMock - 스크립트를 이용한 코드 자동생성을 빌드시에 하지 않고, CppUTest의 script처럼 사용한다면 효과적일 것이다.
* Cgreen - 현재 double type은 boxing/unboxing 형태로 지원하고 있는데, 이 방법을 이용하면
사용자 정의타입으로까지 확장이 가능하다. CppUTest 의 plug-in으로 추가하면 CppUTest에서도 쉽게 사용할 수
있을 것이다.


2008/12/16 강석천 <free...@gmail.com>:

--
Jooyung Han

Reply all
Reply to author
Forward
0 new messages