Why OSAF 1. 테스트 코드를 익힐 수 있습니다.

OSAF를 공개한지 한 달이 아직 안 됐습니다. 10월 23일에 공개했었죠. 지금까지 약 200에 가까운 다운로드를 기록하고 있지만, 전혀… 아무런… 반응이 없다는 것에는 가히 놀라울 뿐입니다. 그냥 제가 쓴 글의 댓글 몇 개 정도 뿐의 관심이 저에게 한 편으로는 아쉬움으로 또 다른 한 편으로는 오기로 다가옵니다.

완전 최첨단 프레임워크인 OSAF에 왜 이렇게 관심이 없을까. 고민을 많이 했습니다. 어렵나? 메이븐 떄문인가? 그거 없어도 되는데. 문서가 부족하긴 부족하고. 그래도 어떻게 이렇게 조용할 수가 있지. 홈피 디자인이 좀 구리긴 한데.. 그거 때문인가? ㅋ. OSAF 발음이 너무 어려운가? 별에 별 생각을 많이 했습니다. 당연히 기운도 빠집니다. OSAF를 공개한 건 어쩌면 OSAF에게 못씁짓을 한 건 아닌지 말이죠.(Max님의 ‘어디가서 밥은 먹고 다녀야 할텐데..’ 라는 댓글이 생각납니다.)

긍정적으로 생각하기로 했습니다. 언젠가는 빛을 보겠지. 열심히 계속 가꾸다 보면 언젠간 알아주겠지. 하고 말이죠. 그래서 OSAF가 여러분에게 어떤 도움을 줄 수 있을지 생각하고 알려드리기로 했습니다. 그 중 첫 번째가 바로 테스트 코드입니다.

OSAF의 테스트 커버리지는 60%가 조금 넘습니다. (앞으로 차차 올릴 예정입니다.) 60%의 테스트 커버리지는 전부 OSAF 개발팀에서 직접 작성한 테스트 코드입니다. 어딘가에서 배껴온 코드가 절대로 아닙니다. 테스트는 초기에 JUnit과 EasyMock을 사용해서 작성 했었습니다. 물론 스프링 테스트 기능도 사용하고 있죠. 배포 직전에는 EasyMock을 Mockito로 교체하여 비슷한 테스트를 보다 깔끔하고 직관적이며 적은 수의 코드로 대체할 수 있었습니다. DBUnit을 확장하여 OSAF가 제공하는 테스트 케이스를 이용하면 DAO 테스트가 매우 간편해질 것 입니다.

이렇게 좋은데… 한 번 들여다 보고 뭐라고 해주시지 않으시겠어요? 좋다. 잘했다. 고맙다. 이런거 말구요. 이 부분의 테스트는 이해가 안 된다. 테스트가 조금 이상하다. 이 부분의 테스트는 왜 안했냐. 어려워서 그런거냐? 이 부분의 테스트는 이렇게 고치는게 좋치 않겠냐? 이런.. 반응이 제가 가장 좋아하는 반응이자 OSAF에게 거름을 주는 방법입니다.

소스 코드는 굳이 다운 받지 않아도(장기적으론 받아 두시면 좋겠지만..)

http://www.opensprout.org:9060/browse/OSAF/osaf/trunk

위 링크로 가시면 웹에서 직접 볼 수 있습니다. 소스 코드나 OSAF 와 관련하여 문제나 제안하고 싶은 것이 있다면 주저하지 마시고 이슈를 등록해 주세요.

http://www.opensprout.org/jira/secure/Dashboard.jspa

expect -> run -> verify 스타일(ex. Easymock) 바이바이

참조 : http://monkeyisland.pl/2008/02/01/deathwish/

이지목 스타일은 녹화 -> 플레이 -> 확인(expect -> run -> verify) 순으로 mocking 또는 stubbing 하는 거였습니다. 그러나 이 스타일은 다음과 같은 단점들이 있습니다.

1. 테스트 메소드가 지져분해짐.
– 이것 저것 예측/녹화를 해줘야 하는데 그게 테스트를 위해서가 아니라 Mock을 위해서 해줘야 한다는게 좀..

2. 자연스러운 테스트 스타일로 느껴지지 않는다.
– 예측을 한 담에 실행하는게 아니라, 실행 한 다음에 예측되는 Mock의 행위를 나열해 주는게 더 자연스럽다.

3. 테스트가 깨지기 쉽다.
– 새로운 기능을 추가하면, Mock을 사용한 테스트가 왕창 깨지는 경우가 발생한다.

4. 보다 자세한 실패 메시지를 보여줄 수 있었을 텐데…

5. 보다 가독성 좋게 만들 수 있었을 텐데…

그래서 상태 기반 테스트를 제공하는 Mockito를 강추 한다는거…

Mockito 홈에서 다음을 인용합니다.

No expect-run-verify also means that Mockito mocks are often ready without expensive setup upfront. They aim to be transparent and let the developer to focus on testing selected behavior rather than absorb attention.

Mockito has very slim API, almost no time is needed to start mocking. There is only one kind of mock, there is only one way of creating mocks. Just remember that stubbing goes before execution, verifications of interactions go afterwards. You’ll soon notice how natural is that kind of mocking when test-driving java code.

즉 stubbing -> execution -> verification 라고 할 수 있겠네요. 훔.. 그래도 왠지 expect -> run -> verify 형태와 비슷해 보이네요.

사용법은 여기에 잘 나와있습니다.

맥 애플리케이션 토렌트와 P2P 프로그램

http://www.mac-torrents.com/index.php

사용자 삽입 이미지
위 사이트에서 맥 애플리케이션과 관련된 토렌트를 구할 수 있습니다.

http://www.limewire.com/download/

저기서 받은 토렌트를 위의 limewire라는 무료 P2P(윈도우의 당나귀급) 프로그램에 드래그 앤 드랍을 하면 알아서 다운로드 해줍니다.

사용자 삽입 이미지위 두 개의 프로그램 조합으로 이제 원 없이 맥을 즐길 수 있게 되었습니다. 나이수~

EasyMock 사용할 때 주의 할 것

    public void foo(Bar bar) {
        …
        
        bar.toby(whiteship);
        bar.whiteship(toby);
    }

위와 같은 메소드를 테스트 할 때 EasyMock을 사용해서 다음과 같은 테스트를 작성할 수 있습니다.

@Test
public void foo(){
   Bar mockBar = createMock(Bar.class);
   …
   mockBar.toby(whiteship);
   mockBar.whiteship(toby);
   replay(mockBar);
   a.foo(mockBar);
   verify(mockBar);
}

테스트가 통과할 것만 같은 코드입니다. 그렇쵸? 대부분은 테스트가 통과 합니다. 그런데 통과하지 않는 경우도 있습니다.

java.lang.IllegalStateException: missing behavior definition for the preceeding method call toby(whiteship);

이런 메시지와 함께 테스트가 통과하지 않습니다.

그럴 때는 뭘 확인해 봐야 할까요? Bar 인터페이스에 있는 whiteship과 foo라는 메소드의 리턴타입이 있는지 확인해봐야 합니다. 리턴타입이 있으면 http://whiteship.tistory.com/1504

EasyMock – Using Stub Behavior for Methods

EasyMock으로 생성한 Mock을 마치 Stub처럼 사용할 수 있습니다. 이 말이 무슨 말이냐면… Mock은 Stub과 달리 예측이라는 개념이 추가되어 있습니다. 따라서 예측 대로 Mock이 수행되지 않으면 AssertionError가 발생하는데, Stub은 그런 개념이 없고, 따라서 구현해둔 메소드가 호출 되든 말든 상관이 없습니다. 다시 돌아가서 Stub 처럼 사용한 다는 것은 Mock으로 레코딩 한 부분이 호출되지 않아도 테스트가 통과 되도록 작성하는 것입니다.

언제 이런 걸 사용할까요. 몇 번이나 호출될지 예상할 수 없을 때 사용할 수 있을 것 같습니다. 아니면 기본적인 규칙이 있다면, 굳이 매번 설정할 필요 없이 EasyMock 문서에 나온대로 사용할 수도 있겠습니다.

expect(mock.voteForRemoval(“Document”)).andReturn(42);
expect(mock.voteForRemoval(not(eq(“Document”)))).andStubReturn(-1);

인자가 “Document”일 때는 42를 반환하고, “Document”라는 인자가 아닐 때는 -1을 반환하도록 레코딩 되어 있습니다.

voteForRemoval이라는 메소드는 최소 한 번은 “Document”라는 인자를 받아서 42를 반환하도록 되어 있으며 그 뒤로는 몇 번 호출될지도 모르지만, “Document”라는 인자가 오지는 않을 것이며, 만약 이런 메소드가 호출되면, -1을 반환하도록 레코딩 한 것입니다.

    @Test
    public void arguments() throws Exception {
        expect(memberDao.bar(1)).andReturn(1);
        expect(memberDao.bar(not(eq(1)))).andStubReturn(2);
       
        replay(memberDao);
        int i1 = memberServiceImpl.bar(1);
//        int i2 = memberServiceImpl.bar(2);
//        int i3 = memberServiceImpl.bar(2);

        assertEquals(1, i1);
//        assertEquals(2, i2);
//        assertEquals(2, i3);
        verify(memberDao);
    }

따라서 위의 코드에서 주석을 풀어도 되고, 안 풀어도 테스트는 통과합니다.
andStubReturn(), andStubThrow(), andStubAnswer() 를 사용할 수 있습니다.