Fight the Future

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

ScalaでTestNGのテストをする

Javaで書いたこういうTestNGのテストがある。

import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class TestNGSample {
	private Target target;

	@Test
	public void verifyAdd1() {
		System.out.println("★★★verifyAdd1()を呼び出した★★★");
		int result = target.add(1, 2);
		assertEquals(result, 3);
	}

	@BeforeMethod
	public void init() {
		System.out.println("★★init()を呼び出した★★");
		target = new Target();
	}

	@Test(expectedExceptions = { java.lang.RuntimeException.class })
	public void exceptionTest() {
		System.out.println("★★★exceptionTest()を呼び出した★★★");
		target.throwException();
	}
}

これをScalaで書き直して実行する。
Targetクラスは足し算するメソッドがあるだけのクラスね。

まずScalaで書き直す。

import org.testng.annotations.{BeforeMethod, Test}
import org.testng.TestNG
import org.testng.TestListenerAdapter
import org.testng.Assert._

class TestNGSample {

  var target:Target = null
  
  @BeforeMethod
  def init() = {
    target = new Target
    Console println "★init()を呼び出した★"
  }
  
  @Test
  def verifyAdd1() = {
    Console println "★★★verifyAdd1()を呼び出した★★★"
    val result = target add(1, 2)
    assertEquals(result, 3)
  }

  @Test{val expectedExceptions = Array(classOf[RuntimeException])}
  def exceptionTest() = {
    Console println "★★★exceptionTest()を呼び出した★★★"
    target throwException
  }
}

import文

import文はJavaと同じ。クラスパスさえ通せばScalaからJavaクラスは普通に使えます。
同じパッケージにある複数のクラスをインポートするときは{}にカンマ区切りでクラスを指定。
「_」はJavaでいう「*」。


Scalaにはstaticという概念がないので(すべてがオブジェクトだから?)、static importはない。
「import org.testng.Assert._」はAssertクラスのstaticメソッドをすべてインポートしてる。

varとval

varとvalはScalaの説明でどこでも載ってるけど、valは一度代入すると変更できない。varは今までの変数と同じ。
Scalaには型推論(注:動的言語ではないので、型がないわけではありません)があるので、変数の型は指定しなくて「も」いい。
指定したければ「var target:Target」のようにコロンの後に型を書く。

テストの実行

Scalaで書いたTestNGのテストは、特に違いなく実行できる。
これはScalaをコンパイルするとclassファイルができるからかな?
まずはmainメソッドからテストを実行することにしよう。

import org.testng.TestNG

object Main {
  def main(args: Array[String]): Unit = {
    val testng = new TestNG
    testng setTestClasses(Array(classOf[TestNGSample]))
    testng run
  }
}

Javaでいうmainメソッドは「main(args: Array[String]): Unit」のシグニチャ
UnitはJavaのvoid(と最初は考えていていいそうだ)。Scalaでは戻り値はコロンの後ろ。
これを実行するとテストが実行される。

[Parser] Running:
  Command line suite

★init()を呼び出した★
★★★exceptionTest()を呼び出した★★★
★init()を呼び出した★
★★★verifyAdd1()を呼び出した★★★

===============================================
Command line suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================

classとobject

気をつけてほしいのは、Mainの方はclassではなくobjectというキーワードを使っていること。
これもScalaの説明でどこでも載ってるけど、objectとするとシングルトンオブジェクトになる。
(自分でnewしてインスタンスは作れなくなる)
mainメソッドを定義するにはclassではなくobjectでないとダメみたい。
「Exception in thread "main" java.lang.NoSuchMethodError: main」と出たので。

eclipseからテストを実行

eclipseにTestNGプラグインを入れていれば、それを使ってテストを実行することもできる。
1つはまった。
eclipseのRun Configurationsで、Annotation Compliance Levelが「JDK」になってるか確認すること。
Scalaで書いているからきちんとプラグインが認識してくれず、「javadoc」がデフォルトになってしまうので。
f:id:jyukutyo:20081017150424p:image
テスト結果も普通にビューに出る。
f:id:jyukutyo:20081017150425p:image

[Parser] Running:
  /Users/jyukutyo/Documents/workspace/ScalaSample/temp-testng-customsuite.xml

★init()を呼び出した★
★★★exceptionTest()を呼び出した★★★
★init()を呼び出した★
★★★verifyAdd1()を呼び出した★★★
PASSED: exceptionTest
PASSED: verifyAdd1

===============================================
    sample.TestNGSample
    Tests run: 2, Failures: 0, Skips: 0
===============================================


===============================================
ScalaSample
Total tests run: 2, Failures: 0, Skips: 0
===============================================

DbUnitNG10,000ページビュー突破&ダウンロードもしてもらえてる

f:id:jyukutyo:20081014180218p:image
TestNG + DbUnitのライブラリDbUnitNGですが、
日々見てもらえてるみたいです。
僕がまったく見ていない日でも1日100ページビューぐらいあって、
ダウンロードもようやく20を超えてくれました。


内輪がどれだけかわからないですがw


でも今まで僕1人でやってきて(今は強力なメンバーがもう1人います)、
これだけ見てもらえたというのはうれしい限りです。


おそらく今月中にバージョン1.0として、バグフィックスTestNGDbUnitのバージョンアップに対応する感じに移行できると思います!

LazyなDataProvider-TestNGをさらに深く理解してみる(3)

@ITさんの連載でデータ駆動テストで簡単にテストのパターンを増やすを解説した。
DataProviderはTestNGで注目を集める便利な機能だ。
DataProviderのメソッドはObject配列を戻り値とすればよいだけなので、
ファイルからでもデータベースからでもテストデータを取得できる。


たとえば、データベースからテストデータを取得するとこんなテストケースになるだろう。

public class NormalDataProividerTest {

	@DataProvider(name = "data")
	public Object[][] data() {
		DeptDao dao = new DeptDao();
		List<Dept> list = dao.listAllDept();

		Object[][] data = new Object[list.size()][];
		int index = 0;
		for (Iterator<Dept> iterator = list.iterator(); iterator.hasNext();) {
			Dept dept = (Dept) iterator.next();
			data[index] = new Object[] { dept };
			index++;
		}

		return data;
	}

	@Test(dataProvider = "data")
	public void test(Dept dept) {
		// call method with Model Object...
		Assert.assertTrue(true);
	}

}

DAOでデータベースからテストデータを取得して、テストメソッドの引数とする。

テストデータが大量にある場合問題となる

ただし、上記のサンプルはデータベースから取得するテストデータが大量である場合、問題を引き起こす。
それは、メモリの消費量だ。
データベースからデータを取得するということは、メモリ上に展開することになるわけで、
データが数百件程度ならまったく問題ないが、たとえば10万件テストデータがあるとOutOfMemoryになるかもしれない。

LazyなDataProvider

TestNGではそうした場合、Iteratorを使ったDataProviderを利用する。

public class LazyDataProviderTest {

	@DataProvider(name = "data")
	public Iterator data() {
		return new DeptIterator();
	}

	@Test(dataProvider = "data")
	public void test(Dept dept) {
		// call method with Model Object...
		Assert.assertTrue(true);
	}

	private static class DeptIterator implements Iterator {

		private int index;

		private DeptDao deptDao = new DeptDao();

		public boolean hasNext() {
			List<Dept> list = deptDao.listAllDept();
			if (index < list.size()) {
				return true;
			}
			return false;
		}

		public Object[] next() {
			List<Dept> list = deptDao.listAllDept();
			Object[] data = new Object[] { list.get(index) };
			index++;
			return data;
		}

		public void remove() {
			throw new UnsupportedOperationException();
		}
	}
}

DataProviderのメソッドが戻り値をObjectからIteratorに変えている。
TestNGはこのIteratorのhasNext()とnext()を呼び出し、テストメソッドに順次渡す。
サンプルではIteratorをstaticネストクラスにした。


メモリを消費しないようにできるトレードオフとしてhasNext()とnext()のそれぞれでデータベースにアクセスするためテストの実行に時間がかかる。
なお、サンプルではデータベースから全件取得しているから、Iteratorを使わないパターンとメモリの消費量としては変わらないけど、
たとえばhasNext()では件数だけSQLで取得するようにしてindexと比較すれば消費量は少なくなるとかできる。


テストデータを大量件数データベースから取得するときは、ぜひIteratorを使ったLazyなDataProviderを使ってください!

アノテーショントランスフォーマー-TestNGをさらに深く理解してみる(2)

打って変わって、今回は実装の話。
TestNGでは実行時に@Testのさまざまな属性の値を変えることができる。
それがアノテーショントランスフォーマー
TestNGのバージョン5.3からの目玉機能。
サンプルで試してみる。


まずはテストクラス。@Testのtimeout属性を変えたいので、わかりやすいようにThread.Sleep()を呼び出してる。
アサートに意味なし。

package net.kronosjp.testng.transformer;

import static org.testng.Assert.*;

import org.testng.annotations.Test;

public class Sample {
	@Test
	public void test() throws InterruptedException {
		Thread.sleep(3000);
		assertTrue(true);
	}
}

これをアノテーショントランスフォーマーとか関係なく普通に実行すると、当然成功する。

PASSED: test

===============================================
    net.kronosjp.testng.transformer.Sample
    Tests run: 1, Failures: 0, Skips: 0
===============================================

ではアノテーショントランスフォーマーを作ってみる。
IAnnotationTransformerインターフェースを実装する。
transform()メソッドがあるだけ。

package net.kronosjp.testng.transformer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.testng.internal.annotations.IAnnotationTransformer;
import org.testng.internal.annotations.ITest;

public class SampleAnnotationTransformer implements IAnnotationTransformer {

	public void transform(ITest annotation, Class testClass,
			Constructor testConstructor, Method testMethod) {
		System.out.println("★テストメソッド名★" + testMethod.getName());
		annotation.setTimeOut(1000);
	}
}

引数に@TestのインスタンスであるITestオブジェクト、そしてリフレクションのオブジェクトが渡される。
だからメソッド名で判断して属性の値を変えたりもできる。
今回はタイムアウトを1000ミリ秒にしてみた。


テストメソッドでは3000ミリ秒待機してる。
ところがアノテーショントランスフォーマーでタイムアウトを1000ミリ秒に設定したから、テストはタイムアウトオーバーで失敗することになる。
@Test(timeout = 1000)と同じ意味。


じゃあアノテーショントランスフォーマーを使ってテストを実行するために、設定ファイルを書く。

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="samplesuite">
	<method-selectors>
		<method-selector>
			<selector-class
				name="net.kronosjp.testng.transformer.SampleAnnotationTransformer"></selector-class>
		</method-selector>
	</method-selectors>
	<test verbose="2" name="sampletest" annotations="JDK">
		<classes>
			<class name="net.kronosjp.testng.transformer.Sample"></class>
		</classes>
	</test>
</suite>

気をつけることは、listener要素じゃなくてmethod-selector要素を使うこと。listener要素にアノテーショントランスフォーマーを書いても動作しなかった。
実行すると次のようになる。

★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
★テストメソッド名★test
FAILED: test
org.testng.internal.thread.ThreadTimeoutException: Method public void net.kronosjp.testng.transformer.Sample.test() throws java.lang.InterruptedException didn't finish within the time-out 1000
... Removed 18 stack frames

===============================================
    sampletest
    Tests run: 1, Failures: 1, Skips: 0
===============================================

「java.lang.InterruptedException didn't finish within the time-out 1000」の表示のとおり、タイムアウトでテストが失敗してる。
けど、なんでtransform()メソッドが何回も呼ばれるんだろう??
ちょっとよくわからない。


プログラムでこうした値を一括で制御できることにはメリットがあると思う。
リフレクションで制御できるのも魅力になっている。

ステートフルなテスト-TestNGをさらに深く理解してみる(1)

@ITさんの連載も終わり、Next Generation Java Testing: TestNG and Advanced Conceptsをネタに記事では書かなかったことを紹介していく。
不定期連載の予定。

ステートフルなテストとは

TestNGは各テストメソッドの呼び出しで同じインスタンスを利用する。詳しくは@ITさんの連載第1回を読んでね。
同じインスタンスだから状態を持つわけで、ステートフルなテストと呼ばれる。
一般的にテスト間で状態を共有することは「やってはいけない」プラクティスである。
ではなぜTestNGはそれをやってしまているのか?

状態のカテゴライズ

まず。状態は2つのものがあり、きちんと区別する必要がある。

  • 不変な状態(Immutable State)
  • 変化する状態(Mutable State)

不変な状態はたとえばfinal宣言したフィールドとか。これはまったく問題ない。
重要なのは値の初期化が一度しか行われないということ。
状態が変わることはないので、こう言える。


不変な状態にアクセスするテストメソッドは互いに独立している。


一方、変化する状態はたとえば普通のインスタンス変数とか。

public class Sample {
  private int count;

  @Test
  public void test1() {
    count++;
    assertEquals(1, count);
  }

  @Test
  public void test2() {
    count++;
    assertEquals(2, count);
  }
}

countはテストメソッドの実行で値が変わる。だからcountは変化する状態。
だけどこのテストは誤り。test1()とtest2()がどんな順序で実行されるかわからないから。
この場合test1()->test2()の順序で実行されなければテストが成功しない。
つまりこう言える。


変化する状態をテスト間で共有してよいのは、どの順序でテストを実行するのか決められるときだけである。


そのためにTestNGのdependsOnXXX属性を利用する。
テスト間で「絶対に」状態を共有してはならない、という原則は誤ってるというのがTestNGの考え。
逆にこうした依存性を指定していないときに変化する状態を用いてはならないということ。

共有する状態の種類 安全か
不変な状態 安全
依存性を指定した変化する状態 安全
依存性を指定していない変化する状態 安全でない

テストの実行速度

あと、同じインスタンスを使うことで実行速度の面でもメリットがある。

@ITさんTestNG記事最終回公開!


@ITさんで初連載いたしましたTestNG記事の最終回が公開されました!

  • 【1】Antからテストをサクッと実行!
  • 【2】テストを並列実行してスピードアップ!
  • 【3】いままでのJUnitのテストはドースル?
  • 【4】TestNGDbUnit=楽々DBテスト
  • コラム 「DbUnitアノテーションで利用するライブラリDbUnitNG」
  • 【5】レポートでもテスト結果を確認できる
  • 実現場でテスティングフレームワークを推進するには?

全3回を1ヶ月で公開できたので、いいリズムで書けました。
もしこの記事が役に立ったという人がいれば、自分自身の仕事の目的である「貢献」ができたということなので非常にうれしいです。


TestNGはuserもdevもメーリングリストが非常に活発で、サマリは日に1通くるぐらいです。
テスティングフレームワークの「現実解」になっていると思います。
個人ベースでも十分利用する価値があるので、ぜひ使ってみてください!

DbUnitNG0.6リリース!

DbUnitNGバージョン0.6をリリースしました!

AssertionHelperクラスのアサーションメソッドから引数としてClassクラスをすべて除きました。
実行したテストクラスはThreadLocalにあるため、引数として渡す必要がなくなりました。

Javadocも再生成しています。
JavadocSubversionにあるのでチェックアウトすると見れます。

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を紹介しようと思います。