Hatena::ブログ(Diary)

エンジニアきまぐれTips

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>

の部分が、上記で作成したアノテーション

mavenJUnitカテゴリー機能を利用するためには、

<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 プラグインとして利用すること。わりと歴史があるツールみたい。

http://dbdeploy.com/

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生成用のテンプレートが必要?)
updateDB対して直接変更するゴール(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 実装を利用する方が良さそう。

上記の例は、データベースが 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();
        }
    }
}