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 안에서 자체해결을 하게끔 강요하는 코드가 나온다.