주요글: 도커 시작하기
반응형

테스트를 작성하다보면 DB와 연동되는 부분을 테스트해야 할 때가 있다. 특히 UI 레이어를 제외한 나머지 레이어(어플리케이션, 도메인, 인프라)에 대한 통합 테스트를 진행하려면 DB 데이터 사용은 필수적이다. 그런데, DB와의 연동 테스트를 작성하다보면 귀찮은 작업이 있는데, 그것은 바로 데이터의 초기화작업이다.


JUnit을 이용해서 작성한 테스트를 실행할 때 마다 DB에 삽입되어 있는 데이터가 다르다면, 테스트를 실행할 때 마다 DB에 포함되어 있는 데이터에 따라 테스트 코드를 변경해야 하는 귀찮은 상황이 발생하기도 한다. 이렇게 되면 자동화된 테스트는 물건너 가버린다. 따라서, DB까지 모두 사용하는 테스트를 작성하려면 테스트를 수행하기 전에 DB 상태를 일정하게 만들어주는 기능이 필요하다. 본 글에서는 DbUnit을 이용해서 이런 상황을 처리하는 방법을 살펴보고자 한다.


본 글에서는 DbUnit 자체에 대한 내용은 자세하게 다루지 않으며, DbUnit을 이용해서 테스트를 실행할 때 DB 데이터를 초기화하는 방법만을 살펴볼 것이다. 데이터 초기화 이외에도 DbUnit을 사용하면 테스트 후에 DB 상태가 원하는 결과로 들어가 있는지 확인하는 기능도 제공하는데, 이에 대한 내용이 궁금하다면 http://www.dbunit.org/howto.html#assertdata 를 방문해보기 바란다.


DbUnit을 이용한 DB 데이터 초기화


DbUnit을 이용해서 DB 데이터를 초기화하려면 다음의 두 가지 작업만 하면 된다.


  • 초기 데이터를 정의하고 있는 XML 파일을 작성한다.
  • XML 파일로부터 데이터를 읽어와 DB를 초기화한다.

초기 데이터를 정의한 XML 파일 작성


매번 동일한 상태로 DB를 만들기 위해 사용되는 XML 파일을 만드는 방법은 간단하다. 데이터를 초기화할 테이블, 테이블 컬럼 목록, 그리고 사용할 초기 데이터를 지정해주기만 하면 된다. 다음은 XML 파일의 작성 예이다.


<?xml version="1.0" encoding="UTF-8" ?>


<dataset>

    <table name="IDREPO">

        <column>ENTITY</column>

        <column>NEXTVAL</column>

        <row>

            <value>USER</value>

            <value>1000</value>

        </row>

        <row>

            <value>DEAL</value>

            <value>1000</value>

        </row>

    </table>


    <table name="USER">

        <column>USER_ID</column>

        <column>EMAIL</column>

        <column>ENC_PASSWORD</column>

        <column>NAME</column>

        <column>EMAIL_AUTH_YN</column>

        <column>AUTH_KEY</column>

        <row>

            <value>1</value>

            <value>madvirus@madvirus.net</value>

            <value>[enc]password</value>

            <value>최범균</value>

            <value>Y</value>

            <value>testauthkey</value>

        </row>

    </table>

</dataset>


위 파일에서 각 태그틑 다음의 의미를 갖는다.

  • table: 테이블을 의미한다. name 속성으로 초기화할 테이블 이름을 지정한다.
    • column: 데이터 초기화시에 사용할 컬럼 목록을 지정한다.
    • row: 한 개의 레코드를 의미한다.
      • value: 해당하는 컬럼에 삽입될 값을 지정한다. 동일한 순서의 column 태그와 쌍을 갖는다.
예를 들어, 위 설정의 경우  IDREPO 테이블에 두 개의 레코드를 삽입하는데, 첫 번째 레코드는 ENTITY 컬럼과 NEXTVAL 컬럼의 값으로 각각 USER와 1000을 사용한다. 비슷하게 USER 테이블에도 USER_ID 컬럼 값이 1이고 EMAIL 컬럼 값이 madvirus@madvirus.net인 한 개의 레코드를 삽입한다.


XML 파일로부터 DB 데이터 초기화하기


알맞게 XML 파일을 작성했다면, 그 다음으로 할 작업은 이 XML 파일을 이용해서 DB를 초기화하는 것이다. 이를 위해 작성한 코드는 다음과 같다.


-- SeedDataLoader 클래스


import java.io.FileNotFoundException;

import java.io.FileReader;

import java.sql.SQLException;


import org.dbunit.database.IDatabaseConnection;

import org.dbunit.dataset.DataSetException;

import org.dbunit.dataset.IDataSet;

import org.dbunit.dataset.xml.XmlDataSet;

import org.dbunit.operation.DatabaseOperation;


public class SeedDataLoader {


    public static void loadIntegrationTestDataSeed() {

        loadData("src/test/resources/inttest-seed-data.xml");

    }


    public static void loadData(String seedFile) {

        IDatabaseConnection conn = null;

        try {

            conn = DbUnitConnectionUtil.getConnection();

            IDataSet data = createDataSet(seedFile);

            DatabaseOperation.CLEAN_INSERT.execute(conn, data);

        } catch (Throwable e) {

            throw new RuntimeException(e);

        } finally {

            close(conn);

        }

    }1


    private static IDataSet createDataSet(String seedFile)

            throws DataSetException, FileNotFoundException {

        return new XmlDataSet(new FileReader(seedFile));

    }


    private static void close(IDatabaseConnection conn) {

        if (conn != null) {

            try {

                conn.close();

            } catch (SQLException e) {

            }

        }

    }

}


-- DbUnitConnectionUtil 클래스

public class DbUnitConnectionUtil {


    public static IDatabaseConnection getConnection()

            throws ClassNotFoundException, SQLException, DatabaseUnitException {

        Class.forName(JdbcConstants.DRIVER);

        return new DatabaseConnection(DriverManager.getConnection(

                JdbcConstants.JDBCURL, JdbcConstants.USER,

                JdbcConstants.PASSWORD));

    }


}


위 코드에서 핵심 부분은 다음의 두 부분이다.


- XmlDataSet을 이용해서 초기화할 데이터 집합을 생성하는 부분

- DataOperations.CLEAN_INSERT.execute()를 이용해서 데이터를 초기화하는 부분


XmlDataSet은 앞서 작성했던 XML을 이용해서 데이터 집합을 생성할 때 사용된다. 이렇게 생성한 데이터 집합을 테스트 DB에 반영하려면 DataOperations가 제공하는 CLEAN_INSERT를 사용하면 된다. CLEAN_INSERT는 전달받은 데이터집합과 관련된 모든 테이블의 데이터를 삭제한 뒤에, 데이터를 추가한다. 이 작업을 테스트 전에 수행하면 모든 데이터를 지우고 설정된 데이터 집합을 삽입하기 때문에, 항상 동일한 상태로 테스트를 실행할 수 있게 된다.


테스트 코드에서 사용하기


실제 테스트 코드에서 사용하려면 다음과 같이 테스트를 수행하기 전에 DB를 초기화해주면 된다.


@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = { ApplicationContextConfig.class })

public class IntTestBase {


    @BeforeClass

    public static void setup() {

        SeedDataLoader.loadIntegrationTestDataSeed();

    }

}



public class DealRepositoryIntTest extends IntTestBase {


    @Autowired

    private DealRepository dealRepository;


    @Test

    public void shouldReturnDealsWhichIdIsLessThanThreeByUsingSpecAndPageable() {

        Specification<Deal> specs = DealSpecs.beforeDeal(4L);

        Pageable pageable = createPageable();

        List<Deal> deals = dealRepository.findBySpecification(specs, pageable);

        assertEquals(2, deals.size());

        assertEquals(3L, getIdOfIdexedDeal(deals, 0));

        assertEquals(2L, getIdOfIdexedDeal(deals, 1));

    }

    ...

}


필자의 경우 동일한 데이터 집합을 사용하는 테스트 기반 클래스(IntTestBase)를 만들고, 이 기반 클래스를 상속받은 클래스에서 DB를 사용하는 테스트를 수행하도록 만든다. @BeforeClass를 사용해서 DB를 초기화하므로, 테스트에 포함된 모든 테스트 메서드를 실행하기 전에 한 번 DB를 초기화한다. 만약 테스트 메서드마다 초기화를 해 주어야 한다면, @Before를 이용해서 초기화 작업을 실행해주면 된다.


앞서 작성한 SeedDataLoader.loadData() 메서드를 사용하면 테스트 클래스마다 서로 다른 데이터 셋을 사용하도록 설정할 수도 있으므로, 상황에 따라 기반 클래스를 상속받지 않고 직접 초기화 작업을 수행해도 된다.


XML 이외도 CVS 포맷 등을 이용해서 데이터 집합을 만들 수도 있는데, 이에 대한 내용이 궁금하다면, http://www.dbunit.org/ 사이트에서 제공하는 문서를 참고하면 된다.


+ Recent posts