===== 개요 =====
데이터베이스 관련 단위 테스트를 수행하기 위한 Test Case 를 작성하고 실행하는 방법을 안내한다.

===== 설명 =====
DAO (Data Access Object) 클래스를 개발하는 경우 이에 대한 단위 테스트 케이스를 작성하기 위해 고려해야 할 사항은 다음과 같다.

  * 데이터베이스 연결
  * 테이블 생성 및 초기 데이터 입력
  * DAO 클래스 실행 시 필요한 테스트 데이터 관리
  * DAO 테스트 후 트랜잭션 Rollback 혹은 Commit 처리

이를 위해, 데이터베이스가 아직 준비 되지 않은 경우에는 개발자 로컬에 hsqldb, derby, mysql 과 같은 dbms를 임시로 설치하기도 한다.\\
데이터베이스가 준비 되었다 하더라도 테이블 생성 스크립트를 작성하고 직접 dbms 와 연결하여 commit 후의 데이터를 확인해야 한다.\\
또한, 프로그램이 수정 보완 되는 과정에서 여러 번 중복 테스트를 하기 위해서는 다시 dbms 에 접속하여 rollback 을 하는 등의 작업을 수행하기도 한다.

만약 dbunit / unitils / spring-test 등을 기본으로 몇 가지 Tip 을 적절히 이용하면 보다 손 쉽게 데이터베이스 관련 테스트 케이스 개발을 수행 할 수 있다. \\
자세한 사항은 아래 사용법을 참조하도록 한다.

===== 환경설정 =====
[[unit_test#환경설정|Unit Test 환경설정]]과 동일하다.

===== 사용법 =====

==== 데이터베이스 연결 ====

먼저, 데이터베이스 준비(기동, DataSource 생성)에 대해 알아보자.\\
테스트를 위해 사용할 수 있는 Database 가 준비되어 있고 이를 위한 접근 방법에 대한 안내까지 받았다면 고민할 필요는 없다.\\
만약 이러한 상황이 아니라면, apache dbcp datasource 를 이용하여 생성하면 되고, springframework 를 사용하고 있다면 더더욱 간단히 해결될 수 있다.\\
사실, egovframework 의 개발환경에서 제공하고 있는 CI Server 를 이용해 반복적으로 테스트를 수행하기 위해서는 테스트만을 위한 전용 DBMS 를 준비하는 것이 이상적이지만, 테스트 수행 후 깔끔하게 rollback 을 수행한다면 테스트 용 전용 DBMS 가 없어도 큰 무리는 없다.\\

=== 프로퍼티 설정 ===

<code:text>
# Properties for the PropertiesDataSourceFactory
database.driverClassName=org.hsqldb.jdbcDriver
database.url=jdbc:hsqldb:sampledb
database.userName=sa
database.password=
</code>

=== Test Case 작성 ===
Test Case 작성 시 **''@RunWith(UnitilsJUnit4TestClassRunner.class)''** 을 선언하면 된다.

Unitils 는 springframework 와 달리 DataSource 가 아닌 TestDataSource 라는 것을 사용한다. \\
이 TestDataSource 는 Unitils 설정에 따라 선언과 동시에 DataSource 를 Getting 하는 것 뿐 아니라 dbmaintain 과 같은 별도의 작업을 동시에 수행할 수 있다.\\ 이는 다음에 설명하기로 하고, 여기서는 단순히 unitils 를 이용하여 TestDataSource 를 Getting 하는 예제만을 이해하도록 한다.

<code:java>
@RunWith(UnitilsJUnit4TestClassRunner.class)
public class DataSourceGetTest_unitilsDataSource {
 
 /*
  * unitils.properties 에 설정 된 database 접근 정보를 기반으로 
  * 테스트 용 DataSource 를 만든 후 자동으로 injection 해 준다.
  * (unitils.properties 파일의 위치와 이름은 변경할 수 없다.)
  * 
  * @see  unitils.properties
  */
 @TestDataSource    
 private DataSource dataSource;

 @Test
 public void checkTestDataSource() {
  assertNotNull("dataSource를 정상적으로 get 했는지를 확인한다.", dataSource);
 }
}
</code>


==== 테이블 생성 및 초기 데이터 입력 ====

Unitils 를 이용하면 위처럼 TestDataSource 를 생성하거나, spring+unitils 조합의 DataSource 를 생성함과 동시에 테스트에 필요한 테이블을 생성하고 초기 데이터를 입력할 수 있다.

이 기능은 다음의 단계로 활용할 수 있다.

  * 단위 테스트에 필요한 테이블을 생성하는 DML 스크립트 작성
  * 이 DML 스크립트 실행 여부를 설정하고, 해당 디렉토리를 설정
  * 테스트 케이스 수행 시 해당 기능 호출

현재는 'oracle', 'db2', 'mysql', 'hsqldb' and 'postgresql' 의 DBMS 만 지원한다.

=== 프로퍼티 설정 ===

== DBMS 의 종류, 스키마 정보 등을 설정 ==
<code:text>
# This property specifies the underlying DBMS implementation. Supported values are 'oracle', 'db2', 'mysql', 'hsqldb' and 'postgresql'.
# The value of this property defines which vendor specific implementations of DbSupport and ConstraintsDisabler are chosen.
database.dialect=hsqldb

# A comma-separated list of all used database schemas. The first schema name is the default one, if no schema name is
# specified in for example a dbunit data set, this default one is used.
# A schema name is case sensitive.
database.schemaNames=PUBLIC

# Type of transaction manager that should be created:
# simple: a simple transaction manager that wraps the datasource to control transactions
# spring: a transaction manager that delegates actions to the transaction manager that is configured in the current spring context
# auto: this will first try to load the spring transaction manager. if spring is not available, it will load the simple transaction manager
transactionManager.type=auto
</code>

== 데이터베이스 스키마 정보를 자동으로 update 할지를 설정 ==

이 때 사용하는 sql 문을 저장해 둔 디렉토리 경로를 설정한다.\\
dbMaintainer.disableConstraints.enabled 는 단위 테스트 수행 시 테이블간의 제약 조건을 제외한다는 것으로 true 로 지정하면 이 관계를 임시적으로 끊는 등의 별도 작업없이 진행이 되므로 유용하다.
<code:text>
# If set to true, the DBMaintainer will be used to update the unit test database schema. This is done once for each
# test run, when creating the DataSource that provides access to the unit test database.
updateDataBaseSchema.enabled=true

# Comma separated list of directories and files in which the database update scripts are located. Directories in this
# list are recursively searched for files.
dbMaintainer.script.locations=src/test/resources/META-INF/persistence/maintenance/hsqldb

# If set to true, an implementation of org.unitils.dbmaintainer.constraints.ConstraintsDisabler will be used to disable
# the foreign key and not null constraints of the unit test database schema.
# The ConstraintsDisabler is configured using the properties specified below. The property with key 'database.dialect'
# specifies which implementation is used.
dbMaintainer.disableConstraints.enabled=true
</code>

==== DAO 클래스 실행 시 필요한 테스트 데이터 관리 ====

데이터베이스 관련 CRUD 단위 테스트 수행 시, 테스트 수행 전에 데이터베이스에 필요한 데이터를 미리 저장해 두고 테스트 종료 후 이를 삭제하는 등의 작업을 좀 더 편리하게 수행할 수 있는 방법을 가이드한다. \\

DBUnit 을 사용할 경우 유용한 기본적인 기능은 다음과 같다.
  * XML 로 필요한 데이터를 선언 해 두면 단위 테스트 수행 시 이 데이터가 데이터베이스에 자동 저장된다
  * 데이터베이스 관련 테스트를 수행하고 그 결과값이 미리 XML 로 저장해 놓은 데이터와 자동으로 비교된다.
  * 테스트 수행 후 트랜잭션 commit/rollback을 지정할 수 있다.
그러나, DBUnit 을 직접 테스트 코드에서 사용하기 위해서는 별도의 이 XML 파일을 Load 하는 등의 별도 프로그램 로직이 필요하여 사용하기에 불편함이 따른다. \\
이의 해소를 위해서 Unitils 는 DBUnit 관련한 기능을 Annotation만으로도 간단하게 사용할 수 있는 기능을 제공하므로, 이를 활용하는 방법을 안내한다.

=== 프로퍼티 설정 ===

== DBUnit 이용할 경우 트랜잭션 처리 방법 설정 ==

DBUnit 을 이용해 데이터를 저장 혹은 삭제한 경우 commit/rollback 할런지를 설정하는 부분이다. \\ 이 부분은 Test Case 를 작성할 때 동적으로 변경할 수 있으므로 우선은 disabled 로 선언 해 둔다. \\ (CI 등을 활용하여 주기적으로 반복 테스트를 수행할 경우에는 rollback으로 설정하는 것을 권장한다.)
<code:text>
# Default behavior concerning execution of tests in a transaction. Supported values are 'disabled', 'commit' and 'rollback'.
# If set to disabled, test are not executed in a transaction by default. If set to commit, each test is run in a transaction,
# which is committed. If set to rollback, each test is run in a transaction, which is rolled back.
DatabaseModule.Transactional.value.default=disabled
</code>

== 사용하는 DBMS 의 종류, 스키마 정보 등을 설정 ==

Unitils 는 여기서 언급된 종류의 DBMS를 지원하며 여기에 없는 DBMS 는 unitils 의 기능을 제한적으로 사용할 수 밖에 없으므로 설정하지 않는다.
<code:text>
# This property specifies the underlying DBMS implementation. Supported values are 'oracle', 'db2', 'mysql', 'hsqldb' and 'postgresql'.
# The value of this property defines which vendor specific implementations of DbSupport and ConstraintsDisabler are chosen.
database.dialect=hsqldb

# A comma-separated list of all used database schemas. The first schema name is the default one, if no schema name is
# specified in for example a dbunit data set, this default one is used.
# A schema name is case sensitive.
database.schemaNames=PUBLIC

# Type of transaction manager that should be created:
# simple: a simple transaction manager that wraps the datasource to control transactions
# spring: a transaction manager that delegates actions to the transaction manager that is configured in the current spring context
# auto: this will first try to load the spring transaction manager. if spring is not available, it will load the simple transaction manager
transactionManager.type=auto
</code>

== 데이터베이스 스키마 정보를 자동으로 update 할지를 설정 ==
이 때 사용하는 sql 문을 저장해 둔 디렉토리 경로를 설정한다.\\
dbMaintainer.disableConstraints.enabled 는 단위 테스트 수행 시 테이블간의 제약 조건을 제외한다는 것으로 true 로 지정하면 이 관계를 임시적으로 끊는 등의 별도 작업없이 진행이 되므로 유용하다.
<code:text>
# If set to true, the DBMaintainer will be used to update the unit test database schema. This is done once for each
# test run, when creating the DataSource that provides access to the unit test database.
updateDataBaseSchema.enabled=true

# Comma separated list of directories and files in which the database update scripts are located. Directories in this
# list are recursively searched for files.
dbMaintainer.script.locations=src/test/resources/META-INF/persistence/maintenance/hsqldb

# If set to true, an implementation of org.unitils.dbmaintainer.constraints.ConstraintsDisabler will be used to disable
# the foreign key and not null constraints of the unit test database schema.
# The ConstraintsDisabler is configured using the properties specified below. The property with key 'database.dialect'
# specifies which implementation is used.
dbMaintainer.disableConstraints.enabled=true
</code>

== 테스트 수행 전 자동으로 입력할 데이터 정의 ==
테스트 클래스 혹은 테스트 메소드 선언부에 **@DataSet (파일의 경로) ** 와 같은 방법으로 호출한다. \\
XML 포맷의 데이터로서 작성 법은 다음과 같다.

<code:xml>
<dataset>
    <테이블이름 컬럼이름1="값" 컬럼이름2="값"  ...... />
</dataset>
</code>

== 테스트 수행 결과를 자동으로 비교할 데이터 정의 ==
테스트 클래스 혹은 테스트 메소드 선언부에 **@ExpectedDataSet (파일의 경로) ** 와 같은 방법으로 호출한다. \\
XML 포맷의 데이터로서 작성 법은 위와 동일하다.

<code:xml>
<dataset>
    <테이블이름 컬럼이름1="값" 컬럼이름2="값"  ...... />
</dataset>
</code>

====  DAO 테스트 후 트랜잭션 Rollback 혹은 Commit 처리 ====

전체 Test Case 에 모두 적용시킬 수 있는 설정을 수행할 수도 있지만, 프로그램에서 선언하여 설정하는 것을 권장한다.
또한, springframework 을 이용하는 경우 해당 springframework 의 txManager 를 Test Case 클래스 선언부에 설정할 수도 있다.
자세한 방법은 샘플 프로그램을 보고 확인하도록 한다.

===== 샘플 =====

아래는 이클립스 메이븐 프로젝트의 예제이다.\\ \\

{{:egovframework:dev:tst:junit_8.dbtestguide.jpg?800x500|}}

예제는 다음의 내용으로 구성되어 있다.

  * 테스트 대상
  * 테스트 시나리오
  * 환경 구성 정보
  * 테스트 프로그램
  * 테스트에 필요한 데이터
  * 테스트 수행 결과


이에 대해 하나하나 설명하기로 한다.

==== 테스트 대상 ====

  * DAO 구현 클래스 : [[NoticeDao.java]]
  * DAO 참조 클래스 : [[NoticeVo.java]]

==== 테스트 시나리오 ====

  - HSQLDB 를 이용 해 Notice 업무 수행을 위한 테이블을 생성(변경 시 업데이트만 수행)한다.
  - selectList, selectCount 등의 메소드 실행을 위해 외부의 XML 로 정의해 둔 기초 데이터 3건을 자동 입력한다.
  - insert / update / delete 메소드 실행을 위해 기초 데이터인 noticeVo 객체를 테스트 수행 전에 생성한다.
  - 의 select 결과에 대해 예상하는 값을 외부의 XML 파일로 저장해 두고 자동으로 비교해 본다.
  - 중복 insert 가 발생 한 경우 예상 한 Exception 이 발생하는 지 확인한다.

unitils.properties 파일로, 여러 개의 unitils 관련 파일을 관리하고 싶을 경우 아래와 같이 해당 위치를 선언해두면 된다.
물론 동시에는 하나의 설정 값만 인식된다. 

==== 환경 구성 ====

  * [[unitils.properties]]
  * [[unitils-local-hsqldb.properties]]


==== 테스트 데이터 ====

  * 테이블 자동 생성을 위한 DML : [[001_initial.sql]]
  * 데이터 자동 입력을 위한 XML : [[AutoInsertionTestDataTest_DataSet.xml]]
  * 조회 결과와 자동으로 비교할 XML : [[AutoVerifyTestResultsTest_ExpectedDataSet.xml]]

==== 테스트 프로그램 ====

  * NoticeDao.java 에 대한 단위 테스트 프로그램 : [[NoticeDaoTest.java]]

==== 테스트 수행 결과 ====

=== 콘솔 로그 ===
데이터 소스 생성하는 모습
<code:text>
2009. 4. 28 오후 2:29:33 org.unitils.database.config.PropertiesDataSourceFactory createDataSource
정보: Creating data source. Driver: org.hsqldb.jdbcDriver, url: jdbc:hsqldb:sampledb, user: sa, password: <not shown>
</code>
데이터베이스 스키마 업데이트 모습
<code:text>
2009. 4. 28 오후 2:29:33 org.unitils.database.DatabaseModule updateDatabase
정보: Checking if database has to be updated.
2009. 4. 28 오후 2:29:34 org.unitils.dbmaintainer.DBMaintainer updateDatabase
정보: Database is up to date
</code>
Spring Context Loading 모습
<code:text>
2009. 4. 28 오후 2:29:34 org.springframework.context.support.AbstractApplicationContext prepareRefresh
정보: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@17a29a1: display name [org.springframework.context.support.ClassPathXmlApplicationContext@17a29a1]; startup date [Tue Apr 28 14:29:34 KST 2009]; root of context hierarchy
2009. 4. 28 오후 2:29:35 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [META-INF/persistence/connection/datasource-spring-with-unitils.xml]
2009. 4. 28 오후 2:29:35 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [META-INF/spring/context-common.xml]
2009. 4. 28 오후 2:29:37 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
정보: Loading XML bean definitions from class path resource [META-INF/spring/context-sqlmap.xml]
2009. 4. 28 오후 2:29:37 org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory
정보: Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@17a29a1]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1a7508a
2009. 4. 28 오후 2:29:37 org.springframework.core.io.support.PropertiesLoaderSupport loadProperties
정보: Loading properties file from class path resource [META-INF/spring/jdbc.properties]
2009. 4. 28 오후 2:29:37 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
정보: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1a7508a: defining beans [dataSource,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,noticeDao,noticeService,noticeVo,emailController,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,sqlMapClient]; root of factory hierarchy
</code>
XML 로 지정한 입력 DataSet 을 읽어오는 부분. 위 샘플에서는 클래스 상위에 선언을 했기 때문에 각 테스트 메소드 실행 전마다 호출되고 있음
<code:text>
2009. 4. 28 오후 2:29:37 org.unitils.dbunit.DbUnitModule getDataSet
정보: Loading DbUnit data set. File names: [D:\_dev\egov_dev\workspace\egovframework.guideprogram.testcase\target\test-classes\META-INF\persistence\testdata\AutoInsertionTestDataTest_DataSet.xml]
2009. 4. 28 오후 2:29:38 org.unitils.dbunit.DbUnitModule getDataSet
정보: Loading DbUnit data set. File names: [D:\_dev\egov_dev\workspace\egovframework.guideprogram.testcase\target\test-classes\META-INF\persistence\testdata\AutoInsertionTestDataTest_DataSet.xml]
2009. 4. 28 오후 2:29:38 org.unitils.dbunit.DbUnitModule getDataSet
정보: Loading DbUnit data set. File names: [D:\_dev\egov_dev\workspace\egovframework.guideprogram.testcase\target\test-classes\META-INF\persistence\testdata\AutoInsertionTestDataTest_DataSet.xml]
2009. 4. 28 오후 2:29:38 org.unitils.dbunit.DbUnitModule getDataSet
정보: Loading DbUnit data set. File names: [D:\_dev\egov_dev\workspace\egovframework.guideprogram.testcase\target\test-classes\META-INF\persistence\testdata\AutoVerifyTestResultsTest_ExpectedDataSet.xml]
2009. 4. 28 오후 2:29:38 org.unitils.dbunit.DbUnitModule getDataSet
정보: Loading DbUnit data set. File names: [D:\_dev\egov_dev\workspace\egovframework.guideprogram.testcase\target\test-classes\META-INF\persistence\testdata\AutoInsertionTestDataTest_DataSet.xml]
</code>

=== JUnit View ===
{{:egovframework:dev:tst:junit_7.daotest_noticedao.jpg?400x300|}}

===== 참고자료 =====

[[http://www.junit.org/]] \\
[[http://www.dbunit.org/]] \\
[[http://www.unitils.org]]