跳到主要内容

GherkinとCucumberによるBDD

BDD(振る舞い駆動開発)とは

BDD(Behavior Driven Development:振る舞い駆動開発)は、ソフトウェアの「振る舞い」に焦点を当てた開発手法です。開発者、QA、ビジネス側の関係者が共通の言語で要件を記述し、その仕様をそのままテストコードとして実行できることが特徴です。

BDDの主な目的

  • 共通言語の確立: 技術者と非技術者が同じ言語でコミュニケーション
  • 仕様の文書化: 実行可能なテストが仕様書としても機能
  • 要件の明確化: 「何を作るべきか」を明確にする
  • リビングドキュメント: コードと仕様が常に同期

TDD(テスト駆動開発)との違い

項目TDDBDD
焦点コードの正しさシステムの振る舞い
記述者主に開発者開発者 + 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 OutlineExamplesを使用します:

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の動作フロー

  1. Featureファイル作成: .feature拡張子でGherkinシナリオを記述
  2. Step Definitions作成: 各ステップに対応する実装コードを記述
  3. テスト実行: CucumberがFeatureファイルを読み込み、Step Definitionsを実行
  4. レポート生成: テスト結果を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: 内部実装やアルゴリズムのテスト(内側から)

両方を組み合わせることで、「正しいものを、正しく作る」ことができます。

参考リンク