はじめに#
TDD(テスト駆動開発)を実践していると、「テストは書いているが、仕様として読みづらい」「開発者以外がテストを理解できない」という課題に直面することがあります。技術的には正しいテストでも、ビジネス要件との対応が見えにくくなってしまうのです。
BDD(Behavior-Driven Development:振る舞い駆動開発) は、この課題を解決するために生まれた開発手法です。2003年にDan North氏がTDDの改良版として提唱し、「ビジネス価値に焦点を当てたテスト」という新しい視点をもたらしました。
本記事では、BDDとTDDの本質的な違い、Gherkin記法によるシナリオ作成、そして両者を組み合わせた効果的な開発フローまで、実践的なコード例とともに解説します。この記事を読み終える頃には、仕様として読めるテストを書き、開発者とビジネスサイドの共通言語を構築できるようになっているでしょう。
BDDとは何か#
BDD(Behavior-Driven Development)とは、ソフトウェアの振る舞いを自然言語に近い形式で記述し、それを実行可能な仕様として活用する開発手法です。
BDDの定義と背景#
Cucumber公式ドキュメントによると、BDDは以下の3つの要素でビジネスと技術の間のギャップを埋めます。
- 役割を越えたコラボレーション: 解決すべき問題について共通理解を構築する
- 迅速で小さなイテレーション: フィードバックと価値の流れを加速させる
- 自動チェック可能なドキュメント: システムの振る舞いを検証可能な形式で文書化する
BDDは「テストを書く」ことよりも「会話を促進する」ことに重点を置いています。テストコードはその会話の副産物であり、実行可能な仕様書として機能します。
BDDの3つのプラクティス#
BDDは日々の活動において、以下の3つのプラクティスを繰り返し実践します。
graph LR
subgraph "Discovery"
A[What it could do<br/>何ができるか]
end
subgraph "Formulation"
B[What it should do<br/>何をすべきか]
end
subgraph "Automation"
C[What it actually does<br/>実際に何をするか]
end
A --> B
B --> C
C --> A
style A fill:#e3f2fd,stroke:#1565c0,color:#000000
style B fill:#fff3e0,stroke:#e65100,color:#000000
style C fill:#e8f5e9,stroke:#2e7d32,color:#000000
| プラクティス |
目的 |
成果物 |
| Discovery |
具体例を通じて要件を探索し、共通理解を構築 |
具体的なシナリオ例 |
| Formulation |
例を構造化された文書として形式化 |
Gherkin形式の仕様書 |
| Automation |
形式化した例を自動テストとして実装 |
実行可能な受け入れテスト |
BDDとTDDの本質的な違い#
BDDとTDDは密接に関連していますが、その焦点と目的には明確な違いがあります。
比較表:BDD vs TDD#
| 観点 |
TDD(テスト駆動開発) |
BDD(振る舞い駆動開発) |
| 起点 |
技術的な機能要件 |
ビジネス価値・ユーザーストーリー |
| 記述言語 |
プログラミング言語 |
自然言語に近い形式(Gherkin等) |
| 読者 |
開発者 |
開発者、QA、ビジネスサイド |
| 粒度 |
ユニット(関数・メソッド) |
振る舞い(ユーザーシナリオ) |
| 目的 |
正しく動作するコードの作成 |
正しい振る舞いの定義と検証 |
| アサーション |
技術的な期待値検証 |
ビジネス的な期待結果の検証 |
アプローチの違いを図解#
graph TB
subgraph "TDD: Inside-Out アプローチ"
T1[ユニットテスト作成]
T2[最小限の実装]
T3[リファクタリング]
T4[統合テストへ拡大]
T1 --> T2 --> T3 --> T4
end
subgraph "BDD: Outside-In アプローチ"
B1[ユーザーストーリー定義]
B2[シナリオ作成]
B3[ステップ定義実装]
B4[内部実装詳細化]
B1 --> B2 --> B3 --> B4
end
style T1 fill:#ffcccc,stroke:#cc0000,color:#000000
style T2 fill:#ccffcc,stroke:#00cc00,color:#000000
style T3 fill:#cce5ff,stroke:#0066cc,color:#000000
style T4 fill:#f0f0f0,stroke:#666666,color:#000000
style B1 fill:#e3f2fd,stroke:#1565c0,color:#000000
style B2 fill:#fff3e0,stroke:#e65100,color:#000000
style B3 fill:#e8f5e9,stroke:#2e7d32,color:#000000
style B4 fill:#f0f0f0,stroke:#666666,color:#000000視点の違い:「どう動くか」vs「何をすべきか」#
TDDでは「この関数は引数3に対して"Fizz"を返す」という実装の検証に焦点を当てます。
1
2
3
4
5
6
|
// TDDスタイル:技術的な視点
@Test
void shouldReturnFizzWhenDivisibleByThree() {
FizzBuzz fizzBuzz = new FizzBuzz();
assertEquals("Fizz", fizzBuzz.convert(3));
}
|
BDDでは「3の倍数が入力されたとき、“Fizz"と表示される」という振る舞いの仕様に焦点を当てます。
1
2
3
4
5
6
7
8
9
10
|
# BDDスタイル:ビジネス的な視点
Feature: FizzBuzz Game
As a player
I want to know the FizzBuzz result
So that I can play the game correctly
Scenario: Number divisible by three
Given I have the number 3
When I check the FizzBuzz result
Then I should see "Fizz"
|
Given-When-Then形式の理解#
BDDの核心は、Given-When-Then形式によるシナリオ記述です。この形式は、テストを「前提条件」「アクション」「期待結果」の3つのパートで構造化します。
Given-When-Thenの役割#
| キーワード |
役割 |
説明 |
| Given |
前提条件 |
システムの初期状態を設定(過去形で記述) |
| When |
アクション |
ユーザーやシステムが行う操作(現在形で記述) |
| Then |
期待結果 |
アクション後に期待される結果(未来の結果として記述) |
| And / But |
補助 |
同じ種類のステップを追加するための接続詞 |
効果的なシナリオの書き方#
良いシナリオを書くためのベストプラクティスを見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 良い例:具体的で読みやすい
Feature: User Authentication
Users should be able to securely log in to their accounts
Scenario: Successful login with valid credentials
Given a registered user with email "alice@example.com"
And the user's password is "SecurePass123"
When the user attempts to log in with email "alice@example.com" and password "SecurePass123"
Then the user should be authenticated successfully
And the user should be redirected to the dashboard
Scenario: Failed login with incorrect password
Given a registered user with email "bob@example.com"
When the user attempts to log in with email "bob@example.com" and password "WrongPassword"
Then the user should see an error message "Invalid email or password"
And the user should remain on the login page
|
アンチパターンを避ける#
シナリオ作成時に避けるべきアンチパターンも理解しておきましょう。
1
2
3
4
5
6
7
8
9
|
# 悪い例:実装詳細が露出している
Scenario: User login
Given I open Chrome browser
And I navigate to "https://example.com/login"
And I find element with id "email-input"
When I type "test@example.com" into the email field
And I click the button with class "submit-btn"
Then I should see HTTP status 200
And the database should contain a session record
|
1
2
3
4
5
|
# 良い例:振る舞いに焦点を当てている
Scenario: User login
Given I am on the login page
When I log in with valid credentials
Then I should be on the dashboard
|
Gherkin記法の基礎#
Gherkinは、BDDシナリオを記述するための構造化言語です。人間が読める形式でありながら、自動テストツールが解釈できる規則を持っています。
Gherkinの主要キーワード#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# language: ja
機能: ショッピングカート管理
オンラインショップの顧客として
商品をカートに追加・削除したい
購入前に注文内容を確認できるように
背景:
前提 ログイン済みのユーザーである
かつ カートが空である
シナリオ: 商品をカートに追加
前提 商品「プログラミング入門書」が在庫にある
もし 「プログラミング入門書」をカートに追加する
ならば カートに「プログラミング入門書」が1個入っている
かつ カートの合計金額が表示される
シナリオアウトライン: 複数商品の追加
もし 「<商品名>」を<個数>個カートに追加する
ならば カートに「<商品名>」が<個数>個入っている
例:
| 商品名 | 個数 |
| JavaScript入門 | 2 |
| React実践ガイド | 1 |
| TypeScript応用 | 3 |
|
英語版の構文#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
Feature: Shopping Cart Management
As an online shop customer
I want to add and remove items from my cart
So that I can review my order before purchase
Background:
Given I am a logged-in user
And my cart is empty
Scenario: Add item to cart
Given the product "Programming Guide" is in stock
When I add "Programming Guide" to my cart
Then my cart should contain 1 "Programming Guide"
And the cart total should be displayed
Scenario Outline: Add multiple items
When I add <quantity> of "<product>" to my cart
Then my cart should contain <quantity> of "<product>"
Examples:
| product | quantity |
| JavaScript Basics | 2 |
| React Practical Guide| 1 |
| TypeScript Advanced | 3 |
|
Data Tablesの活用#
複雑なデータを扱う場合は、Data Tablesを使用します。
1
2
3
4
5
6
7
8
|
Scenario: Register multiple users
Given the following users exist:
| name | email | role |
| Alice | alice@example.com | admin |
| Bob | bob@example.com | user |
| Charlie | charlie@example.com| user |
When I view the user list
Then I should see 3 users
|
JavaScriptでのBDD実装(JestとCucumber)#
JavaScriptエコシステムでは、Jestをベースにしたjest-cucumberや、Cucumber.jsを使ってBDDを実践できます。
Jestでのbdd-lazyライブラリを使った実装#
まず、シンプルにJestでBDDスタイルのテストを書く方法を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
// shopping-cart.spec.js
describe('Shopping Cart', () => {
describe('Feature: Add items to cart', () => {
let cart
let product
describe('Scenario: Add a single item to empty cart', () => {
// Given
beforeEach(() => {
cart = new ShoppingCart()
product = { id: 1, name: 'JavaScript Guide', price: 2500 }
})
test('Given the cart is empty', () => {
expect(cart.items).toHaveLength(0)
})
test('When I add a product to the cart', () => {
cart.addItem(product)
})
test('Then the cart should contain 1 item', () => {
cart.addItem(product)
expect(cart.items).toHaveLength(1)
})
test('And the cart total should equal the product price', () => {
cart.addItem(product)
expect(cart.total).toBe(2500)
})
})
})
})
|
Cucumber.jsによる本格的なBDD実装#
Cucumber.jsを使用すると、Gherkinファイルとステップ定義を分離した本格的なBDD開発が可能です。
ディレクトリ構造:
project/
├── features/
│ ├── shopping-cart.feature
│ └── step-definitions/
│ └── shopping-cart.steps.js
├── src/
│ └── shopping-cart.js
├── package.json
└── cucumber.js
features/shopping-cart.feature:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
Feature: Shopping Cart
As a customer
I want to manage items in my shopping cart
So that I can purchase products I need
Scenario: Add item to empty cart
Given I have an empty shopping cart
When I add a product "JavaScript Guide" priced at 2500 yen
Then my cart should contain 1 item
And the cart total should be 2500 yen
Scenario: Add multiple quantities of same item
Given I have an empty shopping cart
When I add 3 of product "React Tutorial" priced at 3000 yen each
Then my cart should contain 3 items
And the cart total should be 9000 yen
Scenario: Remove item from cart
Given I have a cart with the following items:
| name | price | quantity |
| JavaScript Guide| 2500 | 1 |
| React Tutorial | 3000 | 2 |
When I remove "React Tutorial" from the cart
Then my cart should contain 1 item
And the cart total should be 2500 yen
|
features/step-definitions/shopping-cart.steps.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
const { Given, When, Then } = require('@cucumber/cucumber')
const { expect } = require('chai')
const ShoppingCart = require('../../src/shopping-cart')
let cart
Given('I have an empty shopping cart', function () {
cart = new ShoppingCart()
})
Given('I have a cart with the following items:', function (dataTable) {
cart = new ShoppingCart()
const items = dataTable.hashes()
items.forEach(item => {
const product = {
name: item.name,
price: parseInt(item.price)
}
for (let i = 0; i < parseInt(item.quantity); i++) {
cart.addItem(product)
}
})
})
When('I add a product {string} priced at {int} yen', function (name, price) {
cart.addItem({ name, price })
})
When('I add {int} of product {string} priced at {int} yen each', function (quantity, name, price) {
for (let i = 0; i < quantity; i++) {
cart.addItem({ name, price })
}
})
When('I remove {string} from the cart', function (productName) {
cart.removeItem(productName)
})
Then('my cart should contain {int} item(s)', function (expectedCount) {
expect(cart.itemCount).to.equal(expectedCount)
})
Then('the cart total should be {int} yen', function (expectedTotal) {
expect(cart.total).to.equal(expectedTotal)
})
|
src/shopping-cart.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class ShoppingCart {
constructor() {
this.items = []
}
addItem(product) {
this.items.push(product)
}
removeItem(productName) {
this.items = this.items.filter(item => item.name !== productName)
}
get itemCount() {
return this.items.length
}
get total() {
return this.items.reduce((sum, item) => sum + item.price, 0)
}
}
module.exports = ShoppingCart
|
JavaでのBDD実装(JUnitとCucumber)#
Javaエコシステムでは、Cucumber-JVMを使用してBDDを実践できます。
プロジェクト構成#
pom.xml(依存関係):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<dependencies>
<!-- Cucumber -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.10.3</version>
<scope>test</scope>
</dependency>
<!-- Assertions -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.0</version>
<scope>test</scope>
</dependency>
</dependencies>
|
ディレクトリ構造:
src/
├── main/java/com/example/
│ └── ShoppingCart.java
└── test/
├── java/com/example/
│ ├── RunCucumberTest.java
│ └── steps/
│ └── ShoppingCartSteps.java
└── resources/features/
└── shopping-cart.feature
Featureファイル#
src/test/resources/features/shopping-cart.feature:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
Feature: Shopping Cart Management
As an online shop customer
I want to manage my shopping cart
So that I can purchase the products I need
Background:
Given the following products are available:
| id | name | price |
| 101 | Java入門 | 3500 |
| 102 | Spring Boot実践 | 4200 |
| 103 | JUnit完全ガイド | 2800 |
Scenario: Add single item to empty cart
Given I have an empty cart
When I add product with id 101 to my cart
Then my cart should have 1 item
And the cart total should be 3500 yen
Scenario: Calculate total for multiple items
Given I have an empty cart
When I add product with id 101 to my cart
And I add product with id 102 to my cart
Then my cart should have 2 items
And the cart total should be 7700 yen
Scenario Outline: Apply discount based on total
Given I have items totaling <subtotal> yen in my cart
When I apply the discount rules
Then the discount should be <discount> percent
And the final total should be <final> yen
Examples:
| subtotal | discount | final |
| 5000 | 0 | 5000 |
| 10000 | 5 | 9500 |
| 20000 | 10 | 18000 |
|
ステップ定義#
src/test/java/com/example/steps/ShoppingCartSteps.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
package com.example.steps;
import com.example.Product;
import com.example.ShoppingCart;
import io.cucumber.datatable.DataTable;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.And;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class ShoppingCartSteps {
private Map<Integer, Product> availableProducts = new HashMap<>();
private ShoppingCart cart;
private int appliedDiscount;
@Given("the following products are available:")
public void theFollowingProductsAreAvailable(DataTable dataTable) {
List<Map<String, String>> rows = dataTable.asMaps();
for (Map<String, String> row : rows) {
int id = Integer.parseInt(row.get("id"));
String name = row.get("name");
int price = Integer.parseInt(row.get("price"));
availableProducts.put(id, new Product(id, name, price));
}
}
@Given("I have an empty cart")
public void iHaveAnEmptyCart() {
cart = new ShoppingCart();
}
@Given("I have items totaling {int} yen in my cart")
public void iHaveItemsTotalingYenInMyCart(int total) {
cart = new ShoppingCart();
// ダミー商品を追加して合計金額を設定
cart.addItem(new Product(999, "Dummy", total));
}
@When("I add product with id {int} to my cart")
public void iAddProductWithIdToMyCart(int productId) {
Product product = availableProducts.get(productId);
cart.addItem(product);
}
@When("I apply the discount rules")
public void iApplyTheDiscountRules() {
appliedDiscount = cart.calculateDiscountPercent();
}
@Then("my cart should have {int} item(s)")
public void myCartShouldHaveItems(int expectedCount) {
assertThat(cart.getItemCount()).isEqualTo(expectedCount);
}
@Then("the cart total should be {int} yen")
public void theCartTotalShouldBeYen(int expectedTotal) {
assertThat(cart.getSubtotal()).isEqualTo(expectedTotal);
}
@Then("the discount should be {int} percent")
public void theDiscountShouldBePercent(int expectedDiscount) {
assertThat(appliedDiscount).isEqualTo(expectedDiscount);
}
@And("the final total should be {int} yen")
public void theFinalTotalShouldBeYen(int expectedFinal) {
int finalTotal = cart.calculateFinalTotal();
assertThat(finalTotal).isEqualTo(expectedFinal);
}
}
|
テストランナー#
src/test/java/com/example/RunCucumberTest.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package com.example;
import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;
import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.steps")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty, html:target/cucumber-reports.html")
public class RunCucumberTest {
}
|
BDDとTDDを組み合わせた開発フロー#
BDDとTDDは対立する概念ではなく、補完的な関係にあります。両者を組み合わせることで、より効果的な開発フローを構築できます。
Double Loop TDD#
Outside-In TDDとも呼ばれるこのアプローチでは、外側のBDDループと内側のTDDループを組み合わせます。
graph TB
subgraph "外側のループ(BDD)"
A[受け入れテスト作成<br/>Red] --> B[機能実装<br/>Green]
B --> C[シナリオ完了<br/>Refactor]
C --> A
end
subgraph "内側のループ(TDD)"
D[ユニットテスト作成<br/>Red] --> E[最小実装<br/>Green]
E --> F[リファクタリング]
F --> D
end
B -.-> D
F -.-> B
style A fill:#ffcccc,stroke:#cc0000,color:#000000
style B fill:#ccffcc,stroke:#00cc00,color:#000000
style C fill:#cce5ff,stroke:#0066cc,color:#000000
style D fill:#ffcccc,stroke:#cc0000,color:#000000
style E fill:#ccffcc,stroke:#00cc00,color:#000000
style F fill:#cce5ff,stroke:#0066cc,color:#000000実践的なワークフロー#
- ユーザーストーリーの作成: ビジネス要件をユーザーストーリーとして定義
- Discoveryセッション: ステークホルダーと具体例を探索
- シナリオの形式化: Gherkin形式でシナリオを記述(BDD)
- 受け入れテストの作成: シナリオを実行可能なテストに変換
- TDDで内部実装: Red-Green-Refactorサイクルでコンポーネントを実装
- 受け入れテストがパス: 外側のループが完了
- 次のシナリオへ: 繰り返し
具体例:ユーザー登録機能#
Step 1: BDDシナリオ作成
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Feature: User Registration
As a new user
I want to register an account
So that I can access the application
Scenario: Successful registration with valid data
Given I am on the registration page
When I register with the following details:
| email | password | name |
| test@example.com | SecurePass123 | Taro |
Then my account should be created
And I should receive a welcome email
And I should be logged in automatically
|
Step 2: 受け入れテスト作成(Red状態)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Given("I am on the registration page")
public void iAmOnTheRegistrationPage() {
registrationPage = new RegistrationPage(driver);
registrationPage.navigate();
}
@When("I register with the following details:")
public void iRegisterWithTheFollowingDetails(DataTable dataTable) {
Map<String, String> data = dataTable.asMaps().get(0);
registrationPage.fillForm(
data.get("email"),
data.get("password"),
data.get("name")
);
registrationPage.submit();
}
@Then("my account should be created")
public void myAccountShouldBeCreated() {
// この時点ではUserServiceが存在しないため失敗
assertThat(userService.findByEmail("test@example.com")).isPresent();
}
|
Step 3: 内側のTDDループでUserServiceを実装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// TDDサイクル1: ユーザー保存機能
@Test
void shouldSaveNewUser() {
// Red: テストを書く
UserService service = new UserService(userRepository);
User user = new User("test@example.com", "SecurePass123", "Taro");
service.register(user);
verify(userRepository).save(user);
}
// TDDサイクル2: メール送信機能
@Test
void shouldSendWelcomeEmailAfterRegistration() {
UserService service = new UserService(userRepository, emailService);
User user = new User("test@example.com", "SecurePass123", "Taro");
service.register(user);
verify(emailService).sendWelcomeEmail("test@example.com", "Taro");
}
// TDDサイクル3: 重複チェック
@Test
void shouldRejectDuplicateEmail() {
when(userRepository.existsByEmail("existing@example.com")).thenReturn(true);
UserService service = new UserService(userRepository, emailService);
User user = new User("existing@example.com", "Pass123", "User");
assertThrows(DuplicateEmailException.class, () -> service.register(user));
}
|
Step 4: 受け入れテストがパス(Green状態)
すべてのユニットテストがパスし、コンポーネントが完成したら、受け入れテストもパスします。
BDDを導入すべきシーンと避けるべきシーン#
BDDはすべてのプロジェクトに適しているわけではありません。導入の判断基準を理解しましょう。
BDDが効果的なシーン#
| シーン |
理由 |
| 複雑なビジネスロジック |
仕様の明文化により、認識のずれを防げる |
| ステークホルダーとの協業が多い |
非エンジニアにも読めるドキュメントになる |
| 長期間メンテナンスするシステム |
実行可能な仕様書として価値が持続する |
| アジャイル開発チーム |
ユーザーストーリーとの親和性が高い |
| 規制が厳しい業界(金融・医療等) |
監査可能なドキュメントとして機能する |
BDDが向かないシーン#
| シーン |
理由 |
| プロトタイプ開発 |
仕様が頻繁に変わりオーバーヘッドになる |
| 技術的なライブラリ開発 |
ユーザーストーリーより技術仕様が重要 |
| 小規模な個人プロジェクト |
コミュニケーションコストがそもそも低い |
| 非常にシンプルなCRUDアプリ |
仕様書なしでも明確なケース |
BDDツールエコシステム#
主要なBDDツールを把握し、プロジェクトに適したものを選択しましょう。
言語別BDDツール比較#
| 言語/環境 |
ツール |
特徴 |
| JavaScript |
Cucumber.js |
Gherkin完全サポート、Node.js環境で動作 |
| JavaScript |
jest-cucumber |
JestとCucumberの統合 |
| Java |
Cucumber-JVM |
JUnit統合、豊富なレポート機能 |
| Java |
JBehave |
Story形式のBDDフレームワーク |
| Python |
Behave |
Gherkinサポート、pytest統合 |
| Ruby |
Cucumber |
BDDの元祖、Rubyネイティブ |
| .NET |
SpecFlow |
Visual Studio統合、C#/VB対応 |
| Go |
Godog |
Cucumber公式のGo実装 |
レポート機能#
Cucumberは様々なフォーマットでレポートを出力できます。
1
2
3
4
5
6
7
8
|
# HTML形式レポート
cucumber-js --format html:reports/cucumber-report.html
# JSON形式レポート(CI/CD連携用)
cucumber-js --format json:reports/cucumber-report.json
# JUnit XML形式(CI/CDツール連携)
cucumber-js --format junit:reports/cucumber-junit.xml
|
まとめ#
BDDとTDDは、それぞれ異なる視点からソフトウェア開発を支援するアプローチです。
TDDの焦点:
- 技術的な正しさの検証
- Red-Green-Refactorによる設計改善
- 開発者間のコミュニケーション
BDDの焦点:
- ビジネス価値の明確化
- ステークホルダーとの共通言語構築
- 実行可能な仕様書としてのテスト
両者を組み合わせたDouble Loop TDDを実践することで、以下のメリットが得られます。
- ビジネス要件と実装の一貫性確保
- 非エンジニアにも理解可能なドキュメント
- 堅牢なリグレッションテストスイート
- 仕様変更への迅速な対応
BDDは単なるテスト技法ではなく、チーム全体のコミュニケーションを改善するためのプラクティスです。Gherkin記法やCucumberといったツールは、その目的を達成するための手段に過ぎません。重要なのは、ビジネスと技術の間のギャップを埋め、顧客に価値を届け続けることです。
参考リンク#