Cucumberを使用したJavaテスト自動化の完全ガイド

## はじめに

こんにちは！今回は、Javaプロジェクトでのテスト自動化に欠かせないツールであるCucumberについて、詳しく解説していきます。Cucumberを使うことで、開発者だけでなく、ビジネス側の方々も理解しやすいテストシナリオを作成できます。それでは、Cucumberの基本から応用まで、15章に渡ってご紹介していきましょう。

## 第1章: Cucumberとは

Cucumberは、振る舞い駆動開発（BDD）をサポートするオープンソースのテスティングツールです。人間が読みやすい形式でテストシナリオを記述し、それを自動化されたテストとして実行することができます。

Cucumberの主な特徴は以下の通りです：

- 自然言語でテストシナリオを記述
- 開発者とビジネス側のコミュニケーションを促進
- 多言語対応（日本語でのシナリオ記述も可能）
- Javaを含む多くのプログラミング言語をサポート

## 第2章: Cucumberのセットアップ

まずは、Javaプロジェクトにおいて、Cucumberをセットアップする方法を見ていきましょう。

1. Mavenを使用している場合は、`pom.xml`に以下の依存関係を追加します：

```xml
<dependencies>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>7.11.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-junit</artifactId>
        <version>7.11.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>
```

2. プロジェクトの構造を以下のように設定します：

```
src
├── main
│   └── java
└── test
    ├── java
    └── resources
        └── features
```

これで、Cucumberを使用する準備が整いました。

## 第3章: 最初のFeatureファイル

Cucumberでは、テストシナリオを「Feature」ファイルに記述します。これらのファイルは、Gherkin構文を使用して書かれます。

`src/test/resources/features`ディレクトリに`login.feature`ファイルを作成し、以下の内容を記述してみましょう：

```gherkin
# language: ja
機能: ログイン機能

  シナリオ: 有効な認証情報でログインする
    前提 ログインページを開いている
    もし ユーザー名"user@example.com"を入力する
    かつ パスワード"password123"を入力する
    かつ ログインボタンをクリックする
    ならば ダッシュボードページが表示される
```

この例では、日本語でシナリオを記述しています。`# language: ja`の行は、日本語の使用を宣言しています。

## 第4章: ステップ定義の作成

Featureファイルを作成したら、次はそれに対応するステップ定義を作成します。ステップ定義は、Featureファイルの各ステップを実際のJavaコードにマッピングします。

`src/test/java`ディレクトリに`LoginSteps.java`ファイルを作成し、以下のコードを記述します：

```java
import io.cucumber.java.ja.*;
import static org.junit.Assert.*;

public class LoginSteps {

    @前提("ログインページを開いている")
    public void ログインページを開いている() {
        // ログインページを開く処理
        System.out.println("ログインページを開きました");
    }

    @もし("ユーザー名{string}を入力する")
    public void ユーザー名を入力する(String username) {
        // ユーザー名を入力する処理
        System.out.println("ユーザー名 " + username + " を入力しました");
    }

    @かつ("パスワード{string}を入力する")
    public void パスワードを入力する(String password) {
        // パスワードを入力する処理
        System.out.println("パスワードを入力しました");
    }

    @かつ("ログインボタンをクリックする")
    public void ログインボタンをクリックする() {
        // ログインボタンをクリックする処理
        System.out.println("ログインボタンをクリックしました");
    }

    @ならば("ダッシュボードページが表示される")
    public void ダッシュボードページが表示される() {
        // ダッシュボードページの表示を確認する処理
        System.out.println("ダッシュボードページが表示されました");
        assertTrue(true); // 仮の検証
    }
}
```

このコードでは、Featureファイルの各ステップに対応するメソッドを定義しています。実際のプロジェクトでは、これらのメソッド内で実際のアプリケーションの操作や検証を行います。

## 第5章: テストランナーの作成

テストを実行するために、JUnitのテストランナーを作成します。

`src/test/java`ディレクトリに`RunCucumberTest.java`ファイルを作成し、以下のコードを記述します：

```java
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "",
    plugin = {"pretty", "html:target/cucumber-reports"}
)
public class RunCucumberTest {
}
```

このコードは、Cucumberテストを実行するための設定を行っています。`features`オプションでFeatureファイルの場所を、`glue`オプションでステップ定義の場所を指定しています。

## 第6章: テストの実行

これで、Cucumberテストを実行する準備が整いました。以下のコマンドでテストを実行できます：

```
mvn test
```

テストが成功すると、コンソールに各ステップの実行結果が表示されます。また、`target/cucumber-reports`ディレクトリにHTMLレポートが生成されます。

## 第7章: シナリオアウトライン

同じシナリオを異なるデータセットで繰り返し実行したい場合、シナリオアウトラインを使用します。

`login.feature`ファイルに以下のシナリオアウトラインを追加してみましょう：

```gherkin
  シナリオアウトライン: 複数のユーザーでログインを試みる
    前提 ログインページを開いている
    もし ユーザー名"<username>"を入力する
    かつ パスワード"<password>"を入力する
    かつ ログインボタンをクリックする
    ならば ログイン結果は"<result>"となる

    例:
      | username        | password    | result   |
      | valid@email.com | correctpass | 成功     |
      | invalid@email.com | wrongpass | 失敗     |
```

このシナリオアウトラインは、2つの異なるデータセットでログイン処理をテストします。

## 第8章: フックの使用

Cucumberのフックを使用すると、シナリオの前後で特定の処理を実行できます。

`src/test/java`ディレクトリに`Hooks.java`ファイルを作成し、以下のコードを記述します：

```java
import io.cucumber.java.Before;
import io.cucumber.java.After;

public class Hooks {

    @Before
    public void beforeScenario() {
        System.out.println("シナリオ開始前の処理");
    }

    @After
    public void afterScenario() {
        System.out.println("シナリオ終了後の処理");
    }
}
```

これにより、各シナリオの前後で特定の処理（例：ブラウザの起動と終了）を自動的に実行できます。

## 第9章: タグの使用

タグを使用すると、特定のシナリオやフィーチャーを選択的に実行できます。

`login.feature`ファイルの先頭に以下のタグを追加します：

```gherkin
@login
機能: ログイン機能
```

そして、`RunCucumberTest.java`ファイルの`@CucumberOptions`アノテーションを以下のように変更します：

```java
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "",
    plugin = {"pretty", "html:target/cucumber-reports"},
    tags = "@login"
)
```

これにより、`@login`タグが付いたシナリオのみが実行されます。

## 第10章: バックグラウンドの使用

同じフィーチャー内の複数のシナリオで共通のステップがある場合、バックグラウンドを使用できます。

`login.feature`ファイルを以下のように修正します：

```gherkin
# language: ja
@login
機能: ログイン機能

  背景:
    前提 ログインページを開いている

  シナリオ: 有効な認証情報でログインする
    もし ユーザー名"user@example.com"を入力する
    かつ パスワード"password123"を入力する
    かつ ログインボタンをクリックする
    ならば ダッシュボードページが表示される

  シナリオ: 無効な認証情報でログインを試みる
    もし ユーザー名"invalid@example.com"を入力する
    かつ パスワード"wrongpassword"を入力する
    かつ ログインボタンをクリックする
    ならば エラーメッセージが表示される
```

これにより、各シナリオの前に自動的に「ログインページを開いている」ステップが実行されます。

## 第11章: データテーブルの使用

複雑なデータセットをテストに渡したい場合、データテーブルを使用できます。

新しいシナリオを`login.feature`ファイルに追加します：

```gherkin
  シナリオ: ユーザープロフィールの更新
    前提 ログインしている
    もし 以下のプロフィール情報を更新する:
      | フィールド | 値           |
      | 名前       | 山田 太郎    |
      | 年齢       | 30           |
      | 職業       | ソフトウェア開発者 |
    ならば プロフィールが正常に更新される
```

対応するステップ定義を`LoginSteps.java`に追加します：

```java
import io.cucumber.datatable.DataTable;
import java.util.Map;

public class LoginSteps {
    // ... 既存のコード ...

    @もし("以下のプロフィール情報を更新する:")
    public void プロフィール情報を更新する(DataTable dataTable) {
        List<Map<String, String>> data = dataTable.asMaps(String.class, String.class);
        for (Map<String, String> row : data) {
            String field = row.get("フィールド");
            String value = row.get("値");
            System.out.println(field + "を" + value + "に更新しました");
        }
    }

    @ならば("プロフィールが正常に更新される")
    public void プロフィールが正常に更新される() {
        System.out.println("プロフィールが正常に更新されました");
        assertTrue(true); // 仮の検証
    }
}
```

## 第12章: パラメータ型の変換

Cucumberは、ステップ定義の引数を自動的に適切な型に変換しますが、カスタムの型変換も定義できます。

例えば、日付を扱う場合、以下のようなカスタム型変換を定義できます：

```java
import io.cucumber.java.ParameterType;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class ParameterTypes {

    @ParameterType("\\d{4}-\\d{2}-\\d{2}")
    public LocalDate iso8601Date(String dateString) {
        return LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE);
    }
}
```

これにより、ステップ定義で`LocalDate`型を直接使用できるようになります：

```java
@もし("ユーザーの生年月日を{iso8601Date}に設定する")
public void ユーザーの生年月日を設定する(LocalDate birthDate) {
    System.out.println("ユーザーの生年月日を" + birthDate + "に設定しました");
}
```

## 第13章: シナリオの状態管理

複数のステップ間で状態を共有する必要がある場合、インスタンス変数を使用できます。

`LoginSteps.java`を以下のように修正します：

```java
public class LoginSteps {
    private String currentUser;

    @前提("ユーザー{string}としてログインしている")
    public void ユーザーとしてログインしている(String username) {
        this.currentUser = username;
        System.out.println(username + "としてログインしました");
    }

    @もし("プロフィールページにアクセスする")
    public void プロフィールページにアクセスする() {
        System.out.println(currentUser + "のプロフィールページにアクセスしました");
    }
}
```

この例では、`currentUser`変数を使用して、ログインしたユーザーの情報を保持しています。

## 第14章: 外部リソースの利用

テストデータを外部ファイルから読み込むことで、テストをより柔軟に管理できます。

`src/test/resources`ディレクトリに`test_data.json`ファイルを作成し、以下の内容を記述します：

```json
{
  "validUser": {
    "username": "valid@example.com",
    "password": "correctPassword"
  },
  "invalidUser": {
    "username": "invalid@example.com",
    "password": "wrongPassword"
  }
}
```

そして、この外部データを利用するステップ定義を作成します：

```java
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
import java.util.Map;

public class LoginSteps {
    private Map<String, Map<String, String>> testData;

    public LoginSteps() throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        testData = mapper.readValue(new File("src/test/resources/test_data.json"), Map.class);
    }

    @もし("有効なユーザー情報でログインする")
    public void 有効なユーザー情報でログインする() {
        Map<String, String> validUser = testData.get("validUser");
        String username = validUser.get("username");
        String password = validUser.get("password");
        System.out.println("ユーザー名 " + username + " とパスワードでログインしました");
    }
}
```

この方法により、テストデータの管理が容易になり、テストケースの変更も簡単に行えるようになります。

## 第15章: レポート生成の拡張

Cucumberは標準でHTMLレポートを生成しますが、より詳細なレポートを生成するプラグインを使用することもできます。例えば、Extent Reportsを使用して、より視覚的で詳細なレポートを生成できます。

まず、`pom.xml`に以下の依存関係を追加します：

```xml
<dependency>
    <groupId>tech.grasshopper</groupId>
    <artifactId>extentreports-cucumber7-adapter</artifactId>
    <version>1.7.0</version>
</dependency>
```

次に、`RunCucumberTest.java`の`@CucumberOptions`アノテーションを以下のように修正します：

```java
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "",
    plugin = {
        "pretty",
        "html:target/cucumber-reports/cucumber.html",
        "json:target/cucumber-reports/cucumber.json",
        "com.aventstack.extentreports.cucumber.adapter.ExtentCucumberAdapter:"
    }
)
```

最後に、`src/test/resources`ディレクトリに`extent.properties`ファイルを作成し、以下の内容を記述します：

```properties
extent.reporter.spark.start=true
extent.reporter.spark.out=target/extent-reports/Spark.html
```

これにより、テスト実行後に`target/extent-reports`ディレクトリに詳細なHTMLレポートが生成されます。

以上で、Cucumberを使用したJavaテスト自動化の15章にわたる解説が完了しました。この記事を通じて、Cucumberの基本的な使い方から応用的なテクニックまでを学ぶことができました。Cucumberを使用することで、読みやすく管理しやすいテストシナリオを作成し、効率的なテスト自動化を実現できます。

テスト自動化の旅は、ここから始まります。実際のプロジェクトに適用する際は、アプリケーションの特性や要件に合わせてカスタマイズしていくことが重要です。Cucumberを活用して、品質の高いソフトウェア開発を目指しましょう！