3. XML 파싱 단위 테스트를 위한 Mock 생성때 큰웃음(?)을 선사하는 DOMElement
스프링도 사용하는 dom4j에 들어있음.. Thanx

    /**
     * Test do parse element bean definition builder.
     */
    public void testDoParseElementBeanDefinitionBuilder() {
       
        MockControl control = MockClassControl.createControl(BeanDefinitionBuilder.class);
        BeanDefinitionBuilder builder = (BeanDefinitionBuilder) control.getMock();
        MockElement mockElement = new MockElement("service");
       
        control.expectAndDefaultReturn(builder.addConstructorArgValue("value of id"), builder);
        control.expectAndDefaultReturn(builder.addConstructorArgValue("value of name"), builder);
        control.expectAndDefaultReturn(builder.addConstructorArgReference("value of ref"), builder);
        control.expectAndDefaultReturn(builder.addConstructorArgValue("value of description"), builder);
        control.expectAndDefaultReturn(builder.addConstructorArgValue("value of available"), builder);
        control.expectAndDefaultReturn(builder.addConstructorArgValue("value of not-available-message-id"), builder);
        control.replay();
       
        serviceBeanDefinitionParser.doParse(mockElement, builder );
        control.verify();
    }
   
    class MockElement extends DOMElement{

        public MockElement(String name) {
            super(name);
            setAttribute("id", "value of id");
            setAttribute("name", "value of name");
            setAttribute("ref", "value of ref");
            setAttribute("description", "value of description");
            setAttribute("available", "value of available");
            setAttribute("not-available-message-id", "value of not-available-message-id");
        }}

2. JUnit
    public void testAssertEqualsOnDoubleHelper() throws Exception {
        new UnitTests().assertEquals("정확하게 같은 값이 아닙니다.", 0.0000D, 0.0000D);
        new UnitTests().assertEquals("정확하게 같은 값이 아닙니다.", 0.0000D, 0.0000f);
        new UnitTests().assertEquals("정확하게 같은 값이 아닙니다.", 1.0000D, 1.0000f);
       
        try{
            new UnitTests().assertEquals(1.000001D, 1.0000f);
            fail("delta가 존재합니다.");
        }catch (AssertionFailedError e) {
            // junit은 사용자가 지정한 오류 메시지 뒤에 대괄호를 붙이고 그 안에 expected와 actual 값을 문자열로 붙인다.
            assertTrue(e.getMessage().startsWith("완벽하게 동일한 값은 아닙니다."));
        }
    }

1. abstract 클래스 테스트하기
    public void testAutowireMode() throws Exception {
        assertEquals("디폴트 Autowire 모드가  AUTOWIRE_BY_NAME이 아닙니다.", AutowiredIntegrationTests.AUTOWIRE_BY_NAME,
                new MyIntegrationTests().getAutowireMode());
    }
   
    class MyIntegrationTests extends AutowiredIntegrationTests{}


이올린에 북마크하기(0) 이올린에 추천하기(0)

EasyMock(old)을 활용한 협업 테스트 에서 소개한 불편한 EasyMock 1.2. Mock 객체를 사용한 테스트 자체도 이해하는데 장벽이지만, EasyMock 1.2는 벽을 이중으로 만든다. 기억하기도 어려울 뿐더러 직관성도 떨어진다. 우선 EasyMock과 ClassExtension의 차이가 나타나지 않도록 createControl 팩토리 메소드를 래핑하는 선에서 그냥 썼다. 어제 밤 팀원에서 Mock을 이용한 테스트에 대해 안내하면서 API가 어려워 리듬을 잃어버리는 일이 발생했다.

취침에 들어가려고 발을 씻던 중에 아이디어를 실험하기 위해 컴퓨터 앞에 앉았다. 내일을 위해 오버하면 안되는데... 일단 EasyMock 문서가 부족하다고 불평했었는데, 1.2 안에 sample이 있었다. 이런... 샘플을 무시하는 습관이라니..ㅡㅡ;

API 대치/래핑을 통해 샘플의 코드를 개선할 수 있다면 1차적으로는 성공이다. 그리고, 그것이 최소한 EasyMock 1.2를 써본 팀원에게 좋은 평을 받는다면 졸린 눈으로 버틴 시간이 보상받을 것이다.

        control = MockControl.createControl(IMethods.class);
        mock = (IMethods) control.getMock();

예제의 setUp에서 나온 코드. 전형적인 쌍이다. 사실 난 MockControl이란 것이 마음에 들지 않는다. MockControl은 두 가지 역할을 갖는다. 하나는 Mock객체에 대한 기대값을 설정하는 것이고, 다른 하나는 테스트 수행을 위한 컨트롤 역할이다. 어찌 되었든 MockControl 보다는 MockExpections이 나은 듯 하다. 위키피디아에서 본 Setting expectations 문구가 힘을 실리게 해준다. 위의 두 줄의 문장을 하나로 바꾸고 싶은데, 리턴이 두 개인지라 어려워보인다.

사실상 저 둘은 불가분의 관계로 보이니까 Spring의 ModelAndView 처럼 하나로 묶어보자.

  expectations = new ExpectationsOn(IMethods.class);
  mock = (IMethods) expectations.getMock();

MockExpections 라는 클래스로 합치는 것을 시도해보았으나 결국은 Mock 객체 호출이 필요했다. 여기까지는 별반 차이가 없어 보인다. 하지만, 실행 코드는 좀 간결하고 직관적으로 바뀌었다. 아래와 같은 코드였는데

     mock.throwsNothing(true);
     control.setReturnValue("Test");
     control.setReturnValue("Test2");
     control.replay();

     assertEquals("Test", mock.throwsNothing(true));
     assertEquals("Test2", mock.throwsNothing(true));

     control.verify();


안 불러도 별 차이가 없는, verify()는 생략해버리고 Control보다는 '기대 값'이란 점을 강화했다. API 스타일은 Fluent Interface를 채용했다.

  mock.throwsNothing(true);
  expectations.returns("Test").returns("Test2").assert();

  assertEquals("Test", mock.throwsNothing(true));
  assertEquals("Test2", mock.throwsNothing(true));

ready 가 작위적인 느낌이 있지만, 익숙하지 않은 사람에게 replay 역시 큰 차이가 없다. 여기까지만 하고... 위키피디아의 Mock 객체에 대한 메모를 남겨둔다.


a mock object in its place:

  • supplies non-deterministic results (e.g. the current time or the current temperature);
  • has states that are difficult to create or reproduce (e.g. a network error);
  • is slow (e.g. a complete database, which would have to be initialized before the test);
  • does not yet exist or may change behavior;
  • would have to include information and methods exclusively for testing purposes (and not for its actual task).  
이올린에 북마크하기(0) 이올린에 추천하기(0)

Mock Objects 라이브러리 선정을 위한 참고 자료에서 소개한 easy mock 사용하기. 2.2 홈페이지 문서를 참조하여 작성한다.

easy mock2 부터는 jdk5에서만 구동함!

먼저 Mock 객체를 얻기 위한 세 가지 할 일
  1. create a Mock Object for the interface we would like to simulate,
  2. record the expected behavior, and
  3. switch the Mock Object to replay state.

1번에 대한 예제 코드:
mock = createMock(Collaborator.class);

createMock은 이클립스(3.2 기준) 자동 import가 되지 않으므로 직접 타이핑해야 한다.
import static org.easymock.EasyMock.createMock;

2번에 대한 예제 코드는 애매하다. 업무(도메인)에 따라 천차 만별이기 때문이다. 중요한 것은 JUnit assertion의 기대값을 하나의 객체가 아니라 행위로 설정 혹은 녹화 한다는 점이다. 이렇게 예상되는 행위를 설정할 수 있는 'intelligence'로 인해서 stub과 구분된다는 점을 Expert Spring MVC and Web Flow (Expert) p297에서 언급하고 있다. 마틴 파울러의 글, 테스트 전용 객체(Test Double)에서는 다른 개념을 더 엮어서 분류하고 있다.

3번은 예상/녹화한 대로 실행/재생하는 것이다:
replay(mock);

추가적으로 애초 의도대로 테스트가 진행되었는지 검증이 필요하다:
verify(mock);

To verify that the specified behavior has been used, we have to call verify(mock)

실전 예제

웹 컨트롤러의 ID 중복 확인 사례




이올린에 북마크하기(0) 이올린에 추천하기(0)