【Python】pytest-bddではじめるBDDテスト入門

## はじめに

この記事は、[Stable Diffusion XL on macOS: Efficient Testing with BDD and safetensors](https://tech.jugoya.ai/blog/2025-01-12-sdxl-mac-diffusers-guide)の記事で紹介されているBDDテストの実装例をベースに、pytest-bddの使い方を詳しく解説するものです。実装例の全コードは[GitHubリポジトリ](https://github.com/yonaka15/sdxl-mac-diffusers-guide)で公開されています。

BDD（Behavior Driven Development）は、テスト駆動開発（TDD）から派生した開発手法で、システムの振る舞いを自然言語で記述し、それを元にテストと実装を進めていく方法です。最近、この手法がAIとの協業において効果的であることを実感しています。特にAIに対する要件の明確化や、期待する動作の指示において、その効果を強く感じています。、システムの振る舞いを自然言語で記述し、それを元にテストと実装を進めていく方法です。本記事では、Pythonで人気のテストフレームワークpytestの拡張機能である`pytest-bdd`を使って、BDDテストを実践する方法を解説します。

私は最近、機械学習モデルを使用したプロジェクトでBDDアプローチを採用し、その有用性を実感しました。特に、チーム内でのコミュニケーションや要件の明確化において、大きな効果がありました。

## 環境設定

まずは必要なパッケージをインストールします。

```bash
pip install pytest pytest-bdd
```

## プロジェクト構造

BDDテストを効率的に管理するために、以下のようなプロジェクト構造を推奨します：

```
tests/
├── features/          # Gherkinシナリオ
│   ├── calc/         # 計算機能のfeature
│   │   ├── basic.feature
│   │   └── advanced.feature
├── step_definitions/ # ステップの実装
│   └── test_calc.py
├── fixtures/         # 共通フィクスチャー
│   └── calc.py
└── conftest.py      # テスト全体の設定
```

この構造により、テストの管理が容易になり、コードの再利用性も高まります。

## 基本的な使い方

### 1. フィーチャーファイルの作成

まず、テスト対象の振る舞いを自然言語で記述したフィーチャーファイルを作成します。例として、簡単な計算機能のテストを考えてみましょう。

```gherkin
# tests/features/calc/basic.feature
Feature: 基本的な計算機能
    電卓アプリケーションの基本的な計算機能をテストする

    Background:
        Given 計算機を準備する

    Scenario: 2つの数値の加算
        Given 最初の数値として5を入力する
        And 2番目の数値として3を入力する
        When 加算を実行する
        Then 結果は8になる

    Scenario Outline: 複数のパターンでの加算テスト
        Given 最初の数値として<num1>を入力する
        And 2番目の数値として<num2>を入力する
        When 加算を実行する
        Then 結果は<expected>になる

        Examples:
        | num1 | num2 | expected |
        | 1    | 1    | 2        |
        | 10   | 20   | 30       |
        | -5   | 5    | 0        |
```

### 2. ステップ定義の実装

次に、フィーチャーファイルで定義したステップを実装します。

```python
# tests/step_definitions/test_calc.py
from pytest_bdd import scenario, given, when, then, parsers
from calculator import Calculator

@scenario('calc/basic.feature', '2つの数値の加算')
def test_add():
    pass

@given('計算機を準備する', target_fixture='calculator')
def calculator():
    return Calculator()

@given(parsers.parse('最初の数値として{number:d}を入力する'))
def input_first_number(calculator, number):
    calculator.input_first(number)

@given(parsers.parse('2番目の数値として{number:d}を入力する'))
def input_second_number(calculator, number):
    calculator.input_second(number)

@when('加算を実行する')
def execute_addition(calculator):
    calculator.add()

@then(parsers.parse('結果は{expected:d}になる'))
def check_result(calculator, expected):
    assert calculator.result == expected

# Scenario Outline用のテスト関数
@scenario('calc/basic.feature', '複数のパターンでの加算テスト')
def test_add_multiple():
    pass
```

### 3. テスト対象クラスの実装

最後に、テスト対象となる実装を作成します。

```python
# calculator.py
class Calculator:
    def __init__(self):
        self.first = 0
        self.second = 0
        self.result = 0

    def input_first(self, number):
        self.first = number

    def input_second(self, number):
        self.second = number

    def add(self):
        self.result = self.first + self.second
```

## pytest-bddの高度な使い方

### パラメータ化されたステップ

複数のパターンでテストを実行したい場合、Scenario Outlineを使用します。これにより、同じシナリオを異なるデータセットで実行できます。

```gherkin
Scenario Outline: 様々な計算のテスト
    Given 演算子として<operator>を選択する
    And 最初の数値として<num1>を入力する
    And 2番目の数値として<num2>を入力する
    When 計算を実行する
    Then 結果は<expected>になる

    Examples:
    | operator | num1 | num2 | expected |
    | +        | 5    | 3    | 8        |
    | -        | 10   | 4    | 6        |
    | *        | 2    | 6    | 12       |
```

### フィクスチャーの活用

よく使用する前提条件は、フィクスチャーとして定義することで再利用が可能です。

```python
# tests/fixtures/calc.py
import pytest
from calculator import Calculator

@pytest.fixture
def calculator():
    return Calculator()

@pytest.fixture
def calculator_with_numbers():
    calc = Calculator()
    calc.input_first(10)
    calc.input_second(5)
    return calc
```

### タグによるテスト管理

特定のテストだけを実行したい場合、タグを使用して管理できます。

```gherkin
@slow
@integration
Scenario: 大量データの計算
    Given 1000個の数値を入力する
    When バッチ計算を実行する
    Then すべての結果が正しい
```

タグを指定してテストを実行：

```bash
pytest -v -k "slow and not integration"
```

## BDDテストのベストプラクティス

### 1. シナリオの粒度

シナリオは以下の特徴を持つように設計します：

- 1つのシナリオで1つの振る舞いをテスト
- 理解しやすい自然な日本語で記述
- テストの意図が明確に伝わる

### 2. ステップの再利用

- 共通のステップは再利用可能な形で実装
- パラメータ化を活用して柔軟性を確保
- Backgroundを使用して共通の前提条件を定義

### 3. テストの保守性

- 適切な名前付けとコメントの活用
- モジュール化とコードの整理
- CI/CDパイプラインへの組み込み

## テストレポートの活用

pytest-bddは、テスト結果を分かりやすいレポートとして出力できます。HTML形式のレポートを生成するには、`pytest-html`プラグインを使用します：

```bash
pip install pytest-html
pytest --html=report.html
```

## まとめ

pytest-bddを使用したBDDテストの導入により、特にAIとの協業において以下のメリットを感じています：

- 要件の明確化とドキュメント化
  - 自然言語での記述により、AIに求める動作を明確に指示可能
  - テストケースを通じて期待する動作を具体的に示せる
- 段階的な開発アプローチの実現
  - AIが生成したコードの品質を確保
  - 要件からテスト、実装まで一貫した流れで開発を進められる
- テストの保守性と再利用性の向上
  - AIが生成したコードの品質を継続的に確認可能
  - パターン化された要件は再利用が容易

BDDは単なるテスト手法ではなく、AIとの協業を効果的に進めるためのコミュニケーションツールとしても機能します。自然言語による要件定義とテストの自動化により、AIとの開発プロセスをより確実なものにできます。

## 参考文献

- pytest-bdd公式ドキュメント: [https://pytest-bdd.readthedocs.io/](https://pytest-bdd.readthedocs.io/)
- Behavior Driven Development: [https://cucumber.io/docs/bdd/](https://cucumber.io/docs/bdd/)
- [pytest-bddとBDDによるAIとの効率的な協業アプローチ](https://qiita.com/yonaka15/items/36466afff7650d7f2366) - 本記事のベースとなった実践例

## 著者について

現在、AIと協業したプログラミングの実践に取り組んでいるエンジニアです。特に、AIとの効果的な協業を実現するための手法として、BDDに大きな可能性を感じています。

BDDの特徴である「自然言語による要件定義」と「振る舞いの明確化」は、AIとの協業において以下のような利点があります：

1. AIに対して明確な期待動作を伝えることができる
2. 実装の意図と目的が明確になり、AIとのコミュニケーションが円滑になる
3. テストケースを通じて、AIが生成したコードの品質を確保できる

本記事の内容や、AIとの協業におけるBDDの活用について、コメントでお気軽にご相談ください。