社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

JGiven をユーザーガイド見ながら試す(Java, JUnit)

JGiven とは

公式サイト から引用すると

JGiven は、開発者にフレンドリーで実用的な Java の BDD ツールです。
ドメインエキスパートにとって読みやすいレポートを生成してくれます。

JGiven を使おうとしたきっかけ

Java のプロジェクトに関しては、普段 JUnit を使用してテストを書いていますが、以下のような問題に悩まされています。
1. 既存テストの要点を掴むのが難しいことがある
他人が書いたテストコードはもちろんのこと、自分で書いたテストですら数ヶ月も経つと、テストの要点(目的)などが分かり難くなってしまっていることがあります。
テスト内でテストデータを作ったり、条件を作ったりしていると、それをトレースするのが大変になったりします。

2. テストのレポートがわかりにくい
JUnit でテストを実行した結果、例えその結果が全てパス(成功)だったとしても、どういった仕様を満たしているのかわかりにくい場合があります。
特にテストケースが数十、百以上になってきたりすると、どのようなテストケースがあるのか把握するのが難しくなります。

3. 開発者(プログラマー)しか読めない
上記と類似することですが、テストの仕様を理解するのに、プログラム(ソースコード)を読まなければならない場合があるため、開発者以外が仕様を理解することが難しくなります。
場合によっては、開発者以外のために、別途ドキュメントを用意する必要があります。

結局のところ、以下の2点が満足できれば、これらの問題を解消する手助けになりそうです。
1. 読みやすいテストが書けること
2. 分かりやすいレポートが出力されること
これらが、JGiven フレームワークに期待しているポイントになります。

ユーザーガイド

それでは、ユーザーガイドに沿って、インストールから最初の実行までやってみます。
JGiven User Guide

インストール

JUnitmaven を使用しているので、以下を 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 エラーになる

レポート出力に関しては、機能が充実しているようなので、機会があれば試行してみたいと思います。