1016038887.bmp테스트 클래스와 구현 클래스 간의 갯수가 일치 하지 않습니다. 이유는 인터페이스인 MemberDao 클래스에 대한 테스트 클래스를 만들지 않았기 때문입니다.

이 상황을 Abstract Test라는 아티클을 보면서 개선시켜 보겠습니다.

먼저 인터페이스에 대한 추상 테스트 클래스를 작성합니다. 추상 테스트 클래스는 몇가지 특성이 있는데 기본적으로 다음과 같은 코드가 기본 형태 입니다.
[#M_ more.. | less.. |
import junit.framework.*;

public abstract class AbstractTest extends TestCase
{
  private TestableInterface testableInterface;

  public AbstractTest(String name) { super(name); }

  public void setUp()
  throws Exception
  {
     //Create the object to be tested.
     testableInterface = createTestableInterface()
     //assert that result is non-null
     assertNotNull(testableInterface);
  }

  public abstract TestableInterface createTestableInterface()
  throws Exception;

  public final void testSomething()
  {}

  public final void testSomethingElse()
  {}
}

_M#]

일단 여기서 의문이 드는 것으로

1. 생성자에 String 매개변수가 있다는 점.
2. 팩토리 메소드인 createFoo()에서 Exception을 던지고 있는 모습.
3. 메소드 몇개가 final 이라는 점.

이 중에서 위에 두개는 아직 이해가되지 않지만 세번째 final은 오버라이딩으로 인해 sub class의 의도가 오염될 수 있는 것을 방지 하는 것 같습니다. Liskov 원칙과 관련이 있는 것 같습니다.

위를 따라서 MemberDaoTest 클래스를 작성합니다.
[#M_ more.. | less.. |
import net.webapp2.member.domain.Member;
import junit.framework.TestCase;

public abstract class MemberDaoTest extends TestCase {

   private MemberDao memberDao;

   public MemberDaoTest(String name){
       super(name);
   }

   @Override
   public void setUp() throws Exception {
       memberDao = createMemberDao();
       assertNotNull(memberDao);
   }

   public abstract MemberDao createMemberDao() throws Exception;

   public final void testAdd(){
       Member member = createMember();
       memberDao.add(member);
       assertEquals(1, memberDao.getNumberOfMembers());

       memberDao.add(member);
       assertEquals(2, memberDao.getNumberOfMembers());
   }

   private Member createMember() {

       Member member = new Member();

       String name = "백기선";
       String phone = "016-9889-6911";
       String email = "whiteship2000@gmail.com";
       String messengerId = "whiteship2000@hotmail.com";
       String blugAddress = "http://whiteship.tistory.com";

       member.setName(name);
       member.setPhone(phone);
       member.setEmail(email);
       member.setMessengerId(messengerId);
       member.setBlugAddress(blugAddress);

       return member;
   }
}_M#]
이제 이 추상 테스트 클래스를 상속 받아서 추상 메소드를 구현할 클래스를 만들어야 합니다. 이 클래스의 팩토리 메소드에서 특정 구현 클래스의 객체를 리턴해둬야 할 것 같습니다. 역시 이 클래스에 대한 탬플릿도 있습니다.
[#M_ more.. | less.. |
public class ConcreteTest
extends
  AbstractTest
{
  public TestableInterface createTestableInterface()
  throws Exception
  {
     //Create and return the implementation of TestableInterface
  }

  //optionally add a main method to run the test.
}

_M#]
위 클래스를 따라서 구상 클래스를 작성합니다.
[#M_ more.. | less.. |
import junit.framework.TestSuite;

public class MembeDaoImplTest extends MemberDaoTest {

   public MembeDaoImplTest(String name) {
       super(name);
   }

   @Override
   public MemberDao createMemberDao() throws Exception {
       return new SqlmapMemberDao();
   }

   public static void main(String [] args)
   {
     TestSuite suite = new TestSuite();
     suite.addTestSuite(MembeDaoImplTest.class);
     junit.textui.TestRunner.run(suite);
   }
}
_M#]
이렇게 따라 할 수 있었습니다.

간단하게 Abstract Test를 만드는 순서를 정리하자면

1. 모든 인터페이스(or 추상 클래스)에 대한 추상 테스트 클래스를 작성하라.
2. 인터페이스에 대해서 구체적인 테스트들을 작성하라.(final 메소드들을 가리키는 듯)
3. 추상 테스트 클래스를 구현한 테스트를 작성하여 테스트 하라.

하지만 아직까지 의문점 1, 2는 풀리지 않네요. 차근 차근 봐야겠습니다.

에피소드
[#M_ more.. | less.. |
위의 예제는 적당하지 않다는 문제가 있네요. 기존에 있던 SqlmapMemberDaoTest 클래스는Spring에서 제공하는 AbstractTransactionalDataSourceSpringContextTests 이 클래스를상속 받았기 때문에 SqlMapClient 같은 멤버 변수가 세팅되었던 것인데... 지금까지 따라해본 방식으로 하다보면 자바에선다중 상속이 안되기 때문에 결국 둘 중 하나를 선택해야 하는데 이미 Spring에 있는 클래스를 선택하는게 좋을 것 같습니다.

지금에서야 깨닫게 되는 것이지만 Spring에서 이미 Abstract Test를 제공해주고 있는 것 같습니다. 오.. 놀라워라... 왜 몰랐을까요. ㅠ.ㅠ 아 바보...
_M#]