0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BDD事始め:Gherkin+Cucumber+PlayWrightで作るものの解像度を初動でMAXに

Posted at

BDD事始め:Gherkin+Cucumber+PlayWrightで作るものの解像度を初動でMAXに

注意:この記事はAIが作成しています
参照元の存在、参考元のリンクの信頼度、事実の歪曲がないかをAIによりセルフチェックしています

はじめに

プロジェクトの初期段階で「何を作るのか」の認識がチーム内でズレていることはありませんか?BDD(Behavior-Driven Development:振る舞い駆動開発)は、この課題を解決し、開発初期から要求仕様の解像度を最大化する強力なアプローチです。

本記事では、Gherkin記法によるシナリオ記述、Cucumberによる自動化、そしてPlayWrightを使ったE2Eテストの実装を組み合わせることで、プロダクトの振る舞いを明確に定義し、チーム全体で共通認識を持つ方法を解説します。

BDDとは何か

BDDは、TDD(Test-Driven Development)から派生した開発手法で、システムの「振る舞い」に焦点を当てます。技術的な実装詳細ではなく、ユーザーから見たシステムの動作を中心に据えることで、ビジネス側と開発側の認識のギャップを埋めます。

BDDの3つの核心要素

Gherkin記法:全員が理解できる仕様書

Gherkinは、自然言語に近い形式でテストシナリオを記述する記法です。プログラマーでなくても理解でき、かつ実行可能なテストとして機能します。

基本構文

Feature: ショッピングカートの管理
  ユーザーとして
  商品をカートに追加・削除できる
  購入前に商品を管理したいから

  Background:
    Given ユーザーがECサイトにログインしている
    And 商品一覧ページを表示している

  Scenario: 商品をカートに追加する
    Given 商品「プログラミング入門書」が表示されている
    When ユーザーが「カートに追加」ボタンをクリックする
    Then カートに「プログラミング入門書」が1冊追加される
    And カートアイコンに「1」と表示される

  Scenario Outline: 複数商品の追加
    When ユーザーが<商品名><個数>個カートに追加する
    Then カートの合計金額は<合計金額>円になる

    Examples:
      | 商品名           | 個数 | 合計金額 |
      | ノートPC        | 1    | 150000  |
      | マウス          | 2    | 6000    |
      | キーボード      | 1    | 12000   |

Gherkinのキーワード解説

キーワード 目的 使用タイミング
Feature 機能全体の説明 ファイルの冒頭に1つ
Background 共通の前提条件 各シナリオ実行前に毎回実行
Scenario 具体的なテストケース 1つの振る舞いを検証
Given 前提条件 テストの初期状態を設定
When アクション ユーザーの操作を表現
Then 期待結果 検証すべき結果を記述
And/But 接続詞 複数の条件やアクションを連結

Cucumber:シナリオを実行可能にする

Cucumberは、Gherkinで書かれたシナリオを実際のテストコードに変換するツールです。JavaScript/TypeScriptの実装例を見てみましょう。

プロジェクトセットアップ

# パッケージのインストール
npm init -y
npm install --save-dev @cucumber/cucumber playwright @playwright/test typescript ts-node
npm install --save-dev @types/node

# PlayWrightのブラウザをインストール
npx playwright install

ディレクトリ構造

project-root/
├── features/
│   ├── shopping-cart.feature
│   └── step-definitions/
│       └── shopping-cart.steps.ts
├── support/
│   ├── world.ts
│   └── hooks.ts
├── cucumber.js
├── tsconfig.json
└── package.json

Step Definitionsの実装

// features/step-definitions/shopping-cart.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { CustomWorld } from '../../support/world';

Given('ユーザーがECサイトにログインしている', async function (this: CustomWorld) {
  await this.page.goto('https://example-shop.com');
  await this.page.fill('#email', 'test@example.com');
  await this.page.fill('#password', 'password123');
  await this.page.click('button[type="submit"]');
  await this.page.waitForSelector('.user-dashboard');
});

Given('商品一覧ページを表示している', async function (this: CustomWorld) {
  await this.page.goto('https://example-shop.com/products');
  await this.page.waitForSelector('.product-list');
});

Given('商品{string}が表示されている', async function (this: CustomWorld, productName: string) {
  const product = await this.page.locator(`.product-card:has-text("${productName}")`);
  await expect(product).toBeVisible();
});

When('ユーザーが「カートに追加」ボタンをクリックする', async function (this: CustomWorld) {
  await this.page.click('.add-to-cart-button');
});

Then('カートに{string}が{int}冊追加される', async function (this: CustomWorld, productName: string, quantity: number) {
  await this.page.click('.cart-icon');
  const cartItem = await this.page.locator(`.cart-item:has-text("${productName}")`);
  await expect(cartItem).toBeVisible();
  const quantityElement = await cartItem.locator('.quantity');
  await expect(quantityElement).toHaveText(quantity.toString());
});

Then('カートアイコンに{string}と表示される', async function (this: CustomWorld, count: string) {
  const badge = await this.page.locator('.cart-badge');
  await expect(badge).toHaveText(count);
});

PlayWright:モダンなE2Eテストの実装

PlayWrightは、クロスブラウザ対応の強力なE2Eテストツールです。Cucumberと組み合わせることで、BDDシナリオを実際のブラウザ操作として実行できます。

World設定(テストコンテキスト)

// support/world.ts
import { World, IWorldOptions } from '@cucumber/cucumber';
import { Browser, BrowserContext, Page, chromium } from 'playwright';

export class CustomWorld extends World {
  browser!: Browser;
  context!: BrowserContext;
  page!: Page;

  constructor(options: IWorldOptions) {
    super(options);
  }
}

Hooks設定(前処理・後処理)

// support/hooks.ts
import { Before, After, BeforeAll, AfterAll } from '@cucumber/cucumber';
import { chromium, Browser } from 'playwright';
import { CustomWorld } from './world';

let browser: Browser;

BeforeAll(async function () {
  browser = await chromium.launch({
    headless: process.env.HEADLESS !== 'false',
    slowMo: parseInt(process.env.SLOWMO || '0')
  });
});

Before(async function (this: CustomWorld) {
  this.context = await browser.newContext({
    viewport: { width: 1280, height: 720 },
    ignoreHTTPSErrors: true,
  });
  this.page = await this.context.newPage();
});

After(async function (this: CustomWorld, { result }) {
  if (result?.status === 'FAILED') {
    // 失敗時はスクリーンショットを保存
    const screenshot = await this.page.screenshot({ 
      path: `screenshots/failure-${Date.now()}.png` 
    });
    await this.attach(screenshot, 'image/png');
  }
  await this.context.close();
});

AfterAll(async function () {
  await browser.close();
});

実装フロー:初動で解像度をMAXにする

1. Example Mappingセッション

チーム全体で参加し、以下の要素を整理します:

  • Rules(ルール): ビジネスルールや制約
  • Examples(例): 具体的なシナリオ
  • Questions(疑問): 不明確な点

2. シナリオの段階的詳細化

# 第1段階:ハッピーパスから始める
Scenario: 基本的な商品追加
  When 商品をカートに追加する
  Then カートに商品が入る

# 第2段階:具体的な値を追加
Scenario: 特定商品の追加
  When 「JavaScript完全ガイド」を1冊カートに追加する
  Then カートに「JavaScript完全ガイド」が1冊、3,800円で表示される

# 第3段階:エッジケースを追加
Scenario: 在庫切れ商品の追加試行
  Given 「人気商品X」の在庫が0個
  When 「人気商品X」をカートに追加しようとする
  Then エラーメッセージ「在庫がありません」が表示される
  And カートは空のまま

ベストプラクティス

1. シナリオは独立性を保つ

各シナリオは他のシナリオに依存せず、単独で実行可能にします。

2. 技術的詳細を避ける

# ❌ 悪い例
When ユーザーがid="submit-btn"のボタンをクリックする
Then HTTPステータス200が返される

# ✅ 良い例  
When ユーザーが注文を確定する
Then 注文完了画面が表示される

3. タグを活用した実行制御

@smoke @critical
Scenario: ログイン機能

@slow @integration
Scenario: 外部API連携

# 実行時
npm run test -- --tags "@smoke and not @slow"

4. Page Object Patternの適用

// pages/ShoppingCartPage.ts
export class ShoppingCartPage {
  constructor(private page: Page) {}

  async addToCart(productName: string) {
    await this.page.click(`[data-product="${productName}"] .add-to-cart`);
  }

  async getCartItemCount(): Promise<number> {
    const text = await this.page.textContent('.cart-count');
    return parseInt(text || '0');
  }

  async checkout() {
    await this.page.click('.checkout-button');
    await this.page.waitForURL('**/checkout');
  }
}

CI/CD統合

GitHub Actions設定例

name: BDD E2E Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Install Playwright Browsers
      run: npx playwright install --with-deps
      
    - name: Run BDD tests
      run: npm run test:e2e
      env:
        HEADLESS: true
        
    - name: Upload test results
      if: always()
      uses: actions/upload-artifact@v3
      with:
        name: test-results
        path: |
          test-results/
          screenshots/
          
    - name: Generate Cucumber Report
      if: always()
      run: npm run report:generate

メトリクスとレポーティング

Cucumberレポートの生成

// cucumber.js
module.exports = {
  default: {
    require: ['features/**/*.steps.ts', 'support/**/*.ts'],
    requireModule: ['ts-node/register'],
    format: [
      'progress',
      'html:reports/cucumber-report.html',
      'json:reports/cucumber-report.json',
      'junit:reports/cucumber-report.xml'
    ],
    parallel: 2
  }
};

カバレッジメトリクス

トラブルシューティング

よくある問題と解決策

問題 原因 解決策
ステップが見つからない Step Definitionのパスが間違っている cucumber.jsのrequireパスを確認
タイムアウトエラー 要素の読み込みが遅い waitForSelectorのタイムアウト値を調整
並列実行時の競合 データの共有による競合 各テストで独立したテストデータを使用
フレーキーなテスト 非同期処理の待機不足 適切な待機処理を追加

まとめ

BDD + Gherkin + Cucumber + PlayWrightの組み合わせは、プロジェクト初期から要求の解像度を最大化し、チーム全体で共通認識を持つための強力なアプローチです。

導入による効果

  • コミュニケーションコストの削減: 仕様の認識齟齬が激減
  • 早期の問題発見: 実装前に仕様の矛盾や不明瞭な点を発見
  • 生きたドキュメント: 常に最新の仕様書として機能
  • 品質の向上: 自動テストによる継続的な品質保証

初期投資は必要ですが、プロジェクトが進むにつれて、その価値は指数関数的に増大します。小さなプロジェクトから始めて、徐々に組織全体に展開していくことをお勧めします。

参照リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?