Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

PHP製BDDテストフレームワークBehatでE2Eテスト

More than 3 years have passed since last update.

BehatはPHP製のBDDテストフレームワークです。

概要は下記スライドを見ておくといいでしょう。
http://www.slideshare.net/tchikuba/behatbdd

必要なライブラリのインストール

composer.jsonで必要なライブラリをインストールします。

composer.json
{
    "require-dev": {
        "behat/behat": "3.*",
        "behat/mink": "1.*",
        "behat/mink-extension": "*",
        "behat/mink-goutte-driver": "*",
        "behat/mink-sahi-driver": "*",
        "behat/mink-selenium2-driver": "*",
        "behat/mink-zombie-driver": "*"
    }
}

behat/behat

behatの本体です。
テストシナリオを自然言語に近い形で記述し、実行する機能を提供します。

behat/mink

ブラウザのエミュレーションツールと組み合わせてテストを行う機能を提供します。
behatと組み合わせて利用することが可能です。

behat/mink-extension

behatで利用できるテストの機能を追加したり、テストケースで扱える言語を拡張したりします。

behat/mink-*-driver

behat+minkのテストでブラウザのエミュレーションツールを扱えるようにします。
上記サンプルではgoutte、shai、selenium、zombiejsをインストールしていますが、これらは全てインストールする必要はなく利用するブラウザのエミュレーションツールに対応したものをインストールすればいいです。

behatでテストを行うための準備

behatのテストシナリオを置きたいディレクトリに移動してvendor/bin/behat --initコマンドを実行します。
今回はproject/tests以下にbehatのテストを置いて行くことを前提にします。

コマンドを実行すると以下のようなファイルが配置されます。

project/
  tests/
    features/
      bootstrap/
        FeatureContext.php

behat.ymlを用意する

behatでは実行のための設定をbehat.ymlというyaml形式の設定ファイルの記述していきます。
デフォルトではbehatコマンドの実行ディレクトリと同じ階層にあるbehat.ymlが読み込まれますが、
-cオプションを利用することでbehat.ymlを読み込むパスを指定することも可能です。

今回はプロジェクトディレクトリ直下にbehat.ymlを置くことを想定します。

behat.yml
default:
  suites:
    default:
      path: %paths.base%/tests/features
      contexts:
        - FeatureContext

path

behatは、ここで指定したpathのテストシナリオを読み込んでテストを実行するようになります。

contexts

behatはここで指定したクラスの機能を利用してテストを実行します。

テストの作成と実行

テストはbehat.ymlのpathに指定したディレクトリ以下に作成していきます。
(今回はproject/tests/features/以下)
テストファイルの拡張子はfeatureである必要があります。

empty.feature
# language: ja
フィーチャ: 空のテスト

  シナリオ: 何もしない

一行目は利用する言語の指定です。
デフォルトの状態ではテストケースが存在しないため、featureファイルにも何も記述していません。
この状態でbehatコマンドを実行するとテストが走ります。

フィーチャ: 空のテスト

  シナリオ: 何もしない # features\empty.feature:4

No scenarios
No steps
0m0.04s (10.34Mb)

テストで利用可能なケースの確認

behatではテストケースをbehat.ymlのcontext以下に記述したクラスから読み込みます。
利用可能なテストケースはbehat -dlコマンドで確認出来ます。
デフォルトの状態では何もテストケースが登録されていないため、behat -dlコマンドを打っても何も表示されません。

Mink Extensionのテストケースを追加する

一からテストケースを作っていくのも辛いので、
冒頭でインストールしておいたMink Extensionが提供するテストケースを利用します。

behat.yml
  default:
    suites:
      default:
        path: %paths.base%/tests/features
        contexts:
          - FeatureContext
+         - Behat\MinkExtension\Context\MinkContext
  extensions:
    Behat\MinkExtension:
      base_url: http://google.com/

この状態でbehat -dlコマンドを実行すると利用可能なテストケースが追加されていることが確認出来ます。

default | Given /^(?:|ユーザーは )ホームページを表示している$/
default |  When /^(?:|ユーザーは )ホームページへ移動する$/
default | Given /^(?:|ユーザーは )"(?P<page>[^\s]+)" を表示している$/u
default |  When /^(?:|ユーザーが )"(?P<page>[^\s]+)" へ移動する$/u
default |  When /^(?:|ユーザーが )ページをリロードする$/u
     ・
     ・
     ・

ブラウザのエミュレーションツールと合わせてE2Eテストを行う

googleのトップページから検索を行い、結果画面が表示されることを確認するテストシナリオを作成します。

googling.feature
# language: ja
フィーチャ: グーグル検索

    シナリオ: recursionを検索
        前提 ホームページを表示している
        もし "q" フィールドに "recursion" と入力する
            かつ "btnK" ボタンをクリックする
        ならば "もしかして: recursion" と表示されていること

ブラウザのエミュレーションツールを利用するためには、behat.ymlを編集する必要があります。

behat.yml
  default:
    suites:
      default:
        path: %paths.base%/tests/features
        contexts:
          - FeatureContext
          - Behat\MinkExtension\Context\MinkContext
+   extensions:
+     Behat\MinkExtension:
+       base_url: http://google.com/
+       sessions:
+         default:
+           selenium2: ~

base_url

テスト対象のベースとなるURLを指定します。
Mink Extensionが提供するテストケースのうち、ホームページというキーワードはここで指定したURLを指します。

session

ここに利用するブラウザのエミュレーションツールを指定します。


この状態でbehatコマンドを実行するとテストが走ります。
結果は…

フィーチャ: グーグル検索

  シナリオ: recursionを検索                  # features\sample.feature:4
    前提 ホームページを表示している                  # CustomContext::iAmOnHomepage()
    もし "q" フィールドに "recursion" と入力する   # CustomContext::fillField()
    かつ "btnK" ボタンをクリックする              # CustomContext::pressButton()
    ならば "もしかして: recursion" と表示されていること # CustomContext::assertPageContainsText()
      The text "もしかして: recursion" was not found anywhere in the text of the current page. (Behat\Mink\Exception\ResponseTextException)

--- Failed scenarios:

    features\sample.feature:4

1 scenario (1 failed)
4 steps (3 passed, 1 failed)
0m5.97s (11.00Mb)

テストケースを拡張する

先ほどのテストは検索結果の画面が表示される前に、 "もしかして: recursion" と表示されていること というテストケースが走り、テストが失敗してしまいました。

画面が表示されるまで待つ、つまり n秒待つ というテストケースが必要になります。
bootstrap/FeatureContext.phpを編集して新たにテストケースを加えます。

bootstrap/FeatureContext.php
<?php

use Behat\Behat\Context\Context;
use Behat\Behat\Context\SnippetAcceptingContext;

class FeatureContext implements Context, SnippetAcceptingContext
{
    public function __construct()
    {
    }

    /**
     * @When :time 秒待つ
     */
    public function wait($time)
    {
        sleep($time);
    }
}
googling.feature
  # language: ja
  フィーチャ: グーグル検索

      シナリオ: recursionを検索
          前提 ホームページを表示している
          もし "q" フィールドに "recursion" と入力する
              かつ "btnK" ボタンをクリックする
+             かつ 1 秒待つ
          ならば "もしかして: recursion" と表示されていること

これでbehat -dlコマンドを実行するとテストケースが追加されていることが確認出来ます。

+ default |  When :time 秒待つ
  default | Given /^(?:|ユーザーは )ホームページを表示している$/
  default |  When /^(?:|ユーザーは )ホームページへ移動する$/
  default | Given /^(?:|ユーザーは )"(?P<page>[^\s]+)" を表示している$/u
       ・
       ・
       ・

テストを実行すると今度はパスしました。

フィーチャ: グーグル検索

  シナリオ: recursionを検索                  # features\sample.feature:4
    前提 ホームページを表示している                  # CustomContext::iAmOnHomepage()
    もし "q" フィールドに "recursion" と入力する   # CustomContext::fillField()
    かつ "btnK" ボタンをクリックする              # CustomContext::pressButton()
    かつ 1 秒待つ                          # FeatureContext::wait()
    ならば "もしかして: recursion" と表示されていること # CustomContext::assertPageContainsText()

1 scenario (1 passed)
5 steps (5 passed)
0m9.29s (10.96Mb)

所感

  • テストシナリオが自然言語っていうのが嬉しい、エクセルテスト表とか書きたくないし、見たくもない。
  • seleniumなどのエミュレーションツールを個別に起動しないといけないのが面倒
  • behat公式サイトのドキュメントがバージョン2.5のままで、最新版と思って参考にしてしまうと大いにハマる
  • テスト1回にかかる時間が長いのでpushやcommit時にテスト実行というやり方はキツイ気がする
  • テスト1回にかかる時間が長いのでリリース頻度の高いサービスなどではリリースブランチにマージ前に実行というのもキツイ気がする(テスト実行時間の分リリースが後ろに伸びる、テストがパスしないとさらに長い時間かけてテストするステップを踏むのは辛い)
  • テスト1回にかかる時間が長いので定期的に回す程度がいい?(そうすると誰のコミットでおかしくなったか分かりにくい)

実施できるならシステムの品質を保つのにかなり高い効果が上がると思うが、
実際に運用に組み込む場合の懸念が多いのが難点かな…

E2Eテストやってるところはこのあたり、どうやって解決してるんだろう?

k-motoyan
プログラミング楽しいよ
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away