View on GitHub

Codeception.docs.ja

Codeception docs japanese translation

Download this project as a .zip file Download this project as a tar.gz file

高度な使用法

この章ではあなたのテストのエクスペリエンスを向上させ、プロジェクトをより良い組織に保つためのいくつかのテクニックとオプションについて取り上げます。

Cestクラス

もしCept形式に対して、クラスのような構造を持たせたい場合、プレーンなPHPの替わりにCest形式を利用することができます。 Cest形式はとても単純でCept形式のシナリオと完全な互換性があります。これはつまり、テストが非常に長くなり分割したいと感じた場合、簡単にクラスに移動することができることを意味します。

次のコマンドによりCestを作成することができます。

$ php codecept.phar generate:cest suitename CestName

生成されたファイルは次のようになります。

<?php
class BasicCest
{
    public function _before(\AcceptanceTester $I)
    {
    }

    public function _after(\AcceptanceTester $I)
    {
    }

    // tests
    public function tryToTest(\AcceptanceTester $I) 
    {    
    }
}
?>

それぞれのpublicなメソッド(「_」で始まるものを除く)はテストとして実行され、最初の引数としてアクタークラスを、2つ目の引数して$scenario変数を受け取ります。

_before_afterメソッドはそのクラスのテストに対する共通のセットアップとティアダウンのために使用することができます。このことが、類似の処理をヘルパークラスのみに依存するCept形式と比較して、Cest形式をより柔軟にしています。

ご覧のように、tryToTestメソッドにアクターオブジェクトを渡しています。このオブジェクトを使ってこれまでにやってきた方法でシナリオを描くことができます。

<?php
class BasicCest
{
    // test
    public function checkLogin(\AcceptanceTester $I) 
    {
        $I->wantTo('log in to site');
        $I->amOnPage('/');
        $I->click('Login');
        $I->fillField('username', 'john');
        $I->fillField('password', 'coltrane');
        $I->click('Enter');
        $I->see('Hello, John');
        $I->seeInCurrentUrl('/account');
    }
}
?>

このように、Cestクラスは\Codeception\TestCase\TestPHPUnit_Framework_TestCaseといった親クラスを持ちません。意図的にそうしています。これにより、子クラスで使用することができる共通の振る舞いや処理を伴ってクラスを拡張することができます。ただし、それらのメソッドはテストとして実行されないようprotectedなメソッドとして定義することを忘れないでください。

また、テストがerrorとなったり失敗した時に呼ばれる_failedメソッドについてもCestクラスに定義することができます。

Before/Afterアノテーション

@before@afterアノテーションを使ってテストの実行フローをコントロールすることができます。共通のアクションをprotectedな(テストでない)メソッドに移動し、アノテーションを記述することによってテストの前後でそれらを呼び出すことができます。@before@afterアノテーションを複数利用することによって、複数のメソッドを呼び出すことが可能です。メソッドは上から下の順で呼び出されます。

<?php
class ModeratorCest {

    protected function login(AcceptanceTester $I)
    {
        $I->amOnPage('/login');
        $I->fillField('Username', 'miles');
        $I->fillField('Password', 'davis');
        $I->click('Login');
    }

    /**
     * @before login
     */
    public function banUser(AcceptanceTester $I)
    {
        $I->amOnPage('/users/charlie-parker');
        $I->see('Ban', '.button');
        $I->click('Ban');
    }

    /**
     * @before login
     * @before cleanup
     * @after logout
     * @after close
     */
    public function addUser(AcceptanceTester $I)
    {
        $I->amOnPage('/users/charlie-parker');
        $I->see('Ban', '.button');
        $I->click('Ban');
    }
}
?>

@before@afterアノテーションはインクルードされた関数に対しても使えます。ただし、1つのメソッドに対して同じ種類のアノテーションを複数記述することはできません。1つのメソッドに対して、1つの@beforeと1つの@afterアノテーションのみですd - one method can have only one @before and only one @after annotation.

Dependsアノテーション

@dependsアノテーションを使って現在のテストが事前にどのテストにパスしているべきか、指定することができます。もし事前のテストが失敗したら、対象のテストはスキップされます。 依存しているテストのメソッド名をアノテーションに記述してください。

<?php
class ModeratorCest {

    public function login(AcceptanceTester $I)
    {
        // logs moderator in
    }

    /**
     * @depends login
     */
    public function banUser(AcceptanceTester $I)
    {
        // bans user
    }
}
?>

ヒント:@dependsアノテーションは@beforeアノテーションと組み合わせることができます。

インタラクティブ・コンソール

インタラクティブ・コンソールはCodeceptionのコマンドをテストで実行する前に試すために追加されました。

コンソール

次のコマンドでコンソールを起動することができます。

$ php codecept.phar console suitename

これで、アクタークラスに対応したすべてのコマンドを実行することができ、結果をすぐに確認できます。特に、WebDriverモジュールと使用する場合に便利です。Selenuimとブラウザーの起動にはいつも長い時間がかかります。ところが、コンソールを使って別のセレクタ、異なるコマンドを試すことで、実行された際に確実にパスするテストを書くことができます。

特別なヒント:コンソールとSeleniumを使ってあなたがウェブページを上手に操作できるか上司に見せましょう。この手順を自動化し、プロジェクトに受け入れテストを導入することを説得しやすくなるでしょう。

異なるフォルダーからテストを実行する

もしCodeceptionを使ったプロジェクトが複数あったとしても、すべてのテストを1つのcodecept.pharで実行することができます。 異なるディレクトリーのCodeceptionを実行するために、bootstrapを除くすべてのコマンドに-cオプションが用意されています。

$ php codecept.phar run -c ~/projects/ecommerce/
$ php codecept.phar run -c ~/projects/drupal/
$ php codecept.phar generate:cept acceptance CreateArticle -c ~/projects/drupal/

現在の場所と異なるディレクトリー内にプロジェクトを作成するには、パラメータとしてパスを指定するだけです。

$ php codecept.phar bootstrap ~/projects/drupal/

基本的に-cオプションにはパスを指定しますが、使用する設定ファイルを指定することもできます。従って、テストスイートに対して複数のcodeception.ymlを持つことができます。さまざまな環境や設定を指定するためにこのオプションを使用することができます。特定の設定ファイルを使ってテストを実行するためには、-cオプションにファイル名を渡してください。

グループ

テストをグルーピングして実行する方法はいくつかあります。特定のディレクトリを指定して実行することもできます。

$ php codecept.phar run tests/acceptance/admin

また、特定の(もしくは複数の)グループを指定して実行することもできます。

$ php codecept.phar run -g admin -g editor

このケースではadmineditorグループに属すすべてのテストが実行されます。グループの概念はPHPUnitから取り入れたもので、従来のPHPUnitと同様の振る舞いをします。Ceptをグループに追加するには、scenario変数を使います。

<?php
$scenario->group('admin');
$scenario->group('editor');
// or
$scenario->group(array('admin', 'editor'))
// or
$scenario->groups(array('admin', 'editor'))

$I = new AcceptanceTester($scenario);
$I->wantToTest('admin area');
?>

Test形式とCest形式のテストでは、テストをグループに追加するために@groupアノテーションを使います。

<?php
/**
 * @group admin
 */
public function testAdminUser()
{
    $this->assertEquals('admin', User::find(1)->role);
}
?>

Cestクラスでも同じアノテーションを使用できます。

グループファイル

グループはグローバルまたはテストスイートの設定ファイルで定義できます。 グループに含まれるテストは、配列形式で指定するか、テストの一覧が含まれるファイルパスを指定します。

groups:
  # add 2 tests to db group
  db: [tests/unit/PersistTest.php, tests/unit/DataTest.php]

  # add list of tests to slow group
  slow: tests/_data/slow  

たとえば、最も遅いテストの一覧ファイルを作成し、それらのグループ内で実行することができます。 グループファイルは各行にテスト名が記述されたプレーンテキストファイルです。

tests/unit/DbTest.php
tests/unit/UserTest.php:create
tests/unit/UserTest.php:update

グループファイルは手動で作成することもできますし、サードパーティ製のアプリケーションによって生成することもできます。 たとえば、XMLレポートから遅いテストを抽出してグループファイルを更新するスクリプトを記述することができるでしょう。

1つの定義で複数のグループファイルをロードするような、パターンについても指定することもできます。

groups:
  p*: tests/_data/p*

これは、tests/_data内のp*パターンに一致するすべてのファイルをグループとしてロードします。

リファクタリング

テストが成長していくにつれ、共通の変数や振る舞いを共有するためのリファクタリングが必要になります。古典的な例は、テストスイートのあらゆる箇所で呼び出されるloginアクションです。これは、1度だけ記述しすべてのテストで使用するようにするのが賢明です。

このようなケースにおいてPHPクラスの中にそれらのメソッドを定義するというのは明白です。

<?php
class TestCommons
{
    public static $username = 'john';
    public static $password = 'coltrane';

    public static function logMeIn($I)
    {
        $I->amOnPage('/login');
        $I->fillField('username', self::$username);
        $I->fillField('password', self::$password);
        $I->click('Enter');
    }
}
?>

そして、このファイルを_bootstrap.phpファイルから読み込みます。

<?php
// bootstrap
require_once '/path/to/test/commons/TestCommons.php';
?>

シナリオの中で使います。

<?php
$I = new AcceptanceTester($scenario);
TestCommons::logMeIn($I);
?>

もしアイディアを得られたら、テストコードを構造化するためのいくつかのビルトイン機能について学びましょう。CodeceptionにおけるPageObjectStepObjectパターンの実装について学びます。

ページオブジェクト

ページオブジェクトパターンはテスト自動化エンジニアの間では広く使われています。ページオブジェクトパターンでは、ウェブページをクラスとして、ページ上のDOM要素をプロパティとして表現し、いくつかの基本的なインタラクションをメソッドして持ちます。 ページオブジェクトはテストの柔軟なアーキテクチャを作りこむ際にとても重要です。複雑なCSSやXPathロケーターをテストにハードコードするのではなく、ページオブジェクトクラスに移動してください。

Codeceptionは次のコマンドでページオブジェクトクラスを生成することができます。

$ php codecept.phar generate:pageobject Login

これにより、tests/_pages内にLoginPageクラスが作成されます。基本のページオブジェクトはいくつかのスタブを持った空クラス以上の何ものでもありません。 空クラスであることは、ページオブジェクトがページを表現するUIロケーターとともに準備されることを期待しており、それらのロケーターはページで利用されるでしょう。 ロケーターはpublicでstaticなプロパティとして定義されます。

<?php
class LoginPage
{
    public static $URL = '/login';

    public static $usernameField = '#mainForm #username';
    public static $passwordField = '#mainForm input[name=password]';
    public static $loginButton = '#mainForm input[type=submit]';
}
?>

そして、ページオブジェクトはテストの中で次のように使用されます。

<?php
$I = new AcceptanceTester($scenario);
$I->wantTo('login to site');
$I->amOnPage(LoginPage::$URL);
$I->fillField(LoginPage::$usernameField, 'bill evans');
$I->fillField(LoginPage::$passwordField, 'debby');
$I->click(LoginPage::$loginButton);
$I->see('Welcome, bill');
?>

このとおり、あなたはログインページのマークアップを気兼ねなく変更することができ、このページを対象とするすべてのテストに含まれるロケーターはLoginPageクラスのプロパティに従って更新されるでしょう。

しかし、ここでさらに先に進みましょう。ページオブジェクトの概念は、ページのインタラクションを行うメソッドについてもページオブジェクトクラスに含まれるべき、と規定しています。 先ほど作成したLoginPageクラスではこれを行うことはできません。なぜなら、このクラスはすべてのテストスイートからアクセス可能なので、どのアクタークラスをインタラクションに使用するのかわからないためです。そのため、別のページオブジェクトを作成する必要があります。この例では、ページオブジェクトを使用するテストスイートを明示します。

$ php codecept.phar generate:pageobject acceptance UserLogin

先ほど作成したLoginクラスと名前が衝突しないよう、UserLoginクラスとします

作成されたUserLoginPageクラスは1つの違いをのぞき、ほぼ先ほどのLoginPageクラスと同じように見えます。今度は渡されたアクタークラスのインスタンスを保持しています。AcceptanceTesterにAcceptanceTesterプロパティを介してアクセスできます。では、このクラスにloginメソッドを定義しましょう。

<?php
class UserLoginPage
{
    // include url of current page
    public static $URL = '/login';

    /**
     * @var AcceptanceTester
     */
    protected $AcceptanceTester;

    public function __construct(AcceptanceTester $I)
    {
        $this->AcceptanceTester = $I;
    }

    public static function of(AcceptanceTester $I)
    {
        return new static($I);
    }

    public function login($name, $password)
    {
        $I = $this->AcceptanceTester;

        $I->amOnPage(self::$URL);
        $I->fillField(LoginPage::$usernameField, $name);
        $I->fillField(LoginPage::$passwordField, $password);
        $I->click(LoginPage::$loginButton);

        return $this;
    }    
}
?>

そして、これがテストの中でページオブジェクトを使用する方法の例です。

<?php
$I = new AcceptanceTester($scenario);
UserLoginPage::of($I)->login('bill evans', 'debby');
?>

おそらく、UserLoginPageLoginPageとは同じ役割を担っているようなので、マージすべきかもしれません。しかし、LoginPageが機能テストと受け入れテストの両方で使える一方、UserLoginPageAcceptanceTesterでのみ使うことができます。ですので、グローバルなページオブジェクトを利用するかテストスイート単位のページオブジェクトを使用するかはあなた次第です。もし機能テストと受け入れテストとに多くの共通点がある場合は、グローバルなページオブジェクトにロケーターを格納し、ページオブジェクトの振る舞いの部分については代替としてステップオブジェクトを使ってください。

ステップオブジェクト

ステップオブジェクトパターンはBDDフレームワークに由来します。ステップオブジェクトは異なるテストで広く使われるであろう共通のアクション群を含みます。 前述のloginメソッドはそのような共通アクションの良い例です。似たようなアクションであるリソースの作成・更新・削除についてもステップオブジェくトに移動するべきです。ステップオブジェクトを作成し、どのようなものか見てみましょう。

Memberステップクラスを作成しましょう。ジェネレーターが含まれるメソッドを聞いてくるので、loginを追加しましょう。

$ php codecept.phar generate:stepobject acceptance Member

アクション名を入力するよう求められますが、これはオプションです。はじめに「login」を入力し、エンターキーを押します。すべての必要なアクションを指定した後、ステップオブジェクトの作成に進むには空行のままにしておきます。

$ php codecept.phar generate:stepobject acceptance Member
Add action to StepObject class (ENTER to exit): login
Add action to StepObject class (ENTER to exit):
StepObject was created in <you path>/tests/acceptance/_steps/MemberSteps.php

これで次のようなtests/acceptance/_steps/MemberSteps.phpクラスが作成されます。

<?php
namespace AcceptanceTester;

class MemberSteps extends \AcceptanceTester
{
    public function login()
    {
        $I = $this;

    }
}
?>

ご覧のようにとてもシンプルなクラスです。AcceptanceTesterクラスを継承しているので、AcceptanceTesterのすべてのメソッドとプロパティがこのクラス内で利用可能です。

loginメソッドは次のように実装できるでしょう。

<?php
namespace AcceptanceTester;

class MemberSteps extends \AcceptanceTester
{
    public function login($name, $password)
    {
        $I = $this;
        $I->amOnPage(\LoginPage::$URL);
        $I->fillField(\LoginPage::$usernameField, $name);
        $I->fillField(\LoginPage::$passwordField, $password);
        $I->click(\LoginPage::$loginButton);
    }
}
?>

テストではAcceptanceTesterの代わりにMemberStepsクラスをインスタンス化することでステップオブジェクトを使用することができます。

<?php
$I = new AcceptanceTester\MemberSteps($scenario);
$I->login('bill evans', 'debby');
?>

このように、ステップオブジェクトクラスは従来のページオブジェクトと比較してより単純で可読性に優れています。 ステップオブジェクトの代替としてAcceptanceHelperクラスを利用することができます。ヘルパーの内部では$Iオブジェクトそのものにアクセスできないため、ヘルパーは新しいアクションを実装するために使い、ステップオブジェクトは共通のシナリオを統合するために使うのが良いでしょう。

環境

別の設定を使ってテストを実行する必要がある場合、別の設定環境を定義することができます。 最も典型的なユースケースとしては、種類の違うブラウザーで受け入れテストを実行する場合や、異なるデータベースエンジンを利用してテストを実行する場合です。

ブラウザーのケースについて、環境をどう使うか、実際にやってみましょう。

acceptance.suite.ymlに新しく行を追加する必要があります。

class_name: AcceptanceTester
modules:
    enabled:
        - WebDriver
        - AcceptanceHelper
    config:
        WebDriver:
            url: 'http://127.0.0.1:8000/'
            browser: 'firefox'

env:
    phantom:
         modules:
            config:
                WebDriver:
                    browser: 'phantomjs'

    chrome:
         modules:
            config:
                WebDriver:
                    browser: 'chrome'

    firefox:
        # nothing changed

最初はこのような設定の構造は汚く見えるかもしれませんが、これが最もクリーンな方法です。 基本的に、ルートのenv配下に異なる環境を定義し、名前を付け(phantomとかchromeなど)、そしてすでに設定されている任意のパラメータを再定義します。

テスト実行する際に、--envオプションによって容易にこれらの設定を切り替えることができます。PhantomJSのみのテストを実行するには、次のように--env phantomを渡します。

$ php codecept.phar run acceptance --env phantom

3つのブラウザーすべてのテストを実行するには、単にすべての環境を列挙します。

$ php codecept.phar run acceptance --env phantom --env chrome --env firefox

異なるブラウザーごとに3回テストが実行されるでしょう。

環境に応じて、実行されるべきテストを選択することができます。 たとえば、いくつかのテストはFirefoxでのみ実行され、他のいくつかはChromeのみで実行されるようにする必要があるかもしれません。

Test形式、Cest形式のテストでは、@envアノテーションを使って求められる環境を指定することができます。

<?php
class UserCest
{
    /**
     * This test will be executed only in 'firefox' and 'phantom' environments
     *
     * @env firefox
     * @env phantom
     */
    public function webkitOnlyTest(AcceptanceTester $I)
    {
        // I do something
    }
}
?>

Cept形式では$scenario->env()を使ってください。

<?php
$scenario->env('firefox');
$scenario->env('phantom');
// or
$scenario->env(array('phantom', 'firefox'));
?>

この方法で、どのブラウザーでどのテストを行うか、簡単にコントロールすることができます。

まとめ

Codeceptionは一見シンプルなフレームワークのように見えますが、強力なテストを、単一のAPIを使って、それらをリファクタリングし、そしてインタラクティブ・コンソールを使ってより速く、構築することができます。CodeceptionのテストはグループやCestクラスを使って容易に整理することができ、ロケーターはページオブジェクトに、そして共通のステップはステップオブジェクトに統合することができます。

おそらく単体のフレームワークとしては機能が多すぎるかもしれません。しかし、それにもかかわらずCodeceptionはKISSの原則に従っています。Codeceptionは簡単に始めることができ、習得が容易で、楽に拡張することができるのです。