JGiven で 100% Pure Java BDD(導入編)


この記事は、Java Advent Calendar 2016 の 14 日目の記事です。

13 日目の記事は、tkxlab さんの「データベースを簡単にーJavaSEでもオブジェクト指向データベース(JPA)を」 でした。
15 日目は、kokuzawa さんの「HttpURLConnectionで嵌った話」です。

はじめに

まず、JGiven を使用する動機について軽く述べます。

使い方を知りたいだけの方は、この「はじめに」を読み飛ばして、「JGiven が救う」から読んでください。

BDD したい

ソフトウェアの開発者であるわたしたちは、当然 JUnit とかを使ってユニットテストを書いています。

しかし、以下のような課題があります。

テストの対象範囲が揃わない

ユニットのテスト以外にも、機能のテストなども書きたいです。
しかし、それらを全部 JUnit で書くと、JUnit で実行されるテストの対象範囲がバラバラになってしまいます。

命名規則やアノテーションを用いて区別することもできますが、いずれにせよ特別の工夫が必要になります。

テストをうまく構成しづらい

JUnit は Java で書くので、(もちろん)クラスの分割やメソッドの分割は開発者の自由です。
熟練の開発者であれば、テストクラスやメソッド、ユーティリティクラス等を巧みに構成して、DRY でかつ読みやすいテストを書けるでしょう。

しかし、逆に言うと、JUnit にはテストを DRY に読みやすく構成することをサポートする特別な仕組みはなく、うまく構成するにはそれなりの知識と経験が必要になります。

テストレポートの表現力が低い

JUnit では、命名規則を使ってテストの内容を表現したりします。
たとえば、xUnit Test Patterns では、以下のような名付け方が提案されています。

test<テスト対象>_should<期待する動作>(例 : testRequestApproval_shouldEndUpInAwaitingApproval)

他にも、テスト対象の状態や入力値も含めるほうがよいしょう。

いずれにせよ、どんなに工夫しても所詮 Java のメソッドの名前であり、表現力に限界があります。
また、フレームワークによって名付け方が強制されないため、(たとえば)期待する動作を書き忘れてしまったり、新入りの開発者が規則に気づかず下手な名前をつけてしまう、ということも起こりえます。

結果として、ビジネス関係者にもテストエンジニアにも、あまつさえテストを書いたソフトウェアエンジニア本人にも(書いてから時間が経てば)、テストレポートを読んだだけではどのような動作が保証されているのかわからない、という事態になりかねません。1

だから BDD したい

そこで、BDD フレームワークを導入して、

  • ユニットより一回り(以上)上の範囲を対象に
  • レポートの表現力が高い方法で

テストを書きたいです。

100% Pure Java で

ところが、多くの BDD フレームワークでは、振る舞いを記述する独自の記法を学んで、メンテナンスしていく必要があります。

Java で BDD フレームワークとしては JBehaveCucumber-JVM などがありますが、振る舞いの記述そのものはプレインテキストで行い、Java コードとの紐付けをアノテーションで行う必要があります。2

この方法には、以下のような難点があります。

  • 振る舞いの記述と Java コードを紐付けるアノテーションを覚えるのが面倒
  • 同等のテストを書いた場合の記述量が(Java だけで書く)JUnit よりも増える
  • 独自記法の部分には IDE のリファクタリング機能が効かず、名前変更等が面倒

可能なら、Java だけで書きたい……。

JGiven が救う

そこで JGiven ですよ!

JGiven とは

JGiven とは何か、公式サイト から引用(翻訳)します。

JGiven は、開発者にやさしく、実用的な Java の BDD ツールです。
開発者は、流暢でドメインに特化した API を使用し、Java(のみ)でシナリオを記述します。
ドメインエキスパートは、JGiven が生成したレポートを読むことができます。

そう、Java だけで書けるのです! 3

もちろん、読みやすいレポートも出ます。

JGiven によって、100 % Pure Java による BDD が実現します。

各種のリソース

JGiven 関連の各種リンクを紹介しておきます。

GitHub での開発は活発で、普及活動も精力的に行われており4、まだまだマイナーなフレームワークながら、好感が持てます。

使い方

使い方は、ユーザガイドを見ればだいたいわかります。

と、それだけではあんまりなので、ステップバイステップで簡単に説明します。

基礎編

まずは動かしてみましょう。

依存関係

jgiven-core は JUnit か TestNG と一緒に動作します。

2016/12/14 時点で最新の JGiven 0.12.1 では、JUnit なら 4.9 以上(4.11 以上を推奨)が必要です。

本体を作る

依存関係が解決できたら、早速書きましょう。
以降の例は JUnit による説明です。

まず、テストクラスの本体を作ります。

MyShinyJGivenTest.java
import com.tngtech.jgiven.junit.ScenarioTest;

public class MyShinyJGivenTest
        extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> {
}

簡単ですね。

JGiven のテスト(シナリオ)クラスは、ScenarioTest というクラスを継承します。
ScenarioTest は 3 つの型パラメータを取ります。それぞれ、BDD の典型的な 3 ステップ(Given、When、Then)に対応します。

ステージを作る

さて、この時点で MyShinyJGivenTest の GivenSomeState、WhenSomeAction、ThenSomeOutcome はまだ存在しません。
作りましょう。

GivenSomeState.java
import com.tngtech.jgiven.Stage;

public class GivenSomeState extends Stage<GivenSomeState> {
    public GivenSomeState ある状態のとき() {
        return self();
    }
}
WhenSomeAction.java
import com.tngtech.jgiven.Stage;

public class WhenSomeAction extends Stage<WhenSomeAction> {
    public WhenSomeAction あることをしたら() {
        return self();
    }
}
ThenSomeOutcome.java
import com.tngtech.jgiven.Stage;

public class ThenSomeOutcome extends Stage<ThenSomeOutcome> {
    public ThenSomeOutcome 何かが起こる() {
        return self();
    }
}

せっかくなので、メソッド名は日本語にしてみました。5

シナリオを追加して実行する

準備は整いました。
シナリオを追加して、実行しましょう。

MyShinyJGivenTest.java
import org.junit.Test;
import com.tngtech.jgiven.junit.ScenarioTest;

public class MyShinyJGivenTest
        extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> {

    @Test
    public void 何かが起こる() {
        given().ある状態のとき();
        when().あることをしたら();
        then().何かが起こる();
    }
}

シナリオは、おなじみの JUnit / TestNG のテストメソッドとして追加し、このような記法で記述します。

IDE から

まずは、私が普段使っている IntelliJ IDEA から実行してみます。

枠組みは JUnit なので、普段通り JUnit のテストとして実行することができます。
もちろん、Eclipse や NetBeans でも同様です。

結果がコンソールに出力されます。

シナリオやステップに雑な名前をつけたら、図らずも 90 年代の J-POP の歌詞みたいになりましたが、とにかく成功です。

コマンドラインから

もちろん、コマンドラインからも実行できます。

$ mvn test

レポートの出力まで自動化したいということも考えると、むしろこちらがメインの使い方になるかもしれません。

HTML5 でレポートを出す

JGiven のシナリオを実行すると、デフォルトで 2 種類のレポートが出力されます。

  • コンソールへのレポート
  • JSON ファイルへのレポート

コンソールへのレポートは、既に目にしたものです。

JSON ファイルへのレポートは、jgiven-reports/json ディレクトリに保存されます。
お手元の実行環境でこのディレクトリがどこになるかを調べる手っ取り早い方法は、下記です。6

System.out.println(com.tngtech.jgiven.impl.Config.config().getReportDir().get().getAbsolutePath());

さて、JSON でレポートが出たのはいいのですが、JSON を読むのはつらい。
もちろん、HTML に変換する手段が用意されています。

依存関係

HTML5 レポート機能を使用するには、既に取得した jgiven-core に加えて、jgiven-html5-report への依存を追加してください。

実行方法

コマンドラインから

コマンドラインから実行する方法は、下記です。

java com.tngtech.jgiven.report.ReportGenerator \
  --format=html \
  [--sourceDir=<JSON のレポートがあるディレクトリ>] \
  [--targetDir=<HTML5 のレポートを出力したいディレクトリ>] \

jgiven-core およびその依存モジュール、jgiven-html5-report がクラスパスに通っていることを想定しています。(必要に応じて -cp オプションを使用してください)
--sourceDir / --targetDir を指定しない場合、カレントディレクトリとなります。

maven / gradle から

maven や gradle のゴール / タスクとして定義する方法もユーザガイドに紹介されています。
必要に応じて参照してください。

結果

レポートはこちらです!

サマリの表示、成功/失敗/ペンディングによる分類、タグやクラスごとの分類、検索、印刷等、充実した機能があることがうかがえます。

なお、JGiven そのもののテストのレポートもサンプルとして提供されています。

まとめ

  • JGiven は導入しやすく、保守しやすく、レポートも読みやすいナイスな BDD フレームワークです。
  • プレインテキストや独自記法によるシナリオ記述を必要とせず、100% Pure Java による BDD を実現します。
  • 開発や広報活動も活発で、将来にも期待が持てると思います。

予告

ここまでで JGiven を導入できましたが、肝心のテスト対象クラスやアサーションを記述しておらず、まだ活用できているとは言えません。
数日中には、「JGiven で 100% Pure Java BDD(実践編)」を公開します。


  1. だから、ソフトウェアエンジニアはテストコードの可読性をとても大事にします。テストコードを読めば、何をテストしているのかわかります。しかし、ビジネス関係者や、コードの苦手なテストエンジニアがテストコードをすばやく読んで理解することを期待するわけにはいきません。 

  2. 他には、Spock という Groovy の BDD フレームワークがあります。(参考 : JavaのユニットテストにSpockを適用する)Groovy なので JVM 上で動作しますし、表現力もかなり高いようなのですが、Java プロダクトにに(テストコードとはいえ)Groovy のフレームワークを入れるのはなかなかハードルが高い、という場合も多いかと思います。 

  3. Java を書けないドメインエキスパートがシナリオを直接書けないという難点はありますが、開発者とペアになって書けば解決します。あるいは、ドメインエキスパートは任意の(JGiven の枠組みに親和性の高い)記法で記述し、開発者がそれを Java コードに変換するのでもよいでしょう。この場合も、アノテーションによる紐付けが必要な従来の BDD フレームワークと同等のコストにはなっても、より高コストになることはありません。一度 Java コードにしてしまえば、IDE によるリファクタリングも自由自在です。 

  4. 2016 年の JavaOne でもセッションを持たれていました(スライド : https://janschaefer.github.io/jgiven-slides-javaone-2016/#/) 

  5. テストコードに日本語を使用することには、賛否両論があるかと思います。私が所属するアプレッソでは、テストのメソッド名には日本語を使用し、記述性・可読性の両面でかなり効率的にテストを記述・活用できています。とはいえ、今後プロダクトの開発に日本語を読めない開発者が参加することになれば、また事情も変わってくるかもしれません。 

  6. 参考 : https://github.com/TNG/JGiven/blob/8225c0bfc0f8d7e712c2d2aa58a7db24d72a70b5/jgiven-core/src/main/java/com/tngtech/jgiven/impl/Config.java#L30