Java + Spring でテストデータを効率よく管理する方式を模索する
この記事はゆっくり完成させるつもりでいる。
最終目標
- テスト対象はDBアクセス用のORマッパ(主にSQL)とする
- テスト実行の流れは以下の通りとする
- JUnitでテスト用データを投入する
- ORマッパがクエリを実行し結果をbeanで返す
- JUnitのAssertで結果を検証する
- テストデータはテストメソッドと対でフォルダ内で管理する
- テストデータはExcelかCSVで作成する
- テーブル作成スクリプトも一緒に管理し、設計書管理による乖離を解消する
以下のサンプルでは段階的に利用するパッケージを増やし、各パッケージの理解を深める。そうすることで利用するパッケージの差し替えを可能にすることを狙っている。
はじめに
DB を用意する。本記事ではどの環境でも動くことを前提とするためにJava DBを使ってサンプルを作成する
import java.sql.*;
class JavaDBSample {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection(
"jdbc:derby:/tmp/javadb/sample;create=true");
conn.close();
}
}
以下により、/tmp/javadb/sample ディレクトリが作成されその配下にDB関連のファイルができる
javac JavaDBSample.java
java -classpath "$JAVA_HOME/db/lib/derby.jar;." JavaDBSample
SQL発行までを試す
import java.sql.*;
class JavaDBSample {
public static void main(String[] args) throws SQLException {
Connection conn = DriverManager.getConnection(
"jdbc:derby:/tmp/javadb/sample;create=true");
try {
drop(conn, "sample");
Statement stat = conn.createStatement();
stat.execute("create table sample (id numeric(3), str varchar(5))");
stat.close();
conn.setAutoCommit(false);
stat = conn.createStatement();
stat.execute("insert into sample values (1, 'a')");
stat.close();
stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select * from sample");
while (rs.next()) {
System.out.println(rs.getInt("ID") + ", " + rs.getString("STR"));
}
rs.close();
stat.close();
conn.commit();
conn.close();
}
finally {
try {
shutdown();
}
catch (SQLException e) {
// ignore
}
}
}
public static void drop(Connection conn, String table) {
try {
Statement stat = conn.createStatement();
stat.execute("drop table " + table);
stat.close();
}
catch (SQLException e) {
// ignore
}
}
public static void shutdown() throws SQLException {
DriverManager.getConnection("jdbc:derby:;shutdown=true");
}
}
コンパイルする
javac JavaDBSample.java
実行する
java -classpath "$JAVA_HOME/db/lib/derby.jar;." JavaDBSample
1, a
Gradle でビルドする
以降、依存ライブラリの解決に gradle を利用するので、gradleをインストールしておく
Gradle は現在 2.0 になっているが、ここでは自分の環境にインストール済みの 1.12 で試した結果を記載する
gradle -version
------------------------------------------------------------
Gradle 1.12
------------------------------------------------------------
Build time: 2014-04-29 09:24:31 UTC
Build number: none
Revision: a831fa866d46cbee94e61a09af15f9dd95987421
Groovy: 1.8.6
Ant: Apache Ant(TM) version 1.9.3 compiled on December 23 2013
Ivy: 2.2.0
JVM: 1.7.0_40 (Oracle Corporation 24.0-b56)
OS: Windows 7 6.1 x86
Gradleで先のソースをビルドするため、以下のファイルを作成する。
apply plugin: 'java'
JavaDBSample.java を以下のパスに移動する
(Gradleではソースツリーの構成が定められている)
src/main/java/JavaDBSample.java
前のサンプルで作成していた余分な .class を削除しておく
rm *.class
コンパイルする
gradle build
実行する。混乱を避けるために前に作ったDBは一旦消しておく
rm -rf /tmp/javadb/sample
ビルドされたclass ファイルは build/classes/main 配下に作成されるためクラスパスを変更していることに注意。
java -classpath "$JAVA_HOME/db/lib/derby.jar;build/classes/main" JavaDBSample
1, a
Spring を使う
必要な spring の jar は gradle にダウンロードさせる
apply plugin: 'java'
repositories {
mavenCentral()
}
ext {
springVersion = '4.0.5.RELEASE'
}
dependencies {
compile "org.springframework:spring-beans:$springVersion"
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-context-support:$springVersion"
compile "org.springframework:spring-tx:$springVersion"
compile "org.springframework:spring-jdbc:$springVersion"
}
SpringのapplicationContext.xmlを以下の通り作成する。
今度は、一般的なサンプルに合わせ、パッケージを付けることにする。
パッケージ名は、jp.sample
とした。
DBアクセス周りは慣習(?)に合わせ、META-INF/datasource-context.xml
に記述し、importしている。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="jp.sample" />
<import resource="META-INF/datasource-context.xml" />
</beans>
META-INF/datasource-context.xml
の設定値は、JavaDBを使うため、以下の通りとした。
設定項目 | 値 |
---|---|
driverClassName | org.apache.derby.jdbc.EmbeddedDriver |
url | jdbc:derby:/tmp/javadb/sample;create=true |
jdbcTemplate の databaseProductName | Derby |
databaseProductName はなくても良いかもしれない。
(databaseProductName を "Apache Derby" と書いていたが、実際は "Derby" だった。sql-error-codes.xmlを参照)
username, password は、一般的なDBに合わせ設定しているが、ここで作成しているJavaDBでは実際は関係ない。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="url" value="jdbc:derby:/tmp/javadb/sample;create=true" />
<property name="username" value="user" />
<property name="password" value="pass" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
<property name="databaseProductName" value="Apache Derby" />
</bean>
</beans>
jp.sample.JavaDBSample2 クラスを作成する
package jp.sample;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class JavaDBSample2 {
public static void main(String[] args) {
try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml")) {
JavaDBSample2 sample = context.getBean(JavaDBSample2.class);
sample.execute();
}
}
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional
public void execute() {
// do some work
}
}
コンパイルする
gradle build
class ファイルが build/classes/main/jp/sample/ に作成される
実行にあたっては、ダウンロードしたjarファイルにクラスパスを通すなど
面倒くさいので実行もgradleで行うことにする。
build.gradle を以下のとおり書き換える
なお、$JAVA_HOME/db/lib/derby.jar にクラスパスを渡すのも
面倒くさかったので、javaDB である derby.jar もダウンロード
するように変更した。
apply plugin: 'application'
mainClassName = 'jp.sample.JavaDBSample2'
repositories {
mavenCentral()
}
ext {
springVersion = '4.0.5.RELEASE'
}
dependencies {
compile "org.springframework:spring-beans:$springVersion"
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-context-support:$springVersion"
compile "org.springframework:spring-tx:$springVersion"
compile "org.springframework:spring-jdbc:$springVersion"
compile 'org.apache.derby:derby:10.14.+' // 以下の2019/5/30 追記参照
}
※ 2019/5/30 追記: javaDB(Apache Derby)のEmbeddedDriverは、直近のversion 10.15.1.3 で、derbytools.jarに移動した。またこのバージョンではJDK9を必要とする。本記事ではJDK8を対象としたいため10.14を指定することにした (Apache Derby 10.15.1.3 Release)
実行する。前に作ったDBは一旦消す
rm -rf /tmp/javadb/sample
gradle run
クエリを実行する
JavaDBSample と同じようなcreate table, insert, select をJdbcTemplateで実行する
ID は、BigDecimal になってしまった。RowMapperを使うようにしないとダメなのか?
package jp.sample;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.math.BigDecimal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class JavaDBSample2 {
public static void main(String[] args) {
try (ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml")) {
JavaDBSample2 sample = context.getBean(JavaDBSample2.class);
sample.execute();
}
}
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional
public void execute() {
jdbcTemplate.execute("create table sample2 (id numeric(3), str varchar(5))");
jdbcTemplate.execute("insert into sample2 values (2, 'b')");
List<Map<String,Object>> rows = jdbcTemplate.queryForList("select * from sample2");
for (Map row : rows) {
System.out.println((BigDecimal)row.get("ID") + ", " + (String)row.get("STR"));
}
}
}
JUnitで動作確認をする
この記事の目的はテストなので、JUnitでテストを作成し、結果の正しさ
(ここでは、レコード(2, 'b')が作られること)を確認してみる
Spring でJUnitのテストを書くには、それ用のライブラリ(spring-test)が必要になるようなので、JUnit と一緒にGradleで取ってくることにする。
@RunWithと@ContextConfigurationでspring用のjunitを作成する
また、実行は必要なくなるので、plugin は application から java に戻すことにする
apply plugin: 'java'
repositories {
mavenCentral()
}
ext {
springVersion = '4.0.5.RELEASE'
}
dependencies {
compile "org.springframework:spring-beans:$springVersion"
compile "org.springframework:spring-context:$springVersion"
compile "org.springframework:spring-context-support:$springVersion"
compile "org.springframework:spring-tx:$springVersion"
compile "org.springframework:spring-jdbc:$springVersion"
compile 'org.apache.derby:derby:10.+'
testCompile 'junit:junit:4.+'
testCompile "org.springframework:spring-test:$springVersion"
}
また、テストクラスを以下のように作成する
package jp.sample;
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 JavaDBSample2Test {
@Test
public void test() {
assertTrue(true);
}
}
gradle check
なお、コンパイルエラーではなくテストが実行され失敗した場合には、テストの結果が以下に書かれるので参照する
build/reports/tests/index.html
次に、ライブラリの不足等ないことを確認したらちゃんとテストを書いてみる