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

Retrieved from http://wiki.zeropage.org/wiki.php/TestDrivenDatabaseDevelopment
last modified 2021-02-07 05:28:11