5.1 DbUnit의 장점
DB를 사용하는 부분은 프로그래밍 언어 외적인 부분이 상당 부분 포함되기 때문에 TDD를 적용하기가 종종 쉽지 않다. 이럴때 도움을 받을 수 있는 유틸리티로 DbUnit이 있다.
- 독립적인 데이터베이스 연결 지원
- 데이터베이스의 특정 시점 상태를 쉽게 내보내거나 읽어들일 수 있다.
- 테이블이나 데이터셋을 서로 쉽게 비교할 수 있다.
DbUnit은 독립적으로 사용하기보다는 JUnit 등의 테스트 프레임워크 등과 함께 사용한다. 따라서 테스트 프레임워크라기 보다는 테스트 지원 라이브러리에 더 가깝다.
5.2 데이터셋
데이터셋은 데이터베이스나 그 안에 존재하는 테이블 혹은 그 일부를 xml이나 csv 파일로 나타낸 모습이다.
예) SELLER table
ID | NAME | |
---|---|---|
aaaa | 김가가 | aaa@naver.com |
bbbb | 고나나 | bbb@hanmail.com |
cccc | 황다다 | ccc@naver.com |
1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
<seller ID="aaaa" NAME="김가가" EMAIL="aaa@naver.com"/>
<seller ID="bbbb" NAME="고나나" EMAIL="bbb@hanmail.com"/>
<seller ID="cccc" NAME="황다다" EMAIL="ccc@naver.com"/>
</dataset>
DbUnit은 이런 데이터셋의 형식을 이용해서 DB의 상태를 저장하거나 변경, 유지한다.
데이터베이스 연결과 테이블 초기화
현재 가정한 데이터베이스 내의 데이터 내용이 변경된다면 기능 구현에 문제가 없음에도 불구하고 테스트 케이스가 실패할 수 있다. 따라서 현재 가정되어있는 DB안의 데이터 상태가 테스트를 수행하기 전에 가정했던 모습으로 한결같이 유지 됐으면 좋겠다. 그래서 테스트 관련 테이블 초기화 -> 테스트 케이스 수행 이라는 전략을 세웠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DatabaseRepositoryTest {
//.....
//1.DbUnit을 사용하기 위해서는 DbUnit에서 제공하는 DBTestCase를 상속하도록 작성한다.
//상속하지 않고 DbUnit에서 제공하는 기능을 이용하려면 IDatabaseTester라는 인터페이스를 사용하면 된다.
private IDatabaseTester databaseTester;
@Before
public void setUp() throws Exception {
//2. IDatabaseTester 구현체로 JDBC 연결방식을 이용한다.
databaseTester = new JdbcDatabaseTester(driver, protocol+dbName);
try {
//3. 데이터셋을 지정한다.
IDataSet dataSet = new FlaxXmlDataSetBuilder().build(new File("seller.xml"));
//4. DB커넥션과 데이터셋을 이용해 DB에 특정 작업을 수행한다.
DatabaseOperation.CLEAN_INSERT.execute(databaseTester.getConnection(), dataSet);
}finally {
databaseTester.getConnection().close();
}
}
}
위 setUp() 메소드는 테스트 메소드가 수행되기 전에 항상 seller.xml에 지정된 상태로 테이블을 초기화한다.
데이터셋 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 현재 데이터베이스의 상태를 데이터 셋으로 추출한다.
IDataSet currentDBdataSet = databaseTester.getConnection().createDataSet();
//2. 데이터셋에서 특정 테이블을 가져온다.
ITable actualTable = currentDBdataSet.getTable("seller");
//3.미리 만들어놓은 예상 데이터셋을 읽어들인다.
IDataSet expectedDataSet = new FlaxXmlDataSetBuilder().build(new File("expected_seller.xml"));
//4. 예상 데이터셋 중에서 비교에 사용할 테이블을 읽어들인다.
ITable expectedTable = expectedDataSet.getTable("seller");
//5. DbUnit에서 제공하는 Assertion 클래스의 메소드를 이용해 결과를 비교한다.
Assert.assertEquals(expectedTable, actualTable);
5.3 DbUnit 데이터셋의 종류
DbUnit은 DB 데이터의 구조를 나타내기 위해 데이터셋이라는 개념을 사용한다. 데이터셋은 하나의 타입을 나타냄과 동시에 테이블들의 집합체를 표현하는 IDataSet 인터페이스의 구현체를 의미하기도 한다.
FlatXmlDataSet
- 테이블 이름을 XML TAG 구성요소로 적는다.
- 컬럼 이름은 속성으로 적는다.
- null 값을 넣을 컬럼은 표현하지 않는다. 자동으로 null 값이 들어간다.
- XML, DTD를 지정하지 않아도 된다.
- 데이터셋 중 가장 흔하게 사용된다.
1
2
3
<dataset>
<EMPLOYEE NO="101" NAME="안병현" EMAIL="megan@hssm.kr"></EMPLOYEE>
</dataset>
XmlDataSet
- 다소 장황한 버전
- DTD 반드시 포함
- 잘 사용하지 않음
StreamingDataSet
- 데이터베이스의 커서 개념처럼 단방향으로 동작, 현재 레코드만 메모리에 존재
- UPDATE, INSERT, REFRESH 같은 동작을 하는 XML 데이터셋을 읽어들일 때 매우 효율적으로 동작
1
2
IDataSetProducer producer = new FlatSmlProducer(new InputSource("dataset.xml"));
IDataSet dataSet = new StreamingDataSet(producer);
DatabaseDataSet
- 데이터베이스 인스턴스에 대한 접근을 제공한다.
- 직접 new로 생성하지 않고 팩토리 메소드로 만들어낸다.
1
IDataSet currentDBdataSet = IDatabaseConnection.createDataSet();
QueryDataSet
- 쿼리문으로 데이터셋을 만들어낸다.
1
2
3
QueryDataSet dataSet = new QueryDAtaSet(connection);
dataSet.addTable("NEW_EMPLOYEE", "SELECT * FROM EMPLOYEE WHERE EMPNO > 600");
dataSet.addTAble("DEPARTMENT");
XlsDataSet
- MS 엑셀 문서를 데이터셋으로 인식한다.
- 엑셀 문서 내의 각 시트를 테이블로 인식한다.
- 시트의 첫 번째 줄을 컬럼으로 인식한다.
- 나머지 줄은 데이터 값으로 인식한다.
ReplacementDataSet
- 데이터셋에서 특정한 문자열을 치환하기 위해 사용한다.
- 보통은 null 값을 다르게 표현하고 런타임시에 치환하는 데 많이 사용한다.
1
2
3
4
<dataset>
<EMPLOYEE NO="101" NAME="안병현" EMAIL="megane@hssm.kr"></EMPLOYEE>
<EMPLOYEE NO="102" NAME="김상옥" EMAIL="[null]"></EMPLOYEE>
</dataset>
1
2
ReplacementDataSet dataSet = new ReplacementDataSet(new FlaxXmlDataSet(...));
dataSet.addReplacementPbject("[NULL]", null);
5.4 DbUnit의 DB 지원 기능
DbUnit에서는 데이터셋을 이용한 DB관리 작업을 DataBaseOperation이라는 개념으로 만들어 놓았다. DatabaseOperation.오퍼레이션이름.execute(DB커넥션, 데이터셋)
[오퍼레이션의 종류]
5.5 DbUnit과 Ant
Ant를 사용하는 경우 개발툴이나 IDE에 의존하지 않고, 시스템 레벨에서 배치 작업등을 이용해 좀 더 높은 수준의 자동화를 이룰 수 있다.
DbUnit에서 Ant를 이용하기 위해서는 dbunit.jar 파일을 추가한다. 그리고 Ant 빌드파일 내에 태스크를 정의한다.
1
<taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/>
Ant 적용전
1
2
3
4
5
6
@Before
public void setUp() throws Exception {
databaseTester = new JdbcDatabaseTester(driver, protocol+dbName);
IDataSet dataSet = new FlaxXmlDataSetBuilder().build(new File("seller.xml"));
DatabaseOperation.CLEAN_INSERT.execute(databaseTester.getConnection(), dataSet);
}
Ant 적용후
1
2
3
4
5
6
7
8
9
<target name="sellerdb-init">
<dbunit driver="org.apache.derby.jdbc.EmbeddedDriver"
url="jdbc:derby:shopdb"
userid=""
password=""
>
<opration type="CLEAN_INSERT" src="seller.xml"/>
</dbunit>
</target>
이처럼 무언가 일괄적으로 DbUnit을 이용해 DB를 조작할 때 Ant를 이용하면 유리하다.
5.6 정리
데이터셋을 만드는 일은 비용과 노력이 많이 드는 작업이며, DbUnit을 반드시 써야할지는 고민해볼 필요가 있다.
- 개발자마다 데이터베이스 인스턴스나 스키마를 하나씩 사용하자
- 나중에 정리할 필요가 없게 setUp 처리를 잘하자
- 데이터셋의 크기는 작고, 여러개를 만드는네 꼭 필요한 테스트 데이터 위주로 만들자
- 데이터셋을 너무 많이 만들지 말라
- 클래스 기반으로 만들고 다른 테스트 클래스와 공유해서 사용하지 말자