BDDの話をしよう
Java Advent Calendar -ja 2011の1日目のエントリーをお送りします。カレンダーが進むにつれて高度な話や高ブクマのエントリーが増えてハードルがあがっている気がしますが、ネタ臭香ばしく行きましょう。
では、本題です。Google Codeにtumbler-glassというライブラリがあったので試してみました。「coding-by-example library」と説明があるように、サンプル(例)からコードを書いていくためのライブラリで、BDD(ビヘイビア駆動開発)を促進し、「考える」事をサポートするらしいです。どうも無理に独自の動きを追求せずにJUnitを薄くラップしているライブラリのようです。
シナリオを書く
シナリオは最初からJavaのコードで書くか、テキストで起こして変換します。テキストから変換する方がプログラマでないマネージャなどにウケがいいよね、とか書かれてます。
シナリオはほぼフリーフォーマットで、最初にStoryやScenario等のキーワードがあれば後ろの文章を拾うようです。
Story: el shaddai Scenario: be attacked by enemies and die Given イーノック And ノーマル装備 When 大丈夫だ、問題ない Then 死ぬ Scenario: destroy all enemies Given イーノック And ノーマル装備 When 一番いいのを頼む Then 勝利する
Storyの部分がシナリオクラスになり、Scenarioが1つのテストメソッドになります。StoryとScenarioはクラス名やメソッド名になるのですが、日本語が含まれると無視されるため、英語表記にする必要がありました。
Scenarioの下はGiven、When、Thenと続きます。Givenは初期状態や前提となる状況、Whenがこのシナリオで起こるイベント、Thenがその結果です。つまり、最初のシナリオでは、「イーノックがノーマル装備という前提で、大丈夫だ問題ないとそのままなにもしなかった場合、死ぬ」というシナリオになるわけです。
このようにBDD(ビヘイビア駆動開発)では、振る舞い(仕様、要求)を自然言語に近い形で記述し、それを満たすプログラムを作成するというアプローチです。有名な所はRSpecでしょう。JavaはDSLにするには文法の制約が厳しいのであまり流行っていないと思います。
シナリオをコンバートする
シナリオはコンバータを使ってJavaの単体テストクラスに変換が可能です。変換すると次のようなファイルが生成されます。
import org.junit.*; import org.junit.runner.*; import tumbler.*; import static org.junit.Assert.*; import static tumbler.Tumbler.*; @RunWith(TumblerRunner.class) @Story("el shaddai") public class ElShaddaiScenarios { @Scenario(pending = true) public void shouldBeAttackedByEnemiesAndDie() { Given("イーノック and ノーマル装備"); When("大丈夫だ、問題ない"); Then("死ぬ"); } @Scenario(pending = true) public void shouldDestroyAllEnemies() { Given("イーノック and ノーマル装備"); When("一番いいのを頼む"); Then("勝利する"); }}
この割り切り感は好きです。
尚、このようなJavaのコードを最初からJavaのソースコードとして書いても構いません。重要なのはシナリオを先に考えることですから*1。@RunWith(TumblerRunner.class)から解るように、このシナリオはJUnitのテストとして走らせることができます。pendingがtrueになっているものはIgnoreとして扱われれます。
シナリオを完成させる
シナリオは出来たので実装を加えていきます。この実装は開発中のものであり、製品版と異なる場合がありますがご了承ください。
@Scenario public void shouldBeAttackedByEnemiesAndDie() { Given("イーノック and ノーマル装備"); Enoch enoch = new Enoch(); enoch.equiq(new NormalArmor()); When("大丈夫だ、問題ない"); // do nothing Then("死ぬ"); assertThat(enoch.battle(), is(Status.DEAD)); } @Scenario public void shouldDestroyAllEnemies() { Given("イーノック and ノーマル装備"); Enoch enoch = new Enoch(); enoch.equiq(new NormalArmor()); When("一番いいのを頼む"); enoch.equiq(new AngelArmor()); Then("勝利する"); assertThat(enoch.battle(), is(Status.ALIVE)); }
実装については割愛します。
単体テストでもシナリオ的なテストを作る場合はこのように書くような気がしますが気にしないことにしましょう。
レポートの作成
tumblerの目玉として、綺麗なレポートを作成することができます。JUnitの実行時にオプションで「-DgenerateReport=html」を指定するとHTMLで出力されます(FreeMarkerのjarが必要)。
尚、テキスト出力も可能です。
まとめ
個人的にはRSpecのようなBDDは好みではない*2のですが、振る舞いを考えて設計することは重要だという意見は変わりません。自分の場合はアプローチとしてユーザシナリオ(ユースケースシナリオ)をテストケースとして利用できるような形にする、という事をよくやるのですが、GUIが多いこともあり自動化までは行えない場合が多いです。
tumblerでは、振る舞いをJUnitのテストケースに先に記述しておくという感覚で導入しているのがかえって使いやすいかも?と感じました。また、シナリオをレポートとして出力できるのは、非プログラマのマネージャなどにはウケがいいと感じます*3。ただ、日本語が扱いにくいことは厳しいと思います。また、BDD自体をどのように導入すると効果が高いのかも探っていきたいですね。
というわけで次のエントリーは、id:cactusman1980 です。