BDDフレームワーク「instinct」を触ってみる

Java向けのBDDフレームワークInstinct」というのがあるらしいので、ちょっと試してみた。ブクマには「誰かの評価待ち」っていう何ともウィンプなコメントを残したが、あまりにあんまりなので。

About

Instinct is a Behaviour Driven Development (BDD) framework for Java. Inspired by RSpec, Instinct provides flexible annotation of contexts, specifications and actors; automatic creation of test doubles and test subjects; a state and behaviour expectation API; JUnit test runner integration; Ant support and an IntelliJ IDEA plugin.
Google Code Archive - Long-term storage for Google Code Project Hosting.


InstictはRSpecにインスパイアされた、Java向けのBDDフレームワークです。

  • コンテンツや仕様(specitication)、アクター*1への柔軟なアノテーション記述
  • テスト対象やテスト・ダブルの自動生成
  • 期待されるAPIの振る舞いの記述
  • JUnitの拡張
  • AntやIntelliJIDEA pluginによるサポート


Google Code Archive - Long-term storage for Google Code Project Hosting.:俺々和訳

記法をわかりやすくしたってのとモック利用をサポートしたってのが良さそうな点。JUnitの拡張でもあるので、既存のJUnit関連ツールもそのまま使える。のかな。

とりあえず動かしてみる

Google Code Archive - Long-term storage for Google Code Project Hosting.の Download から Example Project を落として展開、eclipseで"Create project from existing source"からプロジェクトを新規作成する。

必要なモノは全て入ってるはず。起動方法は色々ある。

  • 付属のAnt build.xmlから起動する。独自起動の-run-specsと、junit3拡張およびjunit4拡張を利用した記述が用意されていて、とりあえず全部動くようになってる。
  • JUnitのテストケースとして、そのまま実行できる。eclipseでは"Shift + Alt + x , t"で起動。
  • Javaから呼び出す場合、com.googlecode.instinct.runner.TextRunner.runContexts(final Class... contextClasses)を利用する。例えば、↓のように。
public class StackBehaviourRunner {
    public static void main(String[] args) {
        TextRunner.runContexts(
            AnEmptyStack.class,
            ANonEmptyStack.class);
    }
}
  • テキストファイルに設定を書き込んで…みたいにも呼び出せるらしい。

特徴

  • アノテーションによる記述
    • @Specification : 仕様の記述は、publicなメソッドに@Specificationをつける。
    • @BeforeSpecification : 事前準備は@BeforeSpecificationをつける。
  • Test Double - テスト用に利用するだけのオブジェクト。手動で生成したり、jMockやEasyMockを利用して生成したりもできるとのこと。
    • @Subject : 対象となるオブジェクト
    • @Dummy : パラメータを満たすだけのオブジェクト。dummyObjectのメソッドを呼び出すと、必ず例外が投げられる。enum型、プリミティブ型およびfinalクラスでは利用できない。
    • @Stub : メソッドが呼ばれた場合に予め用意した返却値を返すオブジェクト。email gatewayのように(?)、呼ばれたメソッドを記録できるらしい。
    • @Mock : より高度なスタブ。スタブの機能に加え、予めプログラムした返却をするらしい。

サンプルを読む

com.googlecode.instinct.example.stack.AnEmptyStack を例に。日本語コメントでよごしてます。

package com.googlecode.instinct.example.stack;

import static com.googlecode.instinct.expect.Expect.expect;
import com.googlecode.instinct.integrate.junit4.InstinctRunner;
import com.googlecode.instinct.marker.annotate.BeforeSpecification;
import com.googlecode.instinct.marker.annotate.Dummy;
import com.googlecode.instinct.marker.annotate.Specification;
import static com.googlecode.instinct.marker.annotate.Specification.SpecificationState.PENDING;
import com.googlecode.instinct.marker.annotate.Subject;
import org.junit.runner.RunWith;

//↓の記述でJUnitと連携するご様子。
@RunWith(InstinctRunner.class)
public final class AnEmptyStack {

//@Subjectは記述対象。
//implimention属性を指定でき、指定すると自動生成(auto-creationとかauto-wiring:自動配線と呼んでいる)が出来る…とのことだが、よくわからない。
    @Subject private Stack<Object> stack;

//@Dummyで初期化不要なダミーオブジェクトとして扱える。
//この場合、例えばobject.hashCode()とか呼んだ時点で例外が投げられる。中身はProxyなクラス。
//@Dummyを@Mockと書き換えても動作可能で、@Mockとした場合、object.hashCode()などは呼べる。
//Mockも中身はProxyなクラス。
//例えば"@Mock private java.util.Date object"とすると、toString()で"object"みたいに変数名が表示される。
//@Dummyを@Stubと書き換えても動作可能で、Stubだけ、中身はProxyなクラスではない。
//例えば"@Stub private java.util.Date object"とすると、"Thu Jan 01 09:00:00 JST 1970"なDateインスタンスになる。
    @Dummy private Object object;


//@BeforeSpecificationは、それぞれのSpecificationを実行する前に実行される。
    @BeforeSpecification
    void before() {
        stack = new StackImpl<Object>();
    }

//@Specificationは振舞い仕様の記述であり、テストの記述。
    @Specification
    void isEmpty() {
        expect.that(stack.isEmpty()).isTrue();
    }

    @Specification
    void isNoLongerBeEmptyAfterPush() {
        stack.push(object);
        expect.that(stack.isEmpty()).isFalse();
    }

//expectedException属性で、期待する例外を定義。その場合、withMessage属性で期待するメッセージを定義できるらしいが、ここまでは使わないような気もする。
    @Specification(expectedException = IllegalStateException.class, withMessage = "Cannot pop an empty stack")
    void throwsExceptionWhenPopped() {
        stack.pop();
    }

//state属性で、「未記述」「記述完了」「保留」などの状態を付けられる。
//「未記述」「保留」の場合、そのテストは実行されない。
    @Specification(state = PENDING)
    void hasSomeNewFeatureWeHaveNotThoughtOfYet() {
        expect.that(true).isFalse();
    }
}

雑感

  • モック等のサポートが簡単で嬉しいが、Dummy,Stub,Mockの使い分けがまだよくわからない。
  • Fixtureでテスト用のデータセットを定義できるらしいが、そこまで試してない。
  • Java向けBDDフレームワークであるところのJBehaveよりも、記述はしやすいと思う。アレは今考えると、matcherとか例外のためのブロックとか、記述は面倒だった。
  • csvreader.bddとcsvreader.junitで、同じテスト内容での記述の違いの例示があって、親切。junitでの各テストケースはテスト対象クラス向けの記述になっているのに対し、bddは「csvファイルがこの場合には…」という視点でクラスを分けて記述されている。仕様クラス、みたいな感じだろか。
  • 全体的に使いやすい直感。サンプルは親切だし、ドキュメントの量が少ない割にわからない感が少ないし。「ある程度使う」であれば、このくらいの知識で十分かも。
  • というわけで、より詳しい諸兄の評価待ち(笑)

*1:アクターとは、振舞いを記述する対象物や記述用に一時的に使用するのダブル(=モック、スタブ、ダミー)らのこと。see also http://code.google.com/p/instinct/wiki/Terminology