Whiteship's Note

인터페이스를 사용할 때와 안 할 때의 테스팅 차이

모하니?/Coding : 2007. 8. 22. 14:49


/**
 * KnightOfTheRoundTable 클래스 단위 테스트 불가능.
 * HolyGrailQuest 클래스에 종속성을 가지고 있기 때문에
 * 1. HolyGrailQuest 클래스까지 같이 테스트하게 된다.
 * 2. 여러 경우(null을 반환하거나, exception 발생 시키거나, 제대로 된 경우)를 테스트 할 수 없다.
**/
public class KnightOfTheRoundTableTest extends TestCase {
    public void testEmbarkOnQuest() throws GrailNotFoundException {
        KnightOfTheRoundTable knight = new KnightOfTheRoundTable("Bedivere");
        HolyGrail grail = knight.embarkOnQuest();
        assertNotNull(grail);
        assertTrue(grail.isHoly());
    }
}

/**
 * Quest 인터페이스를 사용했기 때문에
 * 1. 종속성을 가지는 인터페이스(Quest)의 Mock 객체를 만들어서 단위 테스트를 할 수 있다.
 * 2. 여러 Mock 객체를 만들어서 다양한 상황을 테스트 할 수 있다.
**/
public class KnightOfTheRoundTableTest extends TestCase {

    //Best Case :: embarkOnQuest()에서 HolyGrail 반환 받는 경우.
    public void testEmbarkOnQuest() throws GrailNotFoundException {
        KnightOfTheRoundTable knight = new KnightOfTheRoundTable("Bedivere");

        Quest mockQuest = createMock(Quest.class);
        expect(mockQuest.embark()).andReturn(new HolyGrail());
        replay(mockQuest);

        knight.setQuest(mockQuest);
        HolyGrail grail = (HolyGrail) knight.embarkOnQuest();
        assertNotNull(grail);
        assertTrue(grail.isHoly());
    }

    //Bad Case :: embarkOnQuest()에서 null 반환 받는 경우.
    public void testEmbarkObQuest2() throws Exception {
        KnightOfTheRoundTable knight = new KnightOfTheRoundTable("Bedivere");

        Quest mockQuest = createMock(Quest.class);
        expect(mockQuest.embark()).andReturn(null);
        replay(mockQuest);

        knight.setQuest(mockQuest);
        Object grail = knight.embarkOnQuest();
        assertNull(grail);
    }

    //Bad Case :: embarkOnQuest()에서 GrailNotFoundException 예외 발생
    public void testEmbarkOnQuest3() throws Exception {
        KnightOfTheRoundTable knight = new KnightOfTheRoundTable("Bedivere");

        Quest mockQuest = createMock(Quest.class);
        expect(mockQuest.embark()).andThrow(new GrailNotFoundException());
        replay(mockQuest);

        knight.setQuest(mockQuest);
        try{
            knight.embarkOnQuest();
            fail();
        } catch (GrailNotFoundException e) {

        }
    }
}

얼핏 아래의 코드가 더 길고 코딩하기 귀찮아 보이지만, 그렇게 코딩이라도 해서 테스트를 해볼 수 있다는 것이 어딜까요?
사용자 삽입 이미지
위에 있는 코드는 바로 위의 그림처럼 테스트 대상인 KnightOfTheRoundTable 클래스가 종속하고 있는 HolyGrailQuest까지 테스트에 포함이 되게 됩니다. 원하던 상황이 아닙니다. KnightOfTheRoundTable 클래스에서 사용하고 있는 HolyGrailQuest의 행위 하나에 종속이 되게 됩니다. 테스트 할 수 있는 여지가 매우 적어집니다.
사용자 삽입 이미지
하지만 이렇게 중간에 Quest라는 인터페이스를 두기만 하면(코딩도 별로 어렵지 않습니다. 그냥 인터페이스 만들고 그거 implements하게만 하면 되죠.) EasyMock 같은 걸로 Mock 객체 만들어서 Quest를 구현한 클래스가 없더라도 KnightOfTheRoundTable 클래스의 테스트가 가능합니다. Mock 객체의 행동을 맘대로 정할 수 있기 때문에 테스트 할 여지도 많아지죠.

인터페이스 하나 추가로 이렇게 테스팅이 유연해 지는데 어떻게 안 쓸 수 있을까요. 사실 테스팅이 유연해 진 이유는 인터페이스 사용으로 인해 종속성이 완화 되었기 때문이지만 말이죠.
top

Write a comment.




: 1 : ··· : 238 : 239 : 240 : 241 : 242 : 243 : 244 : 245 : 246 : ··· : 299 :