JGiven をユーザーガイド見ながら試す(Java, JUnit)
JGiven を使おうとしたきっかけ
Java のプロジェクトに関しては、普段 JUnit を使用してテストを書いていますが、以下のような問題に悩まされています。
1. 既存テストの要点を掴むのが難しいことがある
他人が書いたテストコードはもちろんのこと、自分で書いたテストですら数ヶ月も経つと、テストの要点(目的)などが分かり難くなってしまっていることがあります。
テスト内でテストデータを作ったり、条件を作ったりしていると、それをトレースするのが大変になったりします。
2. テストのレポートがわかりにくい
JUnit でテストを実行した結果、例えその結果が全てパス(成功)だったとしても、どういった仕様を満たしているのかわかりにくい場合があります。
特にテストケースが数十、百以上になってきたりすると、どのようなテストケースがあるのか把握するのが難しくなります。
3. 開発者(プログラマー)しか読めない
上記と類似することですが、テストの仕様を理解するのに、プログラム(ソースコード)を読まなければならない場合があるため、開発者以外が仕様を理解することが難しくなります。
場合によっては、開発者以外のために、別途ドキュメントを用意する必要があります。
結局のところ、以下の2点が満足できれば、これらの問題を解消する手助けになりそうです。
1. 読みやすいテストが書けること
2. 分かりやすいレポートが出力されること
これらが、JGiven フレームワークに期待しているポイントになります。
ユーザーガイド
それでは、ユーザーガイドに沿って、インストールから最初の実行までやってみます。
JGiven User Guide
インストール
JUnit と maven を使用しているので、以下を pom に指定してあげます。
<dependency>
<groupId>com.tngtech.jgiven</groupId>
<artifactId>jgiven-junit</artifactId>
<version>0.17.0</version>
<scope>test</scope>
</dependency>
JUnit テストクラスを作る
何はともあれ、テストクラスがなければ話が始まらないので作ります。
JUnit の場合は、ScenarioTest を継承する必要があるようです。
更に ScenarioTest は3種類のパラメータを要求しており、それぞれ Given-When-Then と呼ばれます。
import com.tngtech.jgiven.junit.ScenarioTest; public class MyShinyJGivenTest extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> { }
日本語で言うと、以下のようなステージ構成になるようです。
Given : 何かの状態のとき
When : 何かをしたら
Then : 何かの結果になる
これを上手く表現することが出来れば、そのまま仕様のように読み取れるかもしれません。
Given, When, Then クラスを作る
それぞれのステージを構成するためのクラスを生成します。
Given
import com.tngtech.jgiven.Stage; public class GivenSomeState extends Stage<GivenSomeState> { public GivenSomeState some_state() { return self(); } }
When
import com.tngtech.jgiven.Stage; public class WhenSomeAction extends Stage<WhenSomeAction> { public WhenSomeAction some_action() { return self(); } }
Then
public class ThenSomeOutcome extends Stage<ThenSomeOutcome> { public ThenSomeOutcome some_outcome() { return self(); } }
Stage クラスの継承を必須としているわけではないそうですが、and() や self() などの有用なメソッドが提供されているため、推奨されているようです。
※何が有用なのか現時点では不明ですが
テストシナリオを書く
それではいよいよテストシナリオを書いていきます。
最初に作ったテストクラスに追記しています。
public class MyShinyJGivenTest extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> { @Test public void something_should_happen() { given().some_state(); when().some_action(); then().some_outcome(); } }
ScenarioTest を継承したことにより、given(), when(), then() がそれぞれ指定したクラスのインスタンスに紐づいているようです。
なので、インスタンス.メソッド名 という形でメソッドの呼び出しが可能となっています。
実行してみる
実行してみると、以下のような結果が出力されました。
Given, When, Then は固定で、some xxx は、メソッド名(some_state など)から来ているようです。
Test Class: test.java.practice.bdd.jgiven.MyShinyJGivenTest Something should happen Given some state When some action Then some outcome
なので、例えば日本語でメソッド名を作成してみた場合、こんな感じの出力が期待できます。
どんなテストをやっているのか分かりやすいですね。
Given サーバーが止まっている時に When プログラムを実行すると Then エラーになる
もし ScenarioTest を継承することができない場合
すでに他のクラスを継承しているなどで、ScenarioTest を継承することができない場合は、JGiven の JUnit rule を使用すれば良いとのこと。
具体的には、JGivenClassRule と JGivenMethodRule を定義すること。
Given-When-Then それぞれのクラスを @ScenarioStage アノテーションを用いて注入してあげること。
public class UsingRulesTest { @ClassRule public static final JGivenClassRule writerRule = new JGivenClassRule(); @Rule public final JGivenMethodRule scenarioRule = new JGivenMethodRule(); @ScenarioStage GivenSomeState someState; @ScenarioStage WhenSomeAction someAction; @ScenarioStage ThenSomeOutcome someOutcome; @Test public void something_should_happen() { someState.given().サーバーが止まっている時に(); someAction.when().プログラムを実行すると(); someOutcome.then().エラーになる(); } }
ScenarioTest のソースを見てみると、似たようなことをしているので、継承する代わりに自分たちで定義してあげるような格好になっていると思われます。
public class ScenarioTest<GIVEN, WHEN, THEN> extends ScenarioTestBase<GIVEN, WHEN, THEN> { @ClassRule public static final JGivenClassRule writerRule = new JGivenClassRule(); @Rule public final JGivenMethodRule scenarioRule = new JGivenMethodRule( createScenario() ); @Override public Scenario<GIVEN, WHEN, THEN> getScenario() { return (Scenario<GIVEN, WHEN, THEN>) scenarioRule.getScenario(); } }
まとめ
以上、ユーザーガイドに沿って試行してみましたが、当初期待していた2点
> 1. 読みやすいテストが書けること
> 2. 分かりやすいレポートが出力されること
について、JGiven が手助けになる可能性を感じることができました。
もちろん現時点では、テストケースも少なくシンプルなので分かりやすいですが、実際にシナリオが増えてきた場合に、上手くシンプルな状態を保ったまま管理が出来るかは分かりません。
とは言え、テスト結果が以下のように表示されるだけでも強力です。
Given サーバーが止まっている時に When プログラムを実行すると Then エラーになる
たとえば、以下のように Given だけ差し替えることも容易に出来ると思われます。
Given ネットワークが止まっている時に When プログラムを実行すると Then エラーになる
レポート出力に関しては、機能が充実しているようなので、機会があれば試行してみたいと思います。