IT_Programming/Dev Libs & Framework

jMock vs. EasyMock

JJun ™ 2011. 4. 3. 13:34

-----------------------------------------------------------------------------------------------

출처: http://kwon37xi.egloos.com/4126439

-----------------------------------------------------------------------------------------------





jMockEasyMock은 Java 단위 테스트계의 양대 모의 객체 프레임워크이다.

jMock과 EasyMock 외에도 요즘 Mockito라는 모의 객체 프레임워크도 있다.

 

나는 원래 EasyMock을 사용했었으며, Spring등 많은 자바 프로젝트들이 EasyMock을 사용하고 있다. Spring이 EasyMock을 사용하는 판국에 내가 다른 것을 선택할 이유가 없었다. 하지만 요즘 들어 과거에

문자열로 호출할 메소드를 지정해야 했던 jMock이 진짜 메소드 호출 기반으로 바뀌면서 EasyMock을

맹렬히 추격하고 있다. 그래서 이 둘을 비교해보고 싶어졌다.

 

테스트 프레임워크 의존성

jMock은 JUnit3,JUnit4에 대해 상속이나 @RunWith() 어노테이션으로 강결합을 하게 하여 TestNG 등으로 테스트 프레임워크를 바꾸기 어려워 질 수 도있다. 하지만 테스트 프레임워크의 기능을 사용하게 되면

그만큼 코드가 간결해지며, EasyMock 처럼 테스트 프레임워크에 의존적이지 않는 코드로 작성할 수 있는 방법도 있다. 그 경우에는 코드가 더 길어진다.

  1. @RunWith(JMock.class)
    class PublisherTest {
        Mockery context = new JUnit4Mockery();
        //...   
    }

 

 

EasyMock은 테스트 프레임워크에 독립적인 방식만을 지원한다.

테스트 프레임워크와는 전혀 무관하게 작동한다. 하지만 코드가 길어진다.

  1. import static org.easymock.EasyMock.*;
    class PublisherTest {
  2.     private IMocksControl mockControl
  3.     @Before
  4.     public void setUp() {
  5.         mockControl = createControl();
  6.         // ....
  7.     }
  8. }

 

jMock이 두가지 방식을 지원하는데 반해 EasyMock은 한가지 방식만 지원하므로, jMock > EasyMock

 

모의 객체의 행동 검증

jMock은 @RunWith(JMock.class)로 지정했을 경우 테스트 메소드가 종료되면 검증을 자동으로 해준다. 물론 테스트 프레임워크에 비의존적으로 짰을 경우에는 명시적으로 검증하는 코드를 넣어야 한다.

 

EasyMock은 mockControl.replay(), mockControl.verify()를 명시적으로 해줘야 한다. 이걸 가끔 잊어서 테스트를 통과하면 안되는데 통과해버리는 경우를 겪을 수 있다. 물론 이 경우에는 테스트 프레임워크에 의존성이 없다는 장점이 있다.

 

마찬가지로 두 가지 방식을 모두 지원하는, jMock > EasyMock

 

모의 객체 생성

jMock은 항상 하나의 모의 객체건 여러개의 모의 객체를 테스트 할 때건 동일한 방식으로 모의 객체를 생성하고 사용한다.

 

EasyMock은 딱 하나의 모의 객체를 테스트 할 때와 여러 모의 객체를 테스트할 때의 모의 객체 생성방식과 사용법이 다르다. 물론 하나만 할 때가 한 줄 더 적다. 하지만 만일 코드를 여러줄을 테스트하도록 바꿀 경우에 고쳐야할 코드가 많아서 문제를 일으킨다. 차라리 항상 여러 모의 객체를 테스트 한다고 가정하는게 유지 보수성에 더 좋다.

 

코드 자체는 별 차이가 없다. jMock == EasyMock

 

수행 기대 코드(expectation code)

jMock은  수행 기대 코드가 좀 길고 장황하다. 다음과 같은 형태로, Expectations라는 클래스의 익명 클래스를 생성하면서 익명 클래스의 생성자에서 기대하는 행위를 적어준다. 하지만 JUnit 4.4 부터 지원되는 hamcrest 문법을 사용할 수 있어서 가독성이 높다. 또 하나 좋은 점은, EasyMock과는 달리 모의 객체 행동의 기대 코드와 실제 테스트하려는 코드가 뚜렷이 분리되어 가독성을 높인다는 점이다.

  1. final String filename = "test.hwp";
    context.checking(new Expectations() {
        {
            oneOf(mockServletContext).getMimeType(filename.toLowerCase());
            will(returnValue(null));
        }
    });
    // 테스트할 코드 수행.

 

jMock의 경우 기대 코드를 Expectations 의 익명클래스에서 사용한다는 점 때문에, 그 안에서 사용하는 변수를 인스턴스 필드로 하거나 로컬 변수의 경우 final 이어야 한다는 제약이 있다.

 

참고 : new Expectations() {{ oneOf (...); will(returnValue(null);) }}  이 코드는 Expectations의 익명 클래스를 생성하는 것이다. 여기서 안쪽 대괄호 부분은 익명 클래스에서 생성자의 역할을 한다. 생성자에서는 인스턴스 메소드를 자유롭게 호출할 수 있다. 바로 oneOf, will, returnValue 등의 메소드는 Expectations 클래스의 인스턴스 메소드인 것이다.

 

EasyMock은 수행 기대 코드가 간결하다. 하지만 JUnit 4의 hamcrest와 비슷하지만 약간 다른 방식으로 기대값을 기술하기 때문에, JUnit 4의 hamcrest 기능을 사용하는 개발자들은 혼란스러움을 느끼게 된다. 그래서 나는 EasyMock을 사용할 때는 hamcrest 기능을 사용하지 않았다.

그리고 기대 코드가 expect() 라는 구문과 mockControl.replay()를 사이에 두고 구분이 되긴 하지만 jMock에서 처럼 뚜렷이 구분되지는 않는다.

 

  1. expect(mockServletContext.getMimeType(filename.toLowerCase()))
                    .andReturn(null);
  2. mockControl.replay();
  3. // 테스트할 코드 수행...
  4. mockControl.verify();

 

결과는 상당히 주관적이게도, 좀 장황하더라도 hamcrest 문법을 (헷갈리지 않고) 쓸 수 있고 hamcrest를 사용해 확장이 가능한 jMock의 손을 살짝 들어주고 싶다.

jMock >= EasyMock

 

Concrete Class 모의 객체 생성

jMock은 코드 한줄을 추가함으로써 Interface가 아닌 Class에 대한 모의 객체 생성이 가능하다.

  1. private Mockery context = new JUnit4Mockery() {{
        setImposteriser(ClassImposteriser.INSTANCE);
    }};

 

EasyMock은 클래스와 메소드 이름은 모두 같지만 패키지만 다른 클래스를 사용한다. 오히려 혼란스럽다.

  1. import static org.easymock.classextension.EasyMock.*;
    import org.easymock.classextension.IMocksControl;

 

당연히 더욱 간편한 jMock > EasyMock

 

테스트 메소드 내에서 생성된 객체를 모의 객체의 메소드에 인자로 넘길때의 검증

테스트 대상 클래스의 메소드가 메소드 인자들을 조합하여 어떤 객체를 생성하고, 그 값 객체를 모의 객체의 메소드에 전달 할 경우에 중간에 생성된 객체의 값을 어떻게 검증할까?

 

jMock은 이런 상황에 (현재는) 대처할 수 없다. hamcrest의 TypeSafeMatcher을 상속하여 직접 기능을 구현해야 한다. 하지만 그리 어려울 것 같지는 않다.

 

EasyMock이 이에 대비하여 Capture 라는 기능이 마련되어 있다. 모의 객체의 메소드에 넘기는 파라미터 객체를 가로채어 밖으로 끄집어내어 assert 구문을 적용하는 것이 가능하다.

이에 관해서는 http://www.func.nl/community/knowledgebase/easymock-24-capturing-arguments-passed-mock-objects 를 참조한다.

 

실제로 이런 경우는 거의 발생하지 않는다. 그리고 이런 경우가 발생하는 코드가 좋은 것인지는 사실 나도 잘 모르겠다.

 

jMock < EasyMock

 

모의 객체 메소드의 행위 지정

모의 객체 메소드가 특별한 행동을 하도록 코딩하고 싶을 경우가 있다.

jMock은 Action 으로, EasyMock은 Answer로 그 기능을 구현할 수 있다.

 

결론

결론은.. jMock과 EasyMock만 비교하면, jMock에 더 많은 점수를 줄 수 밖에 없다.

하지만..... 너무 당황스럽게도.. 난 Mockito가 제일 좋다. 

이 조사를 하는 과정에서 Mockito에 대해서도 더욱 조사를 했는데... 이런.. Mockito에 반해버렸다!!

나름 반전드라마인가?

 

 

 

더보기

왜 Mockito가 좋은가?

예전에 jMock vs. EasyMock 이라는 포스팅에서 결론은 Mockito라고 내버리면서 왜 그런지 설명을 좀 하겠다고 했었는데, 이제야 그에 관해 글을 쓴다.

이 글은 Mockito 제작자의 is there a defference between asking and telling?라는 글에 대한 정리이며, expect-run-verify Goodbye! 라는 글과, MSDN의 Test Double의 연속성 살펴보기, 그리고 근본 개념을 다루고 있는 Martin Fowler의 Mocks Aren't Stubs 라는 글을 읽어보면 더 많은 도움이 될 것이다.

 

이 글을 읽기전에 Test Double의 연속성 살펴보기를 읽고 최소한 stub이 뭔지는 알고 있어야 한다.

 

테스트 대상 코드

  1. public void deleteByHeadline(String headline) {
        // asking/묻기
        Article article = repository.findArticle(headline); // 헤드라인 문자열에 따라 Article 객체 검색
        // telling/시키기
        repository.delete(article); // 해당 객체 삭제를 명령함
    }

위와 같은 코드가 있고, 이를 단위 테스트로 테스트를 한다고 하자. 만약 모의 객체 프레임워크가 없다면 우리는 ReporitoryStub 이라는 것을 직접 만들어서 다음과 비슷하게 할 것이다.

 

  1. Article article = new Article();
    repositoryStub.setArticleToFind(article); // 스텁에서 리턴해줄 객체를 설정한다.

    articleManager.deleteByHeadline("foo"); // 테스트 대상 코드를 실행한다.

    repositoryStub.verifyArticleDeleted(article); // 테스트 대상 코드가 삭제코드를 호출했는지 여부를 확인하다.

 

이와 같은 패턴의 테스트를

  1. stub
  2. run
  3. assert

라 한다.

 

정리하자면, 1. 객체에게 어떠한 값을 요청하는 행위는 스텁이며, 테스트 대상 코드를  호출하기 전에 미리 어떤 값을 리턴할지를 지정해주는 행위(stubbing)을 해줘야 한다.

여기서는 repositoryStub.setArticleToFind(article); 이 코드에 해당한다.

그리고 2. 테스트 대상 코드를 실행하고, 3. repositoryStub 객체에게 제대로 삭제를 시켰는지(telling)를 검증해야 한다. 여기서 repositoryStub.verifyArticleDeleted(article); 가 이에 해당한다.

 

즉, 다시 반복해서 말하면, 테스트 대상 코드에서 묻기(asking) 부분은 stub의 역할을 한다. 그리고 시키기(telling) 부분은 제대로 호출했는지 검증해야할 부분이 된다.

보다시피 stub은 검증의 대상이 아니다. 우리가 명확히 검증해야 할것은 올바른 객체를 삭제하는 코드에 넘겨주는 호출을 제대로 했는지 여부이다.

 

Expect-Run-Verify

이제 Mockto 제작자의 expect-run-verify 라는 글을 읽어보자. 이 글에 따르면 기존의 모의 객체 프레임워크는 stub-run-assert가 아니라, expect-run-verify 형태이다.

해보면 알겠지만, 이는 자연스러운 TDD의 흐름에 다소 어긋난다.

간단히 EasyMock 모의 객체 프레임워크로 위 테스트 코드를 작성해 보도록 하자. 모의 객체 생성은 이미 됐다고 가정한다(jMock도 기븐 틀은 다를바 없다).

  1. // expect
  2. Article article = new Article();
  3. expect(mockRepository.findArticle("foo")).andReturn(article);
  4. mockRepository.delete(article);

    // run
  5. replay(mockRepository);
  6. articleManager.deleteByHeadline("foo");
  7. // verify
  8. verify(mockRepository);

 

여기서 보면 검증해야 할 코드인 mockRepository.delete(article)이 테스트 실행 부분보다 위에 있음을 알 수 있다. TDD를 해보면 알겠지만, 이는 사고의 흐름에 역행한다.

 

우리는 테스트 코드를 작성할 때 테스트를 실행하는 코드를 작성하고, 그 뒤에 테스트 실행 코드가 실행된 뒤에 어떤 상태가 되어야 하는지를 검증(assert)한다.

그런데, 여기서는 검증에서 통과해야할 코드를 먼저 작성하고 그 뒤에 테스트 대상 코드를 실행한 것이다.

 

또 다른 문제도 있는데, 위 코드에서는 mockRepository.findArticle("foo"); 도 검증 대상이라는 점이다. 이 코드는 분명 스텁인데도 불구하고 verify(mockRepository)가 호출되는 순간 검증 대상에 포함돼 버린다.

 

Mockito는?

Mockito는 가장 자연스러은 흐름에 따라 테스트 코드를 작성할 수 있게 해준다.

  1. Article article = new Article();
  2. // stub
  3. when(mockRepository.findArticle("foo")).thenReturn(article);
  4. // run
  5. articleManager.deleteByHeadline("foo");
  6. // assert
  7. verify(mockRepository).delete(article);

보면 정확히 스텁을 수작업으로 만든 맨 처음 테스트 코드와 형태가 동일함을 알 수 있다.

게다가 코드도 훨씬 간단하다.

 

직접 해보면 TDD시에 사고의 흐름과 테스트 코드의 흐름이 자연스럽게 일치됨을 느끼게 될 것이다.

 

논쟁?

논쟁거리가 있는데, 바로 "스텁은 정말 검증하지 않아도 되는 것인가?"라는 점이다.

실제로 해보면 그럴 필요 없다. 스텁이 제대로 호출 되지 않으면 단위 테스트의 나머지 검증 코드가 제대로 실행 되지 않게 되기 때문이다.

 

결론

Mockito는 모의 객체가 스텁인지 검증 대상이 되는 모의 객체인지를 구분하고, 사고의 흐름에 역행하지 않는다.

게다가 실제 작성해야할 코드의 양도 다른 모의 객체 프레임워크에 비해 훨씬 적다.

클래스와 인터페이스에 대한 모의 객체 생성 문법이 동일하여 구분할 필요가 없고(오히려 이걸 싫어하는 사람도 있다. 개발자들이 인터페이스를 안 만드는 잘못된 버릇을 들일까봐), hamcrest Matcher도 사용할 수 있다.

 

마틴 파울러의 쏘트웍스사의 개발자들도 Mockito를 사용한다고 한다.(누군가가 바로 이래서 Mockito가 검증된거 아니냐는 글을 쓴 걸 본적도 있다).

 

예전에 썼던 jMock vs. EasyMock 글에서 처럼 기능 단위로 다른 프레임워크와 비교해야할 필요성은 못 느끼고 있다. 그냥 다른 애들 되는 것은 거의 다  된다고 보면 된다.

 

내게는 Mockito가 현재 가장 훌륭한 Java 모의 객체 프레임워크이다. Mockito의 사용법은 http://mockito.googlecode.com/svn/branches/1.7/javadoc/org/mockito/Mockito.html 를 참조하면 된다.