来年2009年3月に『The Art of Unit Testing』って本がManningから出るらしい。
が、その画像がこれ。
武士。。。
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】TestNG+DbUnit=楽々DBテスト
- コラム 「DbUnitをアノテーションで利用するライブラリDbUnitNG」
- 【5】レポートでもテスト結果を確認できる
- 実現場でテスティングフレームワークを推進するには?
全3回を1ヶ月で公開できたので、いいリズムで書けました。
もしこの記事が役に立ったという人がいれば、自分自身の仕事の目的である「貢献」ができたということなので非常にうれしいです。
TestNGはuserもdevもメーリングリストが非常に活発で、サマリは日に1通くるぐらいです。
テスティングフレームワークの「現実解」になっていると思います。
個人ベースでも十分利用する価値があるので、ぜひ使ってみてください!
DbUnitNG0.6リリース!
DbUnitNGバージョン0.6をリリースしました!
AssertionHelperクラスのアサーションメソッドから引数としてClassクラスをすべて除きました。
実行したテストクラスはThreadLocalにあるため、引数として渡す必要がなくなりました。
Javadocも再生成しています。
JavadocはSubversionにあるのでチェックアウトすると見れます。
DbUnitNG0.5リリース!
DbUnitNGのバージョン0.5をリリースしました!
ダウンロード - DbUnitNG - SourceForge.JP
アサートメソッドの呼び出しがやや煩雑な感じで、ThreadLocalを使ったらどうかという提案(とパッチ)をもらったので、
導入したいと思います。
これでアサートの引数とか減らせるはずです。
この導入を最後にベータにバージョンを移して、これからの自分のプロジェクトでも使えるようにしっかりテストしてきたいと思います。
アノテーションのデフォルト値を一覧にしました
TestNG + DbUnitライブラリ「DbUnitNG」で、アノテーションのデフォルト値がわかりづらいという指摘をいただいたので、
AnnotationDefault - DbUnitNG Wiki - SourceForge.JPに一覧にしました!
依存ライブラリもコミットしました
DbUnitNG プロジェクト日本語トップページ - SourceForge.JPですが、
Subversionに依存するライブラリもすべてコミットしました。
libディレクトリに入っています。
eclipseなら.projectと.classpathもコミットしているので、
それもチェックアウトしてもらうと便利です。
だいぶ面倒くささがなくなったと思うので、気軽にソースからでも使ってみてください!
@ITにTestNGの記事を連載します
というわけで、これからTestNG普及への活動をしていきます。
このBlogもそうだけど、より多くのエンジニアの目に触れるようなところでもTestNGを紹介しようと思います。
DbUnitNGの今後 - Fight the Future{|じゅくのblog|}
との宣言どおり、TestNGについて@ITさんで連載を始めました。計3回の予定です。
上司であるid:iad_otomamayがHibernateやSpringの連載を@ITにしていた関係で、担当者の方を紹介してもらいました。
とにかくTestNGを普及させていきたいと思います。
今回はデファクトであるJUnitと比較をして、TestNGを理解してもらおうというコンセプトです。
JUnit4がTestNGから影響を受けている部分も多いので、使ってみるのにそれほど学習コストはかからないと思います。
始める敷居を下げて、まずは使ってもらいたいという思いです。
よかったら読んでみてください!
テストコードは違う言語がいい
個人的にはエンタープライズなシステムはJavaを始めとする静的言語が適切だと思ってるけど、テストコードまでJavaで書く必要はないんじゃないか。
テストコードというのはその対象(メソッドとか機能)の第1のクライアントだ。
つまり、使う側であって使われる側ではない。
使われる側はどんな使われ方をするのかというバリエーションを考える必要がある。
でも使う側は目的が1つではっきりしてる(その機能を呼び出すという目的)。
テストコードはパフォーマンスとかセキュリティとか諸々のことはそれほど要求されない。
だからいかに簡単に書けるか、サクッと書けるかってところがポイントだと思う。
めんどくさい、と思わずに済む言語を使う方がいい。
デメリットは2つの言語を使える必要があるってことだけ。
Javaと親和性がある言語なら、たとえばGroovy。そしてScala。
Groovyはスクリプト言語だから記述量が少ないし、Scalaはオブジェクト指向+関数型で後発の言語の分洗練されてる。
どちらもJVM上で実行するし、すぐに使える環境になる。
TestNGもTestNG+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 TestNGでTestNGの開発者である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つです。
これだけしかしないですけど、わりと開発で頻繁に使う部分です。
なんだけど、そもそも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をトップディレクトリに配置する
- ソースコードにライセンスへのリファレンスを追加する