Jbehaveを使ってみた。
るびまのRSpecの記事を見たときから密かに気になってた振舞駆動開発 (Behaviour Driven Development:BDD)ですが、IBM developerWorks ビヘイビア駆動開発を舞台にした冒険より「Jbehave」というJavaでBDDするライブラリがあるらしいのでちょっと使ってみました。
振舞駆動開発とは何か。何がうれしいのか。
るびまのRSpecの記事で熱く語られています。
個人的なイメージはこんな感じ。注:間違っている可能性大です。真に受けないでくださいね。
Jbehaveを使ってみる
Jbehaveを使ったテスト手順の概要は次の通りです。
- テスト対象クラスのAPIだけを書く。
- Behaviorクラスを書く。
- Behaviorクラスは「テストクラス名Behavior」の名前を持つPOJO
- 親クラス等の指定は特にない。
- Behaviorクラスにメソッドを追加して、テストクラスの振る舞いを記述していく。
- Behaviorクラスを動かす。そしてエラーになるのを確認。
- テスト対象クラスを実装する。
- Behaviorクラスを動かす。テストに合格することを確認。
以下で KittenクラスのテストをJbehaveを使ってやってみます。
1.テスト対象クラスのAPIを書く。
Kittenクラスを定義し、APIを書きます。
/** * ねこ * * @version $Revision:$ * @author $Author:$ */ public class Kitten { /**状態*/ private String state; /*** * 状態を取得する。 * @return 状態 */ public String getState () { return state; } /** * ジャンプ。 * <ul> * <li>ジャンプしたあとは状態が"jumping"になる。</li> * </ul> * @throws IllegalStateException 走っている場合 */ public void jump () {} /** * 走る。 * <ul> * <li>走ったあとは状態が"running"になる。</li> * </ul> * @throws IllegalStateException ジャンプ中の場合 */ public void run () throws IllegalStateException {} /** * 止まる。 * <ul> * <li>止まったあとは状態が"stopped"になる。</li> * </ul> */ public void stop () {} }
2.Behaviorクラスを書く。
Behaviorクラスを書きます。
- Kittenクラスの振る舞いなので名前はKittenBehaviorとします。
- publicな「should」メソッドを定義し、振る舞いの検証コードを書きます。
- 振る舞いの検証にはEnsureを使います。また、Ensureでの値の比較にはUsingMatchersを使います。(抽象クラスなので、継承して使う必要があります。サンプルでは無名クラスとして継承しインスタンス化しています。)
- 振る舞いの英訳が面倒なのでshouldメソッドは日本語でいいよね。
import org.jbehave.core.Block; import org.jbehave.core.Ensure; import org.jbehave.core.mock.UsingMatchers; /** * {@link Kitten}の振る舞い。 * * @version $Revision:$ * @author $Author:$ */ public class KittenBehavior { private static final UsingMatchers m = new UsingMatchers() {}; public void should_初期状態はstopped () { Kitten kitten = new Kitten(); Ensure.that( kitten.getState(), m.is( "stopped" ) ); } public void should_走ったあとは状態がrunningになる () { Kitten kitten = new Kitten(); kitten.run(); Ensure.that( kitten.getState(), m.is( "running" ) ); } public void should_ジャンプしたあとは状態がjumpingになる () { Kitten kitten = new Kitten(); kitten.jump(); Ensure.that( kitten.getState(), m.is( "jumping" ) ); } public void should_止まったあとは状態がstoppedになる () { Kitten kitten = new Kitten(); kitten.stop(); Ensure.that( kitten.getState(), m.is( "stopped" ) ); } public void should_走っている時にジャンプした場合IllegalStateExceptionになる () throws Exception { final Kitten kitten = new Kitten(); kitten.run(); Ensure.throwsException( IllegalStateException.class, new Block() { public void run () throws Exception { kitten.jump(); } } ); } public void should_ジャンプ中に走った場合IllegalStateExceptionになる () throws Exception { final Kitten kitten = new Kitten(); kitten.jump(); Ensure.throwsException( IllegalStateException.class, new Block() { public void run () throws Exception { kitten.run(); } } ); } }
3.Behaviorクラスを動かす。
org.jbehave.core.Runを使ってBehaviorクラスを動かし、テストに失敗するのを確認します。以下のコマンドを実行。
java -cp jbehave.jar org.jbehave.core.Run KittenBehavior
実行結果です。全滅です。
FFFFFF Time: 0.343s Failures: 6. 1) KittenBehavior should_ジャンプしたあとは状態がjumpingになる: VerificationException: Expected: same instance as <jumping> but got: <null>: at org.jbehave.core.mock.UsingMatchers.fail(UsingMatchers.java:300) at org.jbehave.core.mock.UsingMatchers.ensureThat(UsingMatchers.java:229) .... 省略 2) KittenBehavior should_止まったあとは状態がstoppedになる: VerificationException: Expected: same instance as <stopped> but got: <null>: at org.jbehave.core.mock.UsingMatchers.fail(UsingMatchers.java:300) at org.jbehave.core.mock.UsingMatchers.ensureThat(UsingMatchers.java:229) at org.jbehave.core.mock.UsingMatchers.ensureThat(UsingMatchers.java:237) .... 省略 3) KittenBehavior should_ジャンプ中に走った場合 illegal state exceptionになる: VerificationException: should have thrown java.lang.IllegalStateException: at org.jbehave.core.mock.UsingMatchers.fail(UsingMatchers.java:300) at org.jbehave.core.Ensure.throwsException(Ensure.java:101) .... 省略 4) KittenBehavior should_初期状態はstopped: VerificationException: Expected: same instance as <stopped> but got: <null>: at org.jbehave.core.mock.UsingMatchers.fail(UsingMatchers.java:300) at org.jbehave.core.mock.UsingMatchers.ensureThat(UsingMatchers.java:229) .... 省略 5) KittenBehavior should_走っている時にジャンプした場合 illegal state exceptionになる: VerificationException: should have thrown java.lang.IllegalStateException: at org.jbehave.core.mock.UsingMatchers.fail(UsingMatchers.java:300) at org.jbehave.core.Ensure.throwsException(Ensure.java:101) .... 省略 6) KittenBehavior should_走ったあとは状態がrunningになる: VerificationException: Expected: same instance as <running> but got: <null>: at org.jbehave.core.mock.UsingMatchers.fail(UsingMatchers.java:300) at org.jbehave.core.mock.UsingMatchers.ensureThat(UsingMatchers.java:229) .... 省略 Total: 6. Failures: 6.
4.テスト対象クラスを実装する。
テストに通るように、Kittenクラスを実装します。
/** * ねこ * * @version $Revision:$ * @author $Author:$ */ public class Kitten { /**状態*/ private String state = "stopped"; /*** * 状態を取得する。 * @return 状態 */ public String getState () { return state; } /** * ジャンプ。 * <ul> * <li>ジャンプしたあとは状態が"jumping"になる。</li> * </ul> * @throws IllegalStateException 走っている場合 */ public void jump () { if ( "running".equals( state ) ) { throw new IllegalStateException(); } this.state = "jumping"; } /** * 走る。 * <ul> * <li>走ったあとは状態が"running"になる。</li> * </ul> * @throws IllegalStateException ジャンプ中の場合 */ public void run () throws IllegalStateException { if ( "jumping".equals( state ) ) { throw new IllegalStateException(); } this.state = "running"; } /** * 止まる。 * <ul> * <li>止まったあとは状態が"stopped"になる。</li> * </ul> */ public void stop () { this.state = "stopped"; } }
5.再度Behaviorクラスを動かす。
実装したら再度Behaviorクラスを動かします。
java -cp jbehave.jar org.jbehave.core.Run KittenBehavior
実行結果です。
...... Time: 0.391s Total: 6. Success!