Gradle セットアップ

// build.gradle
dependencies {
    // Cucumber JVM 本体
    testImplementation "io.cucumber:cucumber-java:7.17.0"
    testImplementation "io.cucumber:cucumber-junit-platform-engine:7.17.0"

    // JUnit 5 プラットフォーム
    testImplementation "org.junit.platform:junit-platform-suite:1.10.2"
    testImplementation "org.junit.jupiter:junit-jupiter:5.10.2"

    // Spring Boot 連携(Spring Boot プロジェクトの場合)
    testImplementation "io.cucumber:cucumber-spring:7.17.0"
}

// src/test/resources/cucumber.properties
cucumber.publish.quiet=true
cucumber.plugin=pretty, html:build/cucumber-reports/report.html, json:build/cucumber-reports/report.json
// src/test/java/com/example/RunCucumberTest.java
// JUnit 5 Suite で Cucumber を起動するランナークラス
import org.junit.platform.suite.api.*;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")   // Feature ファイルの場所
@ConfigurationParameter(
    key = "cucumber.glue",
    value = "com.example.steps"         // Step 定義クラスのパッケージ
)
public class RunCucumberTest { }

Gherkin 文法の基本

Gherkin は自然言語(日本語も対応)で受け入れ条件を記述するための DSL です。

# src/test/resources/features/user-registration.feature
# language: ja

機能: ユーザー登録
  利用者として
  アカウントを作成することで
  サービスを利用できるようになりたい

  シナリオ: 正常なユーザー登録
    前提 ユーザー登録ページを開いている
    もし ユーザー名に "山田太郎" を入力する
    かつ メールアドレスに "taro@example.com" を入力する
    かつ パスワードに "Password123!" を入力する
    かつ "登録" ボタンをクリックする
    ならば 登録完了メッセージが表示される
    かつ メールアドレス "taro@example.com" に確認メールが送信される

  シナリオ: すでに登録済みのメールアドレスはエラー
    前提 "taro@example.com" がすでに登録されている
    もし ユーザー登録ページを開いている
    かつ メールアドレスに "taro@example.com" を入力する
    かつ "登録" ボタンをクリックする
    ならば "このメールアドレスはすでに使用されています" というエラーが表示される

ℹ️ 英語記法が標準

日本語 Gherkin(# language: ja)も使えますが、チームの英語力に自信があれば英語で書く方が CI 連携・ツールとの相性が良いです。Given / When / Then / And / But が標準キーワードです。

Step 定義の実装

Feature ファイルの各 Given/When/Then に対応する Java の Step 定義を実装します。

// src/test/java/com/example/steps/UserRegistrationSteps.java
import io.cucumber.java.*;
import io.cucumber.java.ja.*;   // 日本語アノテーション
import static org.assertj.core.api.Assertions.*;

public class UserRegistrationSteps {

    private final RegistrationPage registrationPage;
    private final EmailService      emailService;
    private final UserRepository    userRepository;

    // Spring DI(@CucumberContextConfiguration が必要)
    public UserRegistrationSteps(
            RegistrationPage page,
            EmailService emailSvc,
            UserRepository userRepo) {
        this.registrationPage = page;
        this.emailService     = emailSvc;
        this.userRepository   = userRepo;
    }

    @前提("ユーザー登録ページを開いている")
    public void openRegistrationPage() {
        registrationPage.open();
    }

    @もし("ユーザー名に {string} を入力する")
    public void enterUsername(String name) {
        registrationPage.enterName(name);
    }

    @もし("メールアドレスに {string} を入力する")
    public void enterEmail(String email) {
        registrationPage.enterEmail(email);
    }

    @もし("パスワードに {string} を入力する")
    public void enterPassword(String password) {
        registrationPage.enterPassword(password);
    }

    @もし("{string} ボタンをクリックする")
    public void clickButton(String buttonName) {
        registrationPage.clickButton(buttonName);
    }

    @ならば("登録完了メッセージが表示される")
    public void verifySuccessMessage() {
        assertThat(registrationPage.getSuccessMessage())
            .isEqualTo("ご登録ありがとうございます!");
    }

    @ならば("メールアドレス {string} に確認メールが送信される")
    public void verifyConfirmationEmail(String email) {
        assertThat(emailService.getSentEmails())
            .anyMatch(e -> e.getTo().equals(email)
                        && e.getSubject().contains("確認"));
    }

    @前提("{string} がすでに登録されている")
    public void existingUser(String email) {
        userRepository.save(new User("既存ユーザー", email, "Existing123!"));
    }

    @ならば("{string} というエラーが表示される")
    public void verifyErrorMessage(String expectedError) {
        assertThat(registrationPage.getErrorMessage()).isEqualTo(expectedError);
    }
}

Scenario Outline とパラメータ化

同じシナリオを複数の入力値で繰り返すには Scenario Outline + Examples を使います。

# パスワードバリデーションのパラメータ化テスト
シナリオアウトライン: パスワードバリデーション
  もし パスワードに "<password>" を入力して登録する
  ならば バリデーション結果は "<result>" になる

  例:
    | password     | result  |
    | Abc1234!     | 成功    |
    | abc1234!     | 失敗    |  # 大文字なし
    | Abcdefg!     | 失敗    |  # 数字なし
    | Abc1234      | 失敗    |  # 記号なし
    | Ab1!         | 失敗    |  # 7文字未満
    | Abc12345678! | 成功    |  # 長いパスワード(境界値)

DataTable の活用

複数の属性を持つデータを Step に渡す場合は DataTable を使います。

# Feature ファイル
シナリオ: 複数ユーザーを一括登録する
  もし 以下のユーザーを登録する:
    | 名前     | メール               | ロール  |
    | 山田太郎 | taro@example.com     | ADMIN   |
    | 鈴木花子 | hanako@example.com   | USER    |
    | 田中一郎 | ichiro@example.com   | USER    |
  ならば ユーザー数が 3 人になる
// Step 定義 — DataTable を受け取る
import io.cucumber.datatable.DataTable;
import java.util.*;

@もし("以下のユーザーを登録する:")
public void registerMultipleUsers(DataTable dataTable) {
    // ヘッダー行を除いたデータ行を Map のリストとして取得
    List<Map<String, String>> rows = dataTable.asMaps(String.class, String.class);

    for (Map<String, String> row : rows) {
        String name  = row.get("名前");
        String email = row.get("メール");
        String role  = row.get("ロール");
        userService.register(name, email, UserRole.valueOf(role));
    }
}

@ならば("ユーザー数が {int} 人になる")
public void verifyUserCount(int expectedCount) {
    assertThat(userRepository.count()).isEqualTo(expectedCount);
}

Hooks(@Before / @After)

// src/test/java/com/example/steps/Hooks.java
import io.cucumber.java.*;

public class Hooks {

    private final DatabaseCleaner dbCleaner;
    private final TestContext     testContext;

    public Hooks(DatabaseCleaner dbCleaner, TestContext testContext) {
        this.dbCleaner   = dbCleaner;
        this.testContext = testContext;
    }

    // 各シナリオ前にDBをクリーン
    @Before
    public void beforeEachScenario(Scenario scenario) {
        dbCleaner.cleanAll();
        System.out.println("開始: " + scenario.getName());
    }

    // 各シナリオ後にリソース解放・スクリーンショット取得
    @After
    public void afterEachScenario(Scenario scenario) {
        if (scenario.isFailed()) {
            // 失敗時にスクリーンショットを埋め込む
            byte[] screenshot = testContext.takeScreenshot();
            scenario.attach(screenshot, "image/png", "失敗時スクリーンショット");
        }
        testContext.reset();
    }

    // 特定タグのシナリオのみに適用
    @Before("@api")
    public void beforeApiScenario() {
        testContext.setApiMode(true);
    }
}

タグ(@Tag)と実行フィルタ

# タグ付きシナリオ
@smoke @critical
シナリオ: ログイン機能の基本動作確認
  ...

@api @regression
シナリオ: ユーザー検索 API の検証
  ...

@slow @performance
シナリオ: 大量データ取込みの性能確認
  ...

# タグで絞り込んで実行(Gradle)
./gradlew test -Dcucumber.filter.tags="@smoke"           # smoke のみ
./gradlew test -Dcucumber.filter.tags="@smoke and @api"  # 両タグ
./gradlew test -Dcucumber.filter.tags="not @slow"        # slow を除外

# CI での推奨設定(PRでは smoke のみ、mainでは regression 全実行)
# .github/workflows/test.yml
- name: Run smoke tests (PR)
  if: github.event_name == 'pull_request'
  run: ./gradlew test -Dcucumber.filter.tags="@smoke"
- name: Run regression tests (main)
  if: github.ref == 'refs/heads/main'
  run: ./gradlew test -Dcucumber.filter.tags="@regression"

Feature ファイルの書き方ベストプラクティス

⚠️ 実装の詳細を Feature ファイルに書かない

「パスワードフィールドの CSS クラス '.pwd-input' に入力する」のような UI の詳細は Feature ファイルに書くべきではありません。ビジネス上の振る舞い(「パスワードを入力する」)を記述し、実装の詳細は Step 定義に隠蔽してください。

1シナリオ = 1ユーザーストーリー

Feature ファイルはビジネスの「受け入れ条件」に直接対応させます。1つのシナリオが長くなりすぎる場合は、機能を適切に分割するシグナルです。シナリオは 5〜8 ステップ以内に収めることを目安にしてください。

まとめ

  • Gherkin の Given-When-Then で「前提 / 操作 / 結果」を明確に分離し、非エンジニアも読める仕様書を書く
  • Scenario Outline + Examples で境界値・同値クラスのパラメータ化テストを簡潔に表現する
  • DataTable で複数データをステップに渡し、テストデータをFeatureファイルに集約する
  • @Before / @After で各シナリオの初期化・後片付けを一元管理し、テストの独立性を確保する
  • タグで CI のテスト実行を制御し、PR ではスモークテスト、main では全回帰テストを実行する