Spring FrameworkのUnitテスト実装方法 1-7.Repositoryテスト(Junit4, spring-test, DBSetup)

Spring FrameworkのUnitテスト実装方法 1-7.Repositoryテスト(Junit4, spring-test, DBSetup)

Spring FrameworkのUnitテスト実装方法 1-7.Repositoryテスト(Junit4, spring-test, DBSetup)

【サンプルソース】
TERASOLUNA Server Framework for Java (5.x) Development Guideline
サンプルソースはこちら
(これのMyBatis3を使用したパターンで作成してます。)

1-7.Repositoryテスト Junit4,Spring-test,DBSetupライブラリを使用したパターン

DBSetupライブラリはその名の通り、テスト時にデータをセットアップするためのライブラリです。
DBUnitを使用した場合と比較すると、メリットは
・Javaファイルでセットアップデータの記述ができるので、xmlファイルが不要になる。
・同じデータの繰り返していわゆるデータを「膨らます」場合、膨らませるデータの個数や設定を制御できるのでxmlファイルに同じデータをコピペしなくて済むから楽。
・セットアップをし直すかどうかの制御ができるので、必要最小限のセットアップで済みその分動作が軽くなる。
などが挙げられるでしょうか・・・?

一方で、デメリットとしては
・セットアップ用のライブラリなので、DBunitでいうところの@ExpectedDatabaseといった検証用の機能は無い(調べたらあるかもですが。)
・DBunitと比べて使っている人が少な目
といったところかも。

【pom.xml】

DBSetupライブラリが必要になります。

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.ninja-squad</groupId>
        <artifactId>DbSetup</artifactId>
        <version>2.1.0</version>
        <scope>test</scope>
   </dependency>
</dependencies>

【テスト対象クラス】

TodoRepository.java
テスト対象はupdateメソッド

public interface TodoRepository {
   Todo findOne(String todoId);

    Collection<Todo> findAll();

    void create(Todo todo);

    boolean update(Todo todo);

    void delete(Todo todo);

    long countByFinished(boolean finished);
}

【テストクラス】

TodoRepositoryTestVerDBsetup.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:META-INF/spring/test-context.xml"})
@Transactional
public class TodoRepositoryTestVerDBsetup {

    @Inject
    TodoRepository target;
    
    @Inject
    DataSource dataSource;
    
    @Inject
    JdbcTemplate jdbctemplate;
    
    private static DbSetupTracker TRACKER = new DbSetupTracker();
    
    @Before
    public void setUp() {
        //DBSetupを使う準備
        Destination dest = new DataSourceDestination(dataSource);
        //sequenceOfメソッドで囲むことによって、連続してデータに対しての処理を実行できる。
        Operation ops = Operations.sequenceOf(DbsetupOperations.INIT_TABLE,
                                                DbsetupOperations.SETUP_TABLE_A);
        DbSetup dbSetup = new DbSetup(dest, ops);
        
        //TRACKERでセットアップを制御する。
        TRACKER.launchIfNecessary(dbSetup);
    }
    
    @Test
    public void testUpdate() throws Exception{
        //テスト用のデータを作成(getTodoDataメソッドはDBからデータを取得するprivateメソッド。取得したデータを書き換えて更新する。)
        SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String todoId = "cceae402-c5b1-440f-bae2-7bee19dc17fb";
        Todo testDataTodo = getTodoData(todoId);
        testDataTodo.setFinished(true);
        
        //updateメソッドのテスト
        boolean actTodo = target.update(testDataTodo);
        
        //結果検証
        assertEquals(actTodo, true);
        
        //期待値の作成
        Todo exptodo = new Todo();
        exptodo.setTodoId("cceae402-c5b1-440f-bae2-7bee19dc17fb");
        exptodo.setTodoTitle("one");
        exptodo.setFinished(true);
        String strDate = "2017-10-01 15:39:17.888";
        Date date = sdFormat.parse(strDate);
        exptodo.setCreatedAt(date);
        
        //処理後データの取得(getTodoDataメソッドはDBからテスト後に変更されたデータを取得するprivateメソッド)
        Todo actTestDataTodo = getTodoData(todoId);
        
        //メソッド実行後テーブルデータ検証
        //date型の表示形式が異なるため、時刻文字列に変換して比較している
        assertEquals(exptodo.getTodoId(), actTestDataTodo.getTodoId());
        assertEquals(exptodo.getTodoTitle(), actTestDataTodo.getTodoTitle());
        assertEquals(exptodo.isFinished(), actTestDataTodo.isFinished());
        assertEquals(sdFormat.format(exptodo.getCreatedAt()) ,sdFormat.format(actTestDataTodo.getCreatedAt()));
        
        /*
         * TRACKERによるセットアップの制御
         * テスト対象が参照系(select)の場合・・・参照のみでありデータベースを更新していないので、データの再セットアップは不要。
         * テスト対象が更新系(insert, update, delete)の場合・・・更新されたデータベースのままだと次のテストに影響を与えるので、データのセットアップをし直す必要がある。
         * TRACKER.skipNextLaunchメソッドを実行することで、次のテスト(@Test)の前に実行される@Beforeのデータセットアップ処理をスキップすることができる。
         * 
         * 今回はupdateメソッドがテスト対象のため、TRACKER.skipNextLaunch();をコメントアウトしている。
         */
        //TRACKER.skipNextLaunch();
        
    }
}

コメントにも書いてありますが、DBSetupのポイントとしてはセットアップの制御をTRACKERで行っている点です。
DBSetupを使う上で必須というわけではないのですが、この制御によって不必要なセットアップを減らすことができます。
更新系の処理の時にのみデータの再セットアップを行って綺麗な状態で始める・・・といったことが可能です。

DbsetupOperations.java

public class DbsetupOperations {
    
    /*
     * テーブルデータを初期化するメソッド
     */
    public static final Operation INIT_TABLE =
            Operations.sequenceOf(
                    Operations.deleteAllFrom("todo"));
    
    /*
     * テーブルにデータをセットアップするメソッド(パターンA)
     */
    public static final Operation SETUP_TABLE_A =
            Operations.sequenceOf(
                    Operations.insertInto("todo")
                        .columns("todo_id","todo_title","finished","created_at")
                        .values("cceae402-c5b1-440f-bae2-7bee19dc17fb","one",false,"2017-10-01 15:39:17.888")
                        .values("5dd4ba78-ff5b-423b-aa2a-a07118aeaf90","two",false,"2017-10-01 15:39:19.981")
                        .values("e3bdb9af-3dde-40b7-b5fb-4b388567ab45","three",false,"2017-10-01 15:39:28.437")
                        .build()
            );
    
    /*
     * テーブルにデータをセットアップするメソッド(パターンB)
     * パターンAとの違いは1行ずつカラムに対してデータを指定している点。
     */
    public static final Operation SETUP_TABLE_B =
            Operations.sequenceOf(
                    Operations.insertInto("todo")
                    //row0
                    .row()
                        .column("todo_id", "cceae402-c5b1-440f-bae2-7bee19dc17fb")
                        .column("todo_title", "one")
                        .column("finished", false)
                        .column("created_at", "2017-10-01 15:39:17.888")
                        .end()
                    //row1
                    .row()
                        .column("todo_id", "5dd4ba78-ff5b-423b-aa2a-a07118aeaf90")
                        .column("todo_title", "two")
                        .column("finished", false)
                        .column("created_at", "2017-10-01 15:39:19.981")
                        .end()
                    //row2
                    .row()
                        .column("todo_id", "e3bdb9af-3dde-40b7-b5fb-4b388567ab45")
                        .column("todo_title", "three")
                        .column("finished", false)
                        .column("created_at", "2017-10-01 15:39:28.437")
                        .end()
                    .build()
            );
}

データセットアップ用のクラスです。
これ以外にデータの膨らまし用に使える処理とかもあります。
詳しくはDBSetupの本家HPに。。。

【その他設定】

test-context.xml
JdbcTemplateを使えるように設定します。(データ検証用のため)
基本はapplicationContext.xmlの設定を丸パクリして、不必要なものを削った感じです。

<!-- jdbcTemplateの設定 -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
    <constructor-arg ref="dataSource" />
</bean>

xmlとかもう見るだけで虫唾が走って無理だわ!!という方にはおすすめかもしれませんw
何より「流れるようなインタフェース」で作成するというコンセプトがカッコいいし便利だし楽です。

サンプルソースはgithubで公開してます。
Spring Unit Test pattern7

DBsetupカテゴリの最新記事