2012-05-17
Maven で JUnit4 の機能を使った単体テストと結合テスト
結合テストは、以下の方法が一番簡単だが、integration-test フェーズで結合テストが実行されないのが何となく気持ち悪かった。
http://d.hatena.ne.jp/okinaka/20120413/1334292605
以下の記事を参考に、integration-test フェーズにて結合テストが実行できることが確認できた。
http://johndobie.blogspot.co.uk/2012/04/unit-and-integration-tests-with-maven.html
記事中の pom.xml やソースの記述に不備があり、やや手間取った。一度手順を整理しようと思う。
サンプルコード
svn co https://designbycontract.googlecode.com/svn/trunk/examples/maven/categories cd categories mvn integration-test
JUnit4 の @Category アノテーション
JUnit4 の @Category アノテーションを使うと、テスト実行時に対象を指定することが出来るとのこと。
http://kentbeck.github.com/junit/javadoc/latest/org/junit/experimental/categories/Category.html
src/test/java/IntegrationTest.java:
public @interface IntegrationTest {}
@Category をテストケースのクラスに設定。
import org.junit.Test; import org.junit.experimental.categories.Category; @Category(IntegrationTest.class) public class ExampleIntegrationTest{ @Test public void longRunningServiceTest() throws Exception { } }
maven の設定
pom.xml:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.11</version> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> <version>2.12</version> </dependency> </dependencies> <configuration> <includes> <include>**/*.class</include> </includes> <excludedGroups>IntegrationTest</excludedGroups> </configuration> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>2.12</version> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> <version>2.12</version> </dependency> </dependencies> <configuration> <groups>IntegrationTest</groups> </configuration> <executions> <execution> <goals> <goal>integration-test</goal> </goals> <configuration> <includes> <include>**/*.class</include> </includes> </configuration> </execution> </executions> </plugin>
ここで、
<groups>IntegrationTest</groups>
の部分が、上記で作成したアノテーション。
maven で JUnit のカテゴリー機能を利用するためには、
<dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-junit47</artifactId> <version>2.12</version> </dependency> </dependencies>
という設定を追加するのがコツとのこと。
maven 実行
mvn integration-test
2012-04-25
sql-maven-plugin で dbdeploy 用管理テーブルを作成
dbdeploy 用の管理テーブル (changelog) を作成する方法として、sql-maven-plugin を利用してみた。
http://mojo.codehaus.org/sql-maven-plugin/
PostgreSQL 用のテーブル定義 (src/main/sql/0000_dbdeploy_changelog.sql) は、
CREATE TABLE IF NOT EXISTS changelog ( change_number BIGINT NOT NULL, complete_dt TIMESTAMP NOT NULL, applied_by TEXT NOT NULL, description TEXT NOT NULL, PRIMARY KEY (change_number) );
pom.xml は、
<project> ... <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>sql-maven-plugin</artifactId> <version>1.5</version> <dependencies> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> </dependency> </dependencies> <configuration> <driver>org.postgresql.Driver</driver> <url>jdbc:postgresql://localhost/testdb</url> <username>username</username> <password>password</password> <srcFiles> <srcFile>${basedir}/src/main/sql/0000_dbdeploy_changelog.sql</srcFile> </srcFiles> </configuration> </plugin> </plugins> </build> ... </project>
あとは、コマンドライン上から、
mvn sql:execute
とすればSQLが実行された。すばらしい!
だが、pom.xml に必要なものをどんどん追加していったら、冗長になってきた。シンプルにする方法はないものか。
2012-04-23
継続的デリバリーの一歩: dbdeploy を使ってみた
dbdeploy とは、java 製のデータベース変更管理ツール。コマンドラインツールとしても使えるが、主な用途は ant タスクや maven プラグインとして利用すること。わりと歴史があるツールみたい。
PostgreSQL の createSchemaVersionTable が用意されていないので hsql 版を流用した。
maven のゴールでは changelog テーブルを作るところまではやってくれないのか。(sql-maven-plugin を使う?)また、undo するには工夫が必要っぽい。とりあえず、mvn dbdeploy:update で更新されることは確認できたので、また時間をおいて調査しようと思う。
maven プラグインの pom.xml の例 (PostgreSQL)
<project> ... <build> <plugins> <plugin> <groupId>com.dbdeploy</groupId> <artifactId>maven-dbdeploy-plugin</artifactId> <version>3.0M3</version> <configuration> <scriptdirectory>src/main/sql</scriptdirectory> <driver>org.postgresql.Driver</driver> <url>jdbc:postgresql://localhost/testdb</url> <userid>user</userid> <password>pass</password> <dbms>pgsql</dbms> </configuration> <dependencies> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.1-901.jdbc4</version> </dependency> </dependencies> </plugin> </plugins> </build> ... </project>
maven プラグインのゴール一覧
http://code.google.com/p/dbdeploy/wiki/UsingTheMavenPlugin
より、ヘルプ参照とのこと。今のところ、以下の3つが用意されている。
| ゴール | 説明 |
|---|---|
| change-script | タイムスタンプ付きの空ファイルを生成するゴール |
| db-scripts | 更新用のSQLファイルを生成するゴール(別途SQL生成用のテンプレートが必要?) |
| update | DB対して直接変更するゴール(apply のみで undo は不可?) |
dbdeploy:change-script
Description: Maven goal for creating a new timestamped dbdeploy change
script.
Implementation: com.dbdeploy.mojo.CreateChangeScriptMojo
Language: java
Available parameters:
name (Default: new_change_script)
Name suffix for the file that will be created (e.g.
add_email_to_user_table).
scriptdirectory (Default: ${project.src.directory}/main/sql)
Directory where change scripts reside.
dbdeploy:db-scripts
Description: Maven goal for creating the apply and undo scripts.
Implementation: com.dbdeploy.mojo.CreateDatabaseScriptsMojo
Language: java
Available parameters:
changeLogTableName
The name of the changelog table to use. Useful if you need to separate
DDL and DML when deploying to replicated environments. If not supplied
defaults to 'changelog'
dbms
String representing our DBMS (e.g. mysql, ora)
delimiter
Delimiter to use to separate scripts into statements, if dbdeploy will
apply the scripts for you i.e. you haven't specified outputfile. Default
;
delimiterType
Either normal: split on delimiter wherever it occurs or row only split on
delimiter if it features on a line by itself. Default normal.
driver
Specifies the jdbc driver.
encoding (Default: ${project.build.sourceEncoding})
Encoding to use for change scripts and output files.
lastChangeToApply
The highest numbered delta script to apply.
lineEnding
Line ending to separate indiviual statement lines when applying directly
to the database. Can be platform (the default line ending for the current
platform), cr, crlf or lf. Default platform.
outputfile
The name of the script that dbdeploy will output. Include a full or
relative path.
password
The password of the dbms user who has permissions to select from the
schema version table.
scriptdirectory (Default: ${project.src.directory}/main/sql)
Full or relative path to the directory containing the delta scripts.
templateDirectory
Directory for your template scripts, if not using built-in
undoOutputfile
The name of the undo script that dbdeploy will output. Include a full or
relative path.
url
Specifies the url of the database that the deltas are to be applied to.
userid
The ID of a dbms user who has permissions to select from the schema
version table.
dbdeploy:update
Description: Maven goal for applying dbdeploy change scripts directly to
the database.
Implementation: com.dbdeploy.mojo.UpdateDatabaseMojo
Language: java
Available parameters:
changeLogTableName
The name of the changelog table to use. Useful if you need to separate
DDL and DML when deploying to replicated environments. If not supplied
defaults to 'changelog'
delimiter
Delimiter to use to separate scripts into statements, if dbdeploy will
apply the scripts for you i.e. you haven't specified outputfile. Default
;
delimiterType
Either normal: split on delimiter wherever it occurs or row only split on
delimiter if it features on a line by itself. Default normal.
driver
Specifies the jdbc driver.
encoding (Default: ${project.build.sourceEncoding})
Encoding to use for change scripts and output files.
lastChangeToApply
The highest numbered delta script to apply.
lineEnding
Line ending to separate indiviual statement lines when applying directly
to the database. Can be platform (the default line ending for the current
platform), cr, crlf or lf. Default platform.
password
The password of the dbms user who has permissions to select from the
schema version table.
scriptdirectory (Default: ${project.src.directory}/main/sql)
Full or relative path to the directory containing the delta scripts.
url
Specifies the url of the database that the deltas are to be applied to.
userid
The ID of a dbms user who has permissions to select from the schema
version table.
2012-04-13
継続的デリバリーへの一歩:自動受け入れテスト(準備編)
自動受け入れテストは、コミットテストが成功して初めて実施されるものであるため、実行タイミングを分けてやる必要がある。ここでは、Java での開発を前提とし、自動受け入れテストは JUnit を使用するものとする。
ググってみたところ、 maven で単体テストと結合テストを分離するための超簡易な方法が紹介されていた。
http://stackoverflow.com/questions/1399240/how-do-i-get-my-maven-integration-tests-to-run
要は、テスト対象のクラス名を指定することで単体テストかどうかを分離している。
# 単体テスト (通常通り) mvn test # 結合テスト (オプション付き) mvn test -Dtest=**/*Integration
単体テストは末尾が Test のクラスが実行され、結合テストは末尾が Integration のクラスが実行される。
つまり、JUnit を利用するうえでは、テストの種類別にクラス名を分ければいいということになる。
多少問題があるが、単体テストと結合テストの実行タイミングを分離するという目的は達成できるので、とりあえずこの方法を採用してみる。
2012-04-08
JUnit で JNDI DataSource を使う
サーバーサイドの場合、データベースのコネクションを取得するために DataSource を利用するのが一般的。JUnit を実行するときには、サーブレットコンテナなどは動作していないため、事前にJNDIの準備が必要。
JUnit JNDI DataSource helper package というものが昔からある。テスト目的なら十分な気もするが、いまいちな感じがして利用せず。
Tomcat の JNDI 実装を利用する方が良さそう。
- http://d.hatena.ne.jp/ellectra/20110719/1311040467
- https://blogs.oracle.com/randystuph/entry/injecting_jndi_datasources_for_junit
上記の例は、データベースが Oracle だが、今回のサンプルでは PostgreSQL を使用している。
まだまだ、単体テスト作成になれてないところがあるが、ぼちぼち作成していこうと思う。
import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; import junit.framework.TestCase; import org.postgresql.ds.PGSimpleDataSource; public class ConnectionTest extends TestCase { protected void setUp() { try { System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory"); System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming"); InitialContext ic = new InitialContext(); ic.createSubcontext("java:"); ic.createSubcontext("java:comp"); ic.createSubcontext("java:comp/env"); ic.createSubcontext("java:comp/env/jdbc"); PGSimpleDataSource ds = new PGSimpleDataSource(); ds.setUser("dbuser"); ds.setPassword("dbpass"); ds.setDatabaseName("dbname"); ds.setServerName("localhost"); ds.setPortNumber(5432); ic.bind("java:comp/env/jdbc/database", ds); } catch (NamingException ex) { ex.printStackTrace(); } } public void testGetConnection() { try{ Context context = new InitialContext(); DataSource ds = (DataSource)context.lookup("java:comp/env/jdbc/database"); java.sql.Connection c = ds.getConnection(); assertNotNull(c); } catch (java.sql.SQLException se) { se.printStackTrace(); } catch (NamingException e) { e.printStackTrace(); } } }