Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

DbUnitNG0.5リリース!

DbUnitNGのバージョン0.5をリリースしました!
ダウンロード - DbUnitNG - SourceForge.JP
アサートメソッドの呼び出しがやや煩雑な感じで、ThreadLocalを使ったらどうかという提案(とパッチ)をもらったので、
導入したいと思います。
これでアサートの引数とか減らせるはずです。


この導入を最後にベータにバージョンを移して、これからの自分のプロジェクトでも使えるようにしっかりテストしてきたいと思います。

依存ライブラリもコミットしました

DbUnitNG プロジェクト日本語トップページ - SourceForge.JPですが、
Subversionに依存するライブラリもすべてコミットしました。
libディレクトリに入っています。
eclipseなら.projectと.classpathもコミットしているので、
それもチェックアウトしてもらうと便利です。


だいぶ面倒くささがなくなったと思うので、気軽にソースからでも使ってみてください!

@ITにTestNGの記事を連載します

というわけで、これからTestNG普及への活動をしていきます。

このBlogもそうだけど、より多くのエンジニアの目に触れるようなところでもTestNGを紹介しようと思います。

DbUnitNGの今後 - Fight the Future{|じゅくのblog|}

との宣言どおり、TestNGについて@ITさんで連載を始めました。計3回の予定です。
上司であるid:iad_otomamayHibernateやSpringの連載を@ITにしていた関係で、担当者の方を紹介してもらいました。
とにかくTestNGを普及させていきたいと思います。


今回はデファクトであるJUnitと比較をして、TestNGを理解してもらおうというコンセプトです。
JUnit4がTestNGから影響を受けている部分も多いので、使ってみるのにそれほど学習コストはかからないと思います。
始める敷居を下げて、まずは使ってもらいたいという思いです。


よかったら読んでみてください!

テストコードは違う言語がいい

個人的にはエンタープライズなシステムはJavaを始めとする静的言語が適切だと思ってるけど、テストコードまでJavaで書く必要はないんじゃないか。


テストコードというのはその対象(メソッドとか機能)の第1のクライアントだ。
つまり、使う側であって使われる側ではない。
使われる側はどんな使われ方をするのかというバリエーションを考える必要がある。
でも使う側は目的が1つではっきりしてる(その機能を呼び出すという目的)。


テストコードはパフォーマンスとかセキュリティとか諸々のことはそれほど要求されない。
だからいかに簡単に書けるか、サクッと書けるかってところがポイントだと思う。
めんどくさい、と思わずに済む言語を使う方がいい。


デメリットは2つの言語を使える必要があるってことだけ。


Javaと親和性がある言語なら、たとえばGroovy。そしてScala
Groovyはスクリプト言語だから記述量が少ないし、Scalaオブジェクト指向+関数型で後発の言語の分洗練されてる。
どちらもJVM上で実行するし、すぐに使える環境になる。


TestNGTestNG+Groovy、TestNG+Scalaといった記事がある。
Groovy - Using TestNG with Groovy
Jack Cough on Software: Using TestNG in Scala: By Example

TestNGでテストにプライオリティをつける

Otaku, Cedric's weblog: Test method priorities in TestNGTestNGの開発者であるCedricがテストメソッドにプライオリティをつける方法を紹介してます。
TestNGには豊富なリスナーインタフェースがあり、リスナーのメソッドの中でテストメソッドのアノテーションを取り出したりできるため、独自のアノテーションを作ることも容易です。
ここでは@Prioirtyっていうアノテーションを作ってます。で、IMethodInterceptorっていうリスナーを実装して、アノテーションを取り出し、プライオリティの値に従って実行順序を制御してます。
IMethodInterceptorにはメソッドが1つだけあります。

List<IMethodInstance> intercept(List<IMethodInstance> methods, ITestContext context)

戻り値のリストの順にテストを実行することになるので、intercept()メソッドで順序を制御します。つまり、プライリティに従ってテストメソッドをソートするという処理になります。
IMethodInstance#getMethod()がITestNGMethodを返し、
さらにITestNGMethod#getMethod()がjava.lang.reflect.Methodを返すので、
あとはMethodからgetAnnotation()すればアノテーションを取れます。

DbUnitNGの今後

TestNGとDbUnitNGを連携するライブラリDbUnitNGは、考えていた機能は作り終えました。
今後は、ライブラリのテストケースを増やして、バグを修正します。
多くの機能を提供するライブラリでもないので、使いやすいと思います。
提供する機能は、大きく3つです。

  1. DbUnitのSetUpとTearDownのアノテーション
  2. BeanのListをDbUnitのDataSetに変換し期待値とアサート
  3. 期待値とDBテーブルのアサートをアノテーション

これだけしかしないですけど、わりと開発で頻繁に使う部分です。


なんだけど、そもそもTestNGの知名度が低い気がする。
決して新しいプロダクトでもないのに。
TestNGというプロダクトそのものの完成度や実力と比べて、その知名度の低さは不当だと思う。
JBoss Seamは統合テストフレームワークTestNGを拡張したものを提供してる。
InfoQにもあるように、海外エンジニアはTestNGを高く評価してる。
一見JUnit4と同じに見えるかもしれないけど、多くの使える機能があるし新しいコンセプトも多い。
コミュニティも活発でMLにも多くの質問と回答が流れている。


というわけで、これからTestNG普及への活動をしていきます。
このBlogもそうだけど、より多くのエンジニアの目に触れるようなところでもTestNGを紹介しようと思います。

TestNG+DbUnitライブラリはApache License, Version 2.0に対応しました

DbUnitNG プロジェクト日本語トップページ - SourceForge.JPのライセンスをApache License, Version 2.0にきちんとしました(つもりです)。
以下のことをしました。

  • LICENSE.txtをトップディレクトリに配置する
  • ソースコードにライセンスへのリファレンスを追加する

参考

http://www.apache.org/licenses/


それに伴いバージョンを0.4.1としてリリースしてます。
ライセンスに詳しい方で何か不備があればぜひご指摘ください!

社内勉強会で発表したJUnit4&TestNGの資料を公開します

日曜日に社内勉強会をしました。
僕は株式会社クロノスに所属しているんですが、「クロノスエンジニアの会」略して「エン会」というタイトルで勉強会をしました。
今までの勉強会のスタイルと変えて、デブサミのようなイメージで社内の有志数人で、様々な内容の発表をしました。
幹事的な役割もして、セッション一覧とかもお遊びでデブサミっぽくしたりしました。


僕は「Beyond JUnit3」ということで、JUnit3の弱点と、JUnit4とTestNGの紹介や比較をソースコードを使って解説してみました。
せっかくなので、その資料とソースコードを公開します。
あんまり大したのじゃないんですけど、よかったら活用してください。


SlideShareにアップしました。
http://www.slideshare.net/jyukutyo/beyond-junit3-presentation/


ソースコードはMediaFireにアップしたので、ここからダウンロードしてください。

TestNGのテストをAntで実行する

<!-- TestNGのJARファイルに含まれるtestngtasksファイルを指定する -->
<taskdef resource="testngtasks" classpath="lib/testng-5.8-jdk15.jar" />

<!-- 実行時のクラスパスに含めるJARファイルを指定する -->
<path id="run.cp">
  <fileset dir="lib" includes="*.jar"/>
</path>

<target name="run-tests">
  <testng classpathref="run.cp" haltOnfailure="true">
    <!-- テストクラスのクラスファイルをクラスパスに含める -->
    <classpath>
      <pathelement location="bin"/>
    </classpath>
    <!-- testng.xmlを指定する -->
    <xmlfileset dir="test" includes="testng.xml" />
  </testng>
</target>

testng.xmlではなくテストクラスを実行する場合、xmlfilesetではなくclassfileset要素を使います。

<classfileset dir="${test.build.dir}" includes="**/*.class" />

DbUnitNG0.4リリース!

TestNG+DbUnitライブラリであるDbUnitNGのバージョン0.4をリリースしました。
ダウンロード - DbUnitNG - SourceForge.JP
DbUnitNGは、TestNGDbUnitを連携させ、SetUpやTearDownのアノテーション化、BeanのListをDbUnitのデータセットへ変換といったことをする小さなライブラリです。


今回の追加点は以下のとおり。

DBに接続して、任意のテーブルあるいは全テーブルをDbUnitの形式でファイルに出力するユーティリティクラスを作りました。
org.dbunitng.data.TestDataFileMakerクラスです。

初期値や期待値のファイルとしてCSVにも対応しました(他はXMLとExcel)。

期待値のファイルとデータベースのテーブルをアサートするアノテーションを作りました。
@TableAssertです。

@TableAssertに機能を追加しました。
結果をDBから取得するクエリをプロパティファイルに記述できるようにしました。

AssertionHelperクラスを使ってアサートする場合、
[null]と記述すると、nullとして扱います。


(追記)Wikiも更新しました。ここに書いたことはすべてWikiにまとめています。

DbUnitライブラリでDBデータをXML,Excel,CSVに出力するクラスを作りました

DBに接続して、任意のテーブルあるいは全テーブルをDbUnitの形式でファイルに出力するユーティリティクラスを作りました。
org.dbunitng.data.TestDataFileMakerクラスです。
使い方は簡単です。

public TestDataFileMaker(String driver, String url, String userName,
			String password, String schema) {

コンストラクタにJDBC接続に必要な情報を渡します。スキーマは特に必要なければnullを渡してください。
メソッドは2つあります。

public void extractTables(String targetDirectory, String[] tableNames,
			FileType type)
public void extractAllTables(String targetDirectory, FileType type)

特定のテーブルだけ出力したい場合extractTables()を、全テーブルを出力する場合はextractAllTables()を呼び出してください。
引数は、targetDirectoryにファイルを出力するディレクトリを渡してください。存在していない場合はディレクトリを作成して出力します。
tableNamesはテーブル名の配列です。
FileTypeはEnumで、XML、EXCEL、CSVから選択してください。この形式でファイルを出力します。
たとえば、こんなテーブルがあるとします。

mysql> select * from dept;
+--------+------------+----------+
| DEPTNO | DNAME      | LOC      |
+--------+------------+----------+
|     10 | ACCOUNTING | NEW YORK | 
|     20 | RESEARCH   | DALLAS   | 
以下省略
mysql> select * from emp;
+-------+--------+-----------+------+------------+---------+---------+--------+
| EMPNO | ENAME  | JOB       | MGR  | HIREDATE   | SAL     | COMM    | DEPTNO |
+-------+--------+-----------+------+------------+---------+---------+--------+
|  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |   10.00 |     20 | 
|  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30 | 
以下省略

こういうコードでファイルに出力できます。

TestDataFileMaker maker =
	new TestDataFileMaker(
		"com.mysql.jdbc.Driver",
		"jdbc:mysql://127.0.0.1/testframework",
		"user",
		"password",
		null);
String targetDir = "temp/xml/specified";
String[] tableNames = new String[] { "dept", "emp" };
maker.extractTables(targetDir, tableNames, FileType.XML);

するとtemp/xml/specifiedディレクトリにファイルを出力します。

temp/xml/specified
 -DEPT.xml
 -EMP.xml

内容もきちんとDbUnit形式です。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <DEPT DEPTNO="10" DNAME="ACCOUNTING" LOC="NEW YORK"/>
以下省略

同様に、Excel形式でもCSV形式でも出力できます。
CSVであれば「table-ordering.txt」も出力します。

DbUnitライブラリでCSV形式のファイルにも対応しました

TestNG+DbUnitライブラリDbUnitNGで、初期値や期待値のファイルとしてCSVにも対応しました(他はXMLとExcel)。


意外に知らないDbUnitでCSVを使う方法 - Fight the Future じゅくのblogにあるように、DbUnitではCSVファイルの場合「table-ordering.txt」というファイルが必須なので、このファイルをアノテーションに指定した場合、CSVとして処理します。
「table-ordering.txt」は定数としてDbUnitNGConstrantsにあります。
こんな感じです。

@SetUpOperation(pathname = DbUnitNGConstrants.CSV_ORDER_FILE, value = DatabaseOperationType.CLEAN_INSERT)
public void testCsvFile() {
}

pathname属性にファイル名「table-ordering.txt」だけ指定しているので、テストクラスと同じパッケージに配置します。
「table-ordering.txt」はこんな感じ。

dept
emp

テーブル名を記述します。そして、この名前に対応するCSVファイルをテキストファイルと同じパッケージに配置します。dept.csvとemp.csvです。

DEPTNO,DNAME,LOC
10,ACCOUNTING,NEW YORK
EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO
7782,CLARK,MANAGER,7839,1981-01-09,2450.0,null,10

1行目は列名、2行目以降にデータを記述します。nullの場合はそのままnullと書きます。

意外に知らないDbUnitでCSVを使う方法

dbunit csv」で検索してもヒットしなかったので書いておく。
DbUnit2.1から、初期値や期待値をCSVファイルに書くことができる。XMLやExcelの代わりに。


使い方はとても簡単。

  1. テーブル名.csvというファイルを作成する(EMPテーブルならemp.csv)
  2. CSVファイルの1行目に列名、2行目以降にデータを記述する
  3. 複数のテーブルにデータを入れたい場合、CSVファイルも複数作成する
  4. 「table-ordering.txt」というファイルを作成する
  5. txtファイルに登録するテーブルの順序を記述する(参照整合性制約に反しないように)
  6. CSVファイル、txtファイルを同じディレクトリに配置する
  7. CsvDataSetのコンストラクタにファイルを配置したディレクトリのjava.io.Fileオブジェクトを渡す


たとえば、EMPテーブルがあるとして、emp.csvはこんな感じ。

EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,DEPTNO
7782,CLARK,MANAGER,7839,1981-01-09,2450.0,10

DEPTテーブルのdept.csv。

DEPTNO,DNAME,LOC
10,ACCOUNTING,NEW YORK

EMPテーブルのDEPTNOは、DEPTテーブルを参照しているので、データを登録するときは「DEPT→EMP」の順序にしたい。
なので「table-ordering.txt」はこうなる。

dept
emp

拡張子.csvは必要なし。
CsvDataSetオブジェクトを生成する。

String pathName = "ディレクトリのクラスパスルートからの位置";
URL url =
	Thread
	.currentThread()
	.getContextClassLoader()
	.getResource(pathName);
CsvDataSet dataSet = new CsvDataSet(new File(url.toURI()));


この話、公式サイトにもドキュメントないような。。。
ソースを読むとわかります。

たとえば。

public CsvDataSet(File dir) throws DataSetException {
    super(new CsvProducer(dir));
    this.dir = dir;
}

CsvDataSetのコンストラクタに引数としてdirつまりディレクトリを渡して。

List tableSpecs = CsvProducer.getTables(dir.toURL(), "table-ordering.txt");
for (Iterator tableIter = tableSpecs.iterator(); tableIter.hasNext();) {
    String table = (String) tableIter.next();
    try {
	produceFromFile(new File(dir, table + ".csv"));

table-ordering.txtを行単位で読んで、その行に.csvをつけたファイルを読み込んでる。

private void produceFromFile(File theDataFile) throws DataSetException, CsvParserException {
    logger.debug("produceFromFile(theDataFile=" + theDataFile + ") - start");

    try {
        CsvParser parser = new CsvParserImpl();
        List readData = parser.parse(theDataFile);
        List readColumns = ((List) readData.get(0));
        Column[] columns = new Column[readColumns.size()];

        for (int i = 0; i < readColumns.size(); i++) {
           columns[i] = new Column((String) readColumns.get(i), DataType.UNKNOWN);
        }

(List) readData.get(0)つまりファイルの1行目が列名として扱われてる。