TDD 로 Database Programming 을 진행하는 방법 & 경험들. See Also TdddArticle ---- [1002]의 경우 TDD 로 DB 부분을 만들때 어떻게 진행될까 궁리하던중 두가지를 실험해보았다. 보통은 TDD로 DB 부분을 만들때 DB Repository 부분에 대해서 MockObject 를 만들지만, 다음은 Mock 을 안만들고 작성해봤다. 어떤 일이 일어날까를 생각하며. {{{~cpp import junit.framework.TestCase; import java.sql.*; public class SpikeRepositoryTest extends TestCase { private Connection con; protected IRepository repository; private String writer; private String title; private String body; public void setUp() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException { initConnection(); repository= new SpikeRepository(con); repository.initialize(); writer = "writer"; title = "title"; body = "body"; } public void tearDown() throws SQLException { repository.destroy(); uninitConnection(); } public void testEdit() throws SQLException { repository.createArticle(writer, title, body); String writerEdited = "writerEdited"; String titleEdited = "titleEdited"; String bodyEdited = "bodyEdited"; repository.edit(1, writerEdited, titleEdited, bodyEdited); Article article = repository.get(1); assertEquals (writerEdited, article.getWriter()); assertEquals (titleEdited, article.getTitle()); assertEquals (bodyEdited, article.getBody()); } public void testTotalArticle() throws SQLException { repository.createArticle(writer, title, body); assertEquals (1, repository.getTotalArticle()); } public void testCreateArticle() throws SQLException { repository.createArticle(writer, title, body); assertEquals (1, repository.getTotalArticle()); } public void testDelete() throws SQLException { repository.createArticle(writer, title, body); repository.delete(1); assertEquals (0, repository.getTotalArticle()); } public void testGet() throws SQLException { repository.createArticle(writer, title, body); Article article2 = repository.get(1); assertEquals(writer, article2.getWriter()); } public void testArticleTableInitialize() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException { repository = new SpikeRepository(con); repository.initialize(); String articleTableName = "articlelist"; String sqlStr="select id, writer, title, body from " + articleTableName; PreparedStatement pstmt= con.prepareStatement(sqlStr); ResultSet rs = pstmt.executeQuery(); assertEquals (0, rs.getRow()); } private void initConnection() throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException { String hostname = "localhost"; String dbname = "reset"; String userId = "reset"; String userPass = "reset"; Class.forName("com.mysql.jdbc.Driver").newInstance(); String url="jdbc:mysql://"+hostname+"/"+dbname+"?user="+userId+"&password="+userPass; con = DriverManager.getConnection(url); } private void uninitConnection() throws SQLException { con.close(); } public void testDuplicatedInitialize() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { repository.initialize(); repository.initialize(); repository.initialize(); repository.destroy(); } } }}} 작성하는중에, DB에 직접 접속해서 확인하는 코드가 테스트에 드러났다. (이는 예상한 일이긴 하다. DB 에 비종속적인 interface 를 제외하더라도 DB 쪽 코드를 계속 쌓아가기 위해선 DB 코드를 어느정도 써야 한다.) 처음 DB 에 직접 데이터를 넣을때는 side-effect가 발생하므로, 테스트를 2번씩 돌려줘서 side-effect를 확인을 했다. 점차적으로 initialize 메소드와 destroy 메소드를 만들고 이를 setUp, tearDown 쪽에 넣어줌으로 테스트시의 side-effect를 해결해나갔다. 프로그래밍을 하다가, 만일 여기서부터 interface 를 추출한뒤에 거꾸로 MockRepository 를 만들 수 있을까 하는 생각을 했다. (interface 를 추출함으로서 같은 메소드에 대해 다른 성격의 Repository, 즉 File Based 나 다른 서버 로부터 데이터를 얻어오는 Repository 등 다형성을 생각해볼 수 있는 것이다.) 결과는 다음의 문제가 발생하였다. 바로, interface 에 DB Exception 던진것들이 묻어나는것이다. {{{~cpp import java.sql.SQLException; public interface IRepository { Article get(int index) throws SQLException; void initialize() throws SQLException; void destroy() throws SQLException; void edit(int index, String writer, String title, String body) throws SQLException; int getTotalArticle() throws SQLException; void createArticle(String writer, String title, String body) throws SQLException; void delete(int index); } }}} 즉, MockRepository 에서는 Exception 을 던질 필요가 없는데, 메소드마다 전부 throw 를 던져줘야 한다. (한편으로는, 다른 언어에서는 상관없는데 Java 에서의 Checked Exception 의 문제일런지도 모르겠다. 만일 MockRepository를 먼저 만든다면? interface 를 추출한 순간에는 문제가 없겠지만, 다음에 DBRepository 를 만들때가 문제가 된다. interface 의 정의에서는 예외를 던지지 않으므로, interface 를 다시 수정하던지, 아니면 SQL 관련 Exception 을 전부 해당 메소드 안에서 try-catch 로 잡아내야 한다. 즉, Database 에서의 예외처리들에 대해 전부 Repository 안에서 자체해결을 하게끔 강요하는 코드가 나온다. 어떤 것이 올바른 일일까. --[1002]