Java + Spring によるテストデータ管理(2)

(前回の続き)

Springを使ったDBアクセスに関するテスト対象クラスとテストクラスがどういったものになるかを確認する。

まず、テスト対象クラスの仕様を考える

一般的なビジネスロジックのようにテーブル、データは当然事前に存在する前提とする。

そうすると、テスト対象クラスの最もシンプルな仕様はテーブル sample3 から取得したデータを Map の List  で返すだけである。

```java:src/main/java/jp/sample/JavaDBSample3.java
package jp.sample;
 
import java.util.List;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
 
@Component
public class JavaDBSample3 {
  @Autowired
  JdbcTemplate jdbcTemplate;
   
  @Transactional
  public List<Map<String,Object>> select() {
      return jdbcTemplate.queryForList("select * from sample3");
  }
}
```

テストではこのクラスが期待した動作をするか確認するために

* テーブルを作成
* データを登録
* テスト対象クラスのインスタンスを作成
* メソッドを実行
* 結果を検証

する流れとなる

以下、まだフレームワークの動作に自信がないので検証はダミー(`assertTrue(true)`)である。

ここで、確認したいのは @Autowired された jdbcTemplate は、テストとテスト対象クラスで共有されているかどうかである

```java:src/test/java/jp/sample/JavaDBSample3Test.java 
package jp.sample;

import java.util.List;
import java.util.Map;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.ContextConfiguration;
import org.junit.runner.RunWith;

import org.junit.*;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class JavaDBSample3Test {

    @Autowired
    JdbcTemplate jdbcTemplate;

    JavaDBSample3 obj;

    @Before
    public void setUp() {
        obj = new JavaDBSample3();
    }

    @Test
    public void test() {
        jdbcTemplate.execute("create table sample3 (id numeric(3), str varchar(5))");
        jdbcTemplate.execute("insert into sample3 values (3, 'c')");

        List<Map<String,Object>> list = obj.select();

        assertTrue(true);
    }
}
```

テストを実行してみる(くれぐれも、/tmp/javadb を一旦削除して試すこと。現状、sample3のdrop tableをしていないので2度実行すると必ず失敗するテストになっている)

```
rm -rf /tmp/javadb/
gradle check
```

テスト実行結果は以下

```
jp.sample.JavaDBSample3Test > test FAILED
    java.lang.NullPointerException at JavaDBSample3Test.java:33
```

だめだった。Spring は黒魔術ではないので書いてないことはやってくれない 。上記の場合、obj.jdbcTemplate に値を設定するタイミングがないため、NullPointerExceptionで落ちている。

この場合

```java
obj.jdbcTemplate = jdbcTemplate;
```

をsetUp() に差し込めばテストは通るがそんな対処は間違っていると直感が言っている。

もとのサンプルを振り替えると

```java
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

obj = context.getBean(JavaDBSample3.class);
```

としたcontextが必要である。しかし、これをそのままsetUp()に書くのもapplicationContext.xml を2回参照する点で間違っていることは明らかである。
どこかしらで context は生成されており、それを取得する方法があるはずだ。調べたところ

```java
import org.springframework.context.ApplicationContext;

...

@Autowired
ApplicationContext context;
```

をテストクラスのフィールドに追加するだけだった。さらには、本当は context  も必要なくて、

```java
@Autowired
JavaDBSample3 obj;
```

っと、obj の生成自体を Spring に任せれば良かっただけであった。
これで、テストクラスは以下のようになる。上記に加えて
* 事前にテーブルをdropする
* 結果の検証を行う
も追加した。テストクラスとして体裁は整った。

```java
package jp.sample;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.ContextConfiguration;

import org.junit.runner.RunWith;
import org.junit.*;

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class JavaDBSample3Test {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    JavaDBSample3 obj;

    @Before
    public void setUp() {
        dropTable("sample3");
    }

    @Test
    public void test() {
        jdbcTemplate.execute("create table sample3 (id numeric(3), str varchar(5))");
        jdbcTemplate.execute("insert into sample3 values (3, 'c')");

        List<Map<String,Object>> list = obj.select();

        assertThat(list.size(), is(1));
        assertThat((BigDecimal)list.get(0).get("ID"), is(BigDecimal.valueOf(3)));
        assertThat((String)list.get(0).get("STR"), is("c"));
    }

    public void dropTable(String table) {
        try {
            jdbcTemplate.execute("drop table " + table);
        }
        catch (DataAccessException e) {
            // ignore
        }
    }
}
```

一度にいろいろ修正しすぎたのでどこを書き換えたのかわかるよう diff 形式の差分も以下に示す。

```diff
@@ -1,9 +1,11 @@
 package jp.sample;
 
+import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 import org.springframework.test.context.ContextConfiguration;
@@ -12,6 +14,8 @@
 import org.junit.*;
 
 import static org.junit.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
+
 @RunWith(SpringJUnit4ClassRunner.class)
 @ContextConfiguration(locations="classpath:applicationContext.xml")
 public class JavaDBSample3Test {
@@ -19,11 +23,12 @@
     @Autowired
     JdbcTemplate jdbcTemplate;
 
+    @Autowired
     JavaDBSample3 obj;
 
     @Before
     public void setUp() {
-        obj = new JavaDBSample3();
+        dropTable("sample3");
     }
 
     @Test
@@ -33,6 +38,17 @@
 
         List<Map<String,Object>> list = obj.select();
 
-        assertTrue(true);
+        assertThat(list.size(), is(1));
+        assertThat((BigDecimal)list.get(0).get("ID"), is(BigDecimal.valueOf(3)));
+        assertThat((String)list.get(0).get("STR"), is("c"));
+    }
+
+    public void dropTable(String table) {
+        try {
+            jdbcTemplate.execute("drop table " + table);
+        }
+        catch (DataAccessException e) {
+            // ignore
+        }
     }
 }
```

Springに関するなんとなくな理解が少し深まった気がする。
次回以降は、効率よくという点を追求していきたい。