GherkinとCucumberによるBDD
BDD(振る舞い駆動開発)とは
BDD(Behavior Driven Development:振る舞い駆動開発)は、ソフトウェアの「振る舞い」に焦点を当てた開発手法です。開発者、QA、ビジネス側の関係者が共通の言語で要件を記述し、その仕様をそのままテストコードとして実行できることが特徴です。
BDDの主な目的
- 共通言語の確立: 技術者と非技術者が同じ言語でコミュニケーション
- 仕様の文書化: 実行可能なテストが仕様書としても機能
- 要件の明確化: 「何を作るべきか」を明確にする
- リビングドキュメント: コードと仕様が常に同期
TDD(テスト駆動開発)との違い
| 項目 | TDD | BDD |
|---|---|---|
| 焦点 | コードの正しさ | システムの振る舞い |
| 記述者 | 主に開発者 | 開発者 + QA + ビジネス側 |
| テストの粒度 | ユニットテスト(関数・メソッド) | シナリオテスト(機能・フィーチャー) |
| 記述言語 | プログラミング言語 | 自然言語(Gherkinなど) |
| テストの目的 | コードの品質保証 | 要件の実現確認 |
| ドキュメント性 | 低い(技術者向け) | 高い(誰でも読める) |
| 開発プロセス | Red→Green→Refactor | 仕様記述→実装→検証 |
TDDとBDDの関係
- TDD: 「正しく作る(Building the thing right)」
- BDD: 「正しいものを作る(Building the right thing)」
BDDとTDDは対立するものではなく、補完関係にあります。BDDで要件レベルのテストを書き、TDDで実装レベルのテストを書くことで、包括的な品質保証が可能になります。
Gherkinとは
Gherkinは、BDDで使用される自然言語ベースの記述言語です。技術的な背景がない人でも読み書きできる構文で、ソフトウェアの振る舞いを記述します。
Gherkinの基本構文
Gherkinは以下のキーワードで構成されます:
- Feature: 機能の説明
- Scenario: 具体的なシナリオ
- Given: 前提条件
- When: 実行するアクション
- Then: 期待される結果
- And / But: 追加の条件やアクション
Gherkinの記述例
Feature: ユーザーログイン機能
ユーザーがシステムにログインできることを確認する
Scenario: 正しい認証情報でログイン成功
Given ユーザー "tanaka@example.com" がパスワード "password123" で登録済み
When ユーザーがメールアドレス "tanaka@example.com" とパスワード "password123" でログインを試みる
Then ログインが成功する
And ユーザーはダッシュボード画面にリダイレクトされる
And "ようこそ、田中さん" というメッセージが表示される
Scenario: 誤ったパスワードでログイン失敗
Given ユーザー "tanaka@example.com" がパスワード "password123" で登録済み
When ユーザーがメールアドレス "tanaka@example.com" とパスワード "wrongpass" でログインを試みる
Then ログインが失敗する
And "メールアドレスまたはパスワードが正しくありません" というエラーメッセージが表示される
Scenario: 存在しないユーザーでログイン失敗
Given システムに登録されていないメールアドレス "unknown@example.com"
When ユーザーがメールアドレス "unknown@example.com" とパスワード "anypass" でログインを試みる
Then ログインが失敗する
And "メールアドレスまたはパスワードが正しくありません" というエラーメッセージが表示される
Scenario Outline(パラメータ化)
複数のデータパターンでテストしたい場合は、Scenario OutlineとExamplesを使用します:
Feature: ショッピングカートの計算
Scenario Outline: 商品数量に応じた合計金額の計算
Given ショッピングカートが空である
When 単価 <単価> 円の商品を <数量> 個追加する
Then カートの合計金額は <合計> 円になる
Examples:
| 単価 | 数量 | 合計 |
| 100 | 1 | 100 |
| 100 | 5 | 500 |
| 250 | 3 | 750 |
| 1000 | 10 | 10000 |
Cucumberとは
Cucumberは、Gherkinで記述されたシナリオを実行可能なテストに変換するBDDフレームワークです。様々なプログラミング言語に対応しています。
対応言語
- Cucumber-JVM: Java, Kotlin, Scala
- Cucumber-Ruby: Ruby
- Cucumber-JS: JavaScript, TypeScript
- SpecFlow: C# (.NET) ※Cucumber相当
- Behave: Python ※Cucumber相当
Cucumberの動作フロー
- Featureファイル作成:
.feature拡張子でGherkinシナリオを記述 - Step Definitions作成: 各ステップに対応する実装コードを記述
- テスト実行: CucumberがFeatureファイルを読み込み、Step Definitionsを実行
- レポート生成: テスト結果をHTML、JSON、XMLなどで出力
┌─────────────────┐
│ Feature File │
│ (Gherkin) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Cucumber Runner │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Step Definitions│
│ (実装コード) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Application │
│ (テスト対象) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Test Report │
└─────────────────┘
実装例
JavaScript/TypeScript
1. プロジェクトのセットアップ
npm install --save-dev @cucumber/cucumber
npm install --save-dev @types/node typescript ts-node
2. Featureファイルの作成
features/login.feature
Feature: ユーザーログイン機能
Scenario: 有効な認証情報でログイン
Given ユーザーがログインページにアクセスする
When メールアドレス "user@example.com" を入力する
And パスワード "securepass" を入力する
And ログインボタンをクリックする
Then ダッシュボードページが表示される
And ウェルカムメッセージが表示される
3. Step Definitionsの実装
features/step_definitions/login.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from 'chai';
// テスト対象のクラス(例)
class LoginPage {
private email: string = '';
private password: string = '';
private isLoggedIn: boolean = false;
navigateTo() {
// ページ遷移の実装
}
enterEmail(email: string) {
this.email = email;
}
enterPassword(password: string) {
this.password = password;
}
clickLogin() {
// 実際の認証ロジック
if (this.email === 'user@example.com' && this.password === 'securepass') {
this.isLoggedIn = true;
}
}
isDashboardDisplayed(): boolean {
return this.isLoggedIn;
}
getWelcomeMessage(): string {
return this.isLoggedIn ? 'ようこそ' : '';
}
}
// World(コンテキスト)の定義
let loginPage: LoginPage;
Given('ユーザーがログインページにアクセスする', function () {
loginPage = new LoginPage();
loginPage.navigateTo();
});
When('メールアドレス {string} を入力する', function (email: string) {
loginPage.enterEmail(email);
});
When('パスワード {string} を入力する', function (password: string) {
loginPage.enterPassword(password);
});
When('ログインボタンをクリックする', function () {
loginPage.clickLogin();
});
Then('ダッシュボードページが表示される', function () {
expect(loginPage.isDashboardDisplayed()).to.be.true;
});
Then('ウェルカムメッセージが表示される', function () {
expect(loginPage.getWelcomeMessage()).to.include('ようこそ');
});
4. Cucumberの設定
cucumber.js
module.exports = {
default: {
require: ['features/step_definitions/**/*.ts'],
requireModule: ['ts-node/register'],
format: ['progress', 'html:reports/cucumber-report.html'],
publishQuiet: true
}
};
5. テスト実行
npx cucumber-js
.NET (C# + SpecFlow)
SpecFlowは.NETエコシステムにおけるCucumber相当のBDDフレームワークです。
1. プロジェクトのセットアップ
# .NET テストプロジェクトの作成
dotnet new nunit -n MyApp.Specs
cd MyApp.Specs
# SpecFlowとその他の依存関係をインストール
dotnet add package SpecFlow.NUnit
dotnet add package SpecFlow.Tools.MsBuild.Generation
dotnet add package FluentAssertions
dotnet add package Microsoft.NET.Test.Sdk
2. Featureファイルの作成
Features/Login.feature
Feature: ユーザーログイン機能
ユーザーがシステムにログインできることを確認する
Scenario: 有効な認証情報でログイン
Given ユーザーがログインページにアクセスする
When メールアドレス "user@example.com" を入力する
And パスワード "securepass" を入力する
And ログインボタンをクリックする
Then ダッシュボードページが表示される
And ウェルカムメッセージが表示される
3. Step Definitionsの実装
Steps/LoginSteps.cs
using TechTalk.SpecFlow;
using FluentAssertions;
namespace MyApp.Specs.Steps
{
[Binding]
public class LoginSteps
{
private LoginPage _loginPage;
[Given(@"ユーザーがログインページにアクセスする")]
public void ユーザーがログインページにアクセスする()
{
_loginPage = new LoginPage();
_loginPage.NavigateTo();
}
[When(@"メールアドレス ""(.*)"" を入力する")]
public void メールアドレスを入力する(string email)
{
_loginPage.EnterEmail(email);
}
[When(@"パスワード ""(.*)"" を入力する")]
public void パスワードを入力する(string password)
{
_loginPage.EnterPassword(password);
}
[When(@"ログインボタンをクリックする")]
public void ログインボタンをクリックする()
{
_loginPage.ClickLogin();
}
[Then(@"ダッシュボードページが表示される")]
public void ダッシュボードページが表示される()
{
_loginPage.IsDashboardDisplayed().Should().BeTrue();
}
[Then(@"ウェルカムメッセージが表示される")]
public void ウェルカムメッセージが表示される()
{
_loginPage.GetWelcomeMessage().Should().Contain("ようこそ");
}
}
// テスト対象のクラス(例)
public class LoginPage
{
private string _email;
private string _password;
private bool _isLoggedIn;
public void NavigateTo()
{
// ページ遷移の実装
}
public void EnterEmail(string email)
{
_email = email;
}
public void EnterPassword(string password)
{
_password = password;
}
public void ClickLogin()
{
// 実際の認証ロジック
if (_email == "user@example.com" && _password == "securepass")
{
_isLoggedIn = true;
}
}
public bool IsDashboardDisplayed()
{
return _isLoggedIn;
}
public string GetWelcomeMessage()
{
return _isLoggedIn ? "ようこそ" : string.Empty;
}
}
}
4. SpecFlowの設定
specflow.json
{
"language": {
"feature": "ja-JP"
},
"bindingCulture": {
"name": "ja-JP"
},
"stepAssemblies": [
{
"assembly": "MyApp.Specs"
}
]
}
5. テスト実行
dotnet test
SpecFlow特有の機能
Hooks(フック)の利用
[Binding]
public class Hooks
{
[BeforeScenario]
public void BeforeScenario()
{
// 各シナリオ実行前の初期化処理
Console.WriteLine("シナリオ開始");
}
[AfterScenario]
public void AfterScenario()
{
// 各シナリオ実行後のクリーンアップ処理
Console.WriteLine("シナリオ終了");
}
[BeforeFeature]
public static void BeforeFeature()
{
// Feature実行前の処理
}
[AfterFeature]
public static void AfterFeature()
{
// Feature実行後の処理
}
}
ScenarioContextの利用
[Binding]
public class SharedSteps
{
private readonly ScenarioContext _scenarioContext;
public SharedSteps(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
[Given(@"変数 ""(.*)"" に値 ""(.*)"" を設定する")]
public void 変数に値を設定する(string key, string value)
{
_scenarioContext[key] = value;
}
[Then(@"変数 ""(.*)"" の値は ""(.*)"" である")]
public void 変数の値を確認する(string key, string expectedValue)
{
_scenarioContext[key].Should().Be(expectedValue);
}
}
BDDのベストプラクティス
1. シナリオの書き方
✅ Good: 具体的で明確
Given ユーザー "田中太郎" の口座残高が 10000 円である
When 5000 円を引き出す
Then 口座残高は 5000 円になる
❌ Bad: 曖昧で抽象的
Given ユーザーがお金を持っている
When お金を引き出す
Then 残高が減る
2. Given-When-Thenの原則
- Given: システムの初期状態を設定(複数可)
- When: テスト対象のアクション(通常1つ)
- Then: 期待される結果の検証(複数可)
3. 宣言的 vs 命令的
✅ 宣言的(推奨): 「何を」に焦点
When ユーザーが新しい記事を作成する
❌ 命令的: 「どのように」に焦点
When "新規作成" ボタンをクリックする
And タイトルフィールドに "テスト記事" と入力する
And 本文フィールドに "これはテストです" と入力する
And "公開" ボタンをクリックする
4. 独立性と再利用性
- 各シナリオは独立して実行可能にする
- Step Definitionsは再利用可能な粒度で作成
- シナリオ間で状態を共有しない
まとめ
BDDのメリット
- ✅ コミュニケーション改善: 共通言語でチーム全体が要件を理解
- ✅ 仕様の明確化: 曖昧さを排除し、要件を具体化
- ✅ リビングドキュメント: 常に最新の仕様書として機能
- ✅ 早期発見: 要件の齟齬を開発初期に発見
BDDの課題
- ❌ 学習コスト: チーム全体がGherkinとBDDの概念を理解する必要
- ❌ メンテナンスコスト: Feature/Step Definitionsの保守が必要
- ❌ 実行速度: E2Eテストは実行に時間がかかる
適用場面
BDDは以下のような場合に特に有効です:
- 要件が複雑で、ステークホルダー間の合意が重要な場合
- ドメイン知識が重要なビジネスロジックを扱う場合
- QA、プロダクトオーナー、開発者の協働が求められる場合
- 受け入れテストの自動化が必要な場合
TDDとの使い分け
- BDD: ユーザーストーリーや受け入れ基準のテスト(外側から)
- TDD: 内部実装やアルゴリズムのテスト(内側から)
両方を組み合わせることで、「正しいものを、正しく作る」ことができます。