Home 5장 데이터베이스 테스트 DbUnit
Post
Cancel

5장 데이터베이스 테스트 DbUnit

5.1 DbUnit의 장점

DB를 사용하는 부분은 프로그래밍 언어 외적인 부분이 상당 부분 포함되기 때문에 TDD를 적용하기가 종종 쉽지 않다. 이럴때 도움을 받을 수 있는 유틸리티로 DbUnit이 있다.

  • 독립적인 데이터베이스 연결 지원
  • 데이터베이스의 특정 시점 상태를 쉽게 내보내거나 읽어들일 수 있다.
  • 테이블이나 데이터셋을 서로 쉽게 비교할 수 있다.

DbUnit은 독립적으로 사용하기보다는 JUnit 등의 테스트 프레임워크 등과 함께 사용한다. 따라서 테스트 프레임워크라기 보다는 테스트 지원 라이브러리에 더 가깝다.

5.2 데이터셋

데이터셋은 데이터베이스나 그 안에 존재하는 테이블 혹은 그 일부를 xml이나 csv 파일로 나타낸 모습이다.

예) SELLER table

IDNAMEEMAIL
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 처리를 잘하자
  • 데이터셋의 크기는 작고, 여러개를 만드는네 꼭 필요한 테스트 데이터 위주로 만들자
  • 데이터셋을 너무 많이 만들지 말라
  • 클래스 기반으로 만들고 다른 테스트 클래스와 공유해서 사용하지 말자
This post is licensed under CC BY 4.0 by the author.

4장 한계 돌파를 위한 노력, Mock을 이용한 TDD

6장 단위테스트 지원 라이브러리:Unitils