JUnit実践入門の「第12章 データベースのテスト」でも取り上げられているDbUnitさんのRuleから派生して、RuleとかRuleChainとかその辺をちょっと書いておきます。
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (68件) を見る
セットアップ
ざくっと使えるようにするための build.gradle はこんな感じ。Mavenでもだいたいわかりますよね。
apply plugin: 'java' repositories.mavenCentral() dependencies { testCompile 'com.h2database:h2:1.3.170' testCompile 'junit:junit:4.11' testCompile 'org.dbunit:dbunit:2.4.9' testCompile 'org.slf4j:slf4j-simple:1.7.2' }
載ってるRuleさん(DbUnitTester)
'extends AbstractDatabaseTester' とやってますが、JDBCを使うなら特化されてる 'JdbcDatabaseTester' や 'PropertiesBasedJdbcDatabaseTester' を使っても良いと思います。*1
JdbcDatabaseTester を使用すると若干シンプルにかけます。が、コンストラクタが例外をスローしちゃうので @Rule を付けたフィールドでは使えません。こういうときは二通りの解決が出来ます。
// メソッドに Rule をつける @Rule public DbUnitTester tester() throws ClassNotFoundException { return new DbUnitTester("org.h2.Driver", "jdbc:h2:tcp://localhost/db;SCHEMA=ut", "sa", "", "ut") { // 略 }; } // メソッドを作る @Rule public DbUnitTester tester = DbUnitTester.create();
別に「Ruleはフィールドに付与し、コンストラクタを呼ばなければならない」なんて制約はありませんので。
それっぽく書くとこんな感じになるます。
実際は 'UserDaoDbUNitTeser' のようなものを作るでしょうし、どうでも良いことになるでしょうけども、もし「コンストラクタが例外を投げるとRuleに出来ない!」なんてなると勿体ないので。
で、TableごとにDrop-CreateのようなRuleを作るなら、いちいちコンストラクタで渡すのは面倒だから 'PropertiesBasedJdbcDatabaseTester' を使うことになると思います。接続情報とか散らばるの良く無い。
Ruleが増えてきた場合(RuleChain)
Ruleを使うようになると、そのうちセットでRuleをつけていくようになるかもしれません。
public class HogeTest { @Rule public TestRuleA a = new TestRuleA(); @Rule public TestRuleB a = new TestRuleB(); @Rule public TestRuleC a = new TestRuleC(); // Tests... }
Ruleを選択して付けたり剥がしたりできるのはRuleの魅力の一つですが、セットで付ける場合(かつそのRuleの参照を必要としない場合)には無駄に行をとっているように見えます。こういうときはRuleChainですね。(P.159)
public class HogeTest { @Rule public RuleChain chain = RuleChain.outerRule(new TestRuleC()) .around(new TestRuleB()).around(new TestRuleA()); // Tests... }
元の記述ではRuleフィールドの定義順のように思えて、実際の適用順序は決まってなかったりします。このあたりはJava7やJUnit4.11で突然テストの実行順序が変わって戸惑った方もいるかもしれませんが、アレと似たようなものです。*2
RuleChain の適用順が気になるかもしれませんが、大雑把に言うと「Ruleは各テストを包むもの」です。だから outerRule/around となっていて、 before/after などではないわけです。TestWatcher などが一つのRuleで前後処理ができることを思えば、前後だと謎ですよね。
RuleChainはまだうるさい
さて、RuleChain を使って1フィールドに収まったように見えますが、まだ問題は解決してなかったりします。うるさい(長い)し、「お決まりのRule」が変わって足したり引いたりするのは複数のテストにまたがるととても手間です。
てことで、こう。
public class HogeTest { @Rule public TestRule chain = HogeTests.hogeRules(); // Tests... } public class HogeTests { public static RuleChain hogeRules() { return RuleChain.outerRule(new TestRuleC()) .around(new TestRuleB()).around(new TestRuleA()); } }
適用するRuleのセットを選択してRuleChainを作るようなメソッドにする手もあります。ただしRuleのフィールドやメソッドを使いたい場合*3には、この手は素直には使えませんので、素直じゃないやりかたや、別の解決をしてください。
テストの基底クラスを作りたくなる誘惑との戦いは続きます(しみじみ)