
はじめに
前回は、テスト駆動開発(Test-driven development、以下TDD)とは何か、TDDの目的は何かについて話しました。今回は、振る舞い駆動開発(Behavior Driven Development、以下BDD)が考案された経緯と、specBDDとscenarioBDDという2種類のBDDの違いについて説明します。

BDDの誕生
BDDはDan Northによって考えられたものです。2006年に自身の記事「Introducing BDD(邦訳:BDDの導入 – Dan North)」でBDDという考えに至った経緯が述べられています。
Danの考えたBDDでは、TDDのルールから2つの変更を入れました。
・クラス名やメソッド名から「test」という単語を取り除く
・テストメソッド名は「should」という単語で始める(日本語の場合、『〜〜すべき』という単語で終わる)ようにする
これによって、より「振る舞い(特にユーザー視点での振る舞い)」に注目するようになりました。
Danによると、コードを変更してテストが失敗した時には、以下の3つに分類できます。
・バグを埋め込んでしまった。悪い子だ。解決法:バグを直す
BDDの導入 – Dan Northから引用
・意図されたふるまいは変わらず関連性があったが、どこか別の場所に移されていた。解決法:テストを移動し、場合によっては変更する。
・ふるまいがもはや正しくない。システムの前提が変わってしまっている。解決法:テストを削除する。
このうち、3つ目の解決法「テストを削除する。」に対して、BDDは大きな効果をもたらします。
元々のTDDでは、「testメソッドを削除する」という行動になってしまい、「本当にテストを削除して良いの?大丈夫?」と不安になります。これは前回話した「TDDによって品質を保証している」という誤解によって、testメソッドそのものの削除に対して非常に億劫になってしまっているからです。
一方、BDDではその心理的ハードルを低くしています。「振る舞いが違うのであれば、『should』で始まっている振る舞いの定義を削除した方が良いよね」という考え方に持っていこうとしています。
Liz Keoghは有識者との議論の中で、「『テスト』という言葉を使用する際の問題は、誰もが自分が何をテストしているのかを知っていると想定していることです。」と語っています。逆を言うと、BDDは事実を知らないという前提に立ちやすいと言えます。
振る舞いに注目することのメリット
BDDによってユーザー視点での振る舞いに注目すると良いことがもう一つあります。振る舞いに注目すると内部構造に注目せずに済む可能性が高くなります。これは、TDDではあまり得られない(得ることは可能だが、なかなか得ることに気付きにくい)効果でもあります。
例えば、自動販売機についてのプログラミングを考えた時、振る舞いに注目していない例として以下のようなテストコードを書くかもしれません。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VendingMachineTest {
@Test
void _残高の引き算のテスト() {
VendingMachine vendingMachine = new VendingMachine();
vendingMachine.setCredit(500); //500をセットする
vendingMachine.minusFromCredit(100); //100を引く
assertEquals(400, vendingMachine.getCredit()); //400になる
}
}
これはTDDで書くことによって、プロダクトコードのロジックも作成できます。
ですが、果たしてこのコードは自動販売機を表すことができているのでしょうか?あまりにも内部構造に注目しすぎています。
一方で、ユーザー視点での振る舞いに注目すると以下のように書くことができます。今回は、Given/When/Thenの形で書くことにします。(Given/When/Thenの形をGherkin記法と呼びます。Gherkin記法については第6回の時に説明します)
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VendingMachineBehave {
@Test
void _1000円札を投入後100円のコーラを購入するとコーラと900円のお釣りが出るべき() {
//前提(Given)
VendingMachine vendingMachine = new VendingMachine();
Coin coin = new Coin(1000);
Goods drinkGoods = new Goods("Cola", 100);
//動作(When)
vendingMachine.insertCoin(coin); //お金を入れる
vendingMachine.purchase(drinkGoods); //商品を購入する
//結果(Then)
assertEquals("Cola", vendingMachine.dispensingDrink()); //商品が出てくる
assertEquals(900, vendingMachine.getChange()); //お釣りが出てくる
}
}
これぞまさに、自動販売機での振る舞いであると感じることができるでしょう。
BDDを行う上で大切にすべきこと
Liz Keoghは先ほどと同じ有識者との議論の場で、BDDを進めていく前提の考えを定義しました。
・間違っていると仮定する ・どのように間違っているかを理解するために会話する ・十分なフィードバックを得られたら実装する |
つまり、振る舞いに注目し、認識が間違っているところがないか議論することこそがBDDで大切にしていることだと捉えたのです。
先ほどの、ユーザー視点での自動販売機の振る舞いに注目した例をもう一度見てみましょう。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VendingMachineBehave {
@Test
void _1000円札を投入後100円のコーラを購入するとコーラと900円のお釣りが出るべき() {
//前提(Given)
VendingMachine vendingMachine = new VendingMachine();
Coin coin = new Coin(1000);
Goods drinkGoods = new Goods("Cola", 100);
//動作(When)
vendingMachine.insertCoin(coin); //お金を入れる
vendingMachine.purchase(drinkGoods); //商品を購入する
//結果(Then)
assertEquals("Cola", vendingMachine.dispensingDrink()); //商品が出てくる
assertEquals(900, vendingMachine.getChange()); //お釣りが出てくる
}
}
これを書くことで、色々な疑問が出てくるはずです。例えば以下のような疑問が出てくるかもしれません。
・1000円札の投入は本当に可能なのか?
・新1000円札と旧1000円札の両方で投入が可能なのか?
・購入した場合、必ずお釣りが出てくるのか?(お釣りが出てこないで別の飲み物が買えたりしないのか)
・900円のお釣りとは、500円硬貨1枚+100円硬貨4枚なのか、それとも100円硬貨9枚なのか?
このように、BDDでは振る舞いについて具体的に考えることで、色々な疑問を発見することを大切にしています。
specBDDとscenarioBDD
自動販売機の振る舞いに注目していない例(内部構造に寄りすぎた例)ですが、Danの作った2つのルール「クラス名やメソッド名から『test』という単語を取り除く」「テストメソッド名は『should』という単語で始める(日本語の場合、『〜〜すべき』という単語で終わる)ようにする」を適用してBDDっぽく書くことができます。
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VendingMachineBehave {
@Test
void _500円から100円を引くと400円になるべき() {
//前提(Given)
VendingMachine vendingMachine = new VendingMachine();
//動作(When)
vendingMachine.setCredit(500);
vendingMachine.minusFromCredit(100);
//結果(Then)
assertEquals(400, vendingMachine.getCredit());
}
}
このように、内部構造に寄っており実装レベルの仕様を表現しているBDDを、Behatの作者であるKonstantin KudryashovはspecBDDと名付けました。
一方、お釣りについて記述していた例のように、ユーザー視点の振る舞いに注目して表現したBDDを、Konstantin KudryashovはscenarioBDDと名付けました。scenarioBDDの別の言い方としてstoryBDDと呼ぶこともあります。
scenarioBDDに対応したフレームワークを用いて、ビジネス関係者にもより分かりやすい記述をする
scenarioBDDはユーザー視点の振る舞いを示そうとしているため、ビジネス関係者にも理解してもらいたいという意図があります(詳しくは第4回でお話しします)。
しかし、Javaなどのプログラミング言語を用いてGiven/When/Thenの形で書くだけだと、プログラミング言語特有の記述(例えばクラスの生成記述など)が気になってしまい、ビジネス関係者には理解してもらいづらいです。
そこで、よりビジネス関係者にも理解してもらいやすくするために、シナリオ部分と実装部分を分けて記述するフレームワークが出てきました。その代表的なフレームワークとしてCucumberがあります。
先ほどまで書いていたユーザー視点での自動販売機の振る舞いのシナリオ部分を、Cucumberを用いて書くと以下のようになります。
Feature: 自動販売機
Scenario: 飲み物を買うとお釣りが出る
Given 自動販売機がある
When 1000 円札を入れる
And 100 円の "コーラ" を選ぶ
Then "コーラ" が出てくる
And 900 円のお釣りが出る
この記述ならば、プログラミング言語に馴染みのないビジネス関係者にも理解してもらいやすくなります。
次回予告
今回はBDDの誕生とspecBDD/scenarioBDDの違いについて説明しました。
ただし、今回説明したscenarioBDDは本来考えるべきことのほんの一部に過ぎません。
実際にscenarioBDDで考えるべき具体的なプロセスおよび例については第4回以降に説明します。
次回は、BDDと振る舞い駆動開発(ATDD)/実例による仕様(Specification by Example)との関係性について話していきます。
連載一覧
TDDとBDD/ATDD(1) TDDはテスト手法ではない
TDDとBDD/ATDD(2) 2種類のBDD
TDDとBDD/ATDD(3) BDDとATDDとSbE
TDDとBDD/ATDD(4) ツールとしてのBDDとプロセスに組み込まれたBDD
TDDとBDD/ATDD(5) BDDのプロセスその1「発見(Discovery)」と実例マッピング
TDDとBDD/ATDD(6) BDDのプロセスその2「定式化(Formulation)」とBRIEFの原則
TDDとBDD/ATDD(7) BDDのプロセスその3「自動化(Automation)」