マニュアルには載っていないCodeceptionのコツ/TIPS

バージョン2で入った新機能などを紹介するエントリなど書く書く詐欺をしていたら年末になってしまいました。
というか2014年、2つめのエントリ。。。。

そんな今年は

  • ZF2 x Codeception
  • Laravel4 x Codeception

を中心にお仕事をしておりました。

社内におけるCodeceptionの実績も溜まってきましたので、その経験をもとにした、マニュアルには載っていないちょっとしたコツやTIPSを紹介したい思います。


Actorにはテンションの上がる名前をつけよう

「マニュアルには載っていない」と前置きしながらいきなりマニュアルに載っていることを。。。

バージョン1では「Guy」固定だったActor名、バージョン2からは自由に名前を決めることができます!
(「FunctionalGuy」、「AcceptanceGuy」の「Guy」部分です。)
正直バージョン2の新機能で最も素晴らしいのはココじゃないか!?

実際のActor名の指定はbootstrapのオプションとして指定します。

php codecept.phar bootstrap -a Actor名

好きなアノ子の名前でも、ペットの名前でも、親しみの湧く名前を付けましょう。

「ああ、いま君はこのフィールドに値を入力してくれているんだね!」
「さぁ、クリックしておくれ、submitボタンを!」
「ああ、君には見えたかい?h1タグに刻まれた僕の想いを!」

胸が熱くなること間違いなしです。
楽しくテストしましょう。


CSSセレクタをシンプルに記述できるようにしよう

$I->see(...), $I->seeElement(...)など、様々な箇所に登場してくるCSSセレクタ
あれ、テストコードの可読性を下げるやっかいなヤツです。
性格にも依るんじゃないかと思いますが、時にはこんなことに。

<?php
$I->see('山田太郎', '#users table tbody tr:nth-child(5) td:nth-child(2)');

まぁ、PageObject使えよ!ということかもしれませんが、
その前にテンプレートのid属性やclass属性をテストを意識したものに整理しておくことをおすすめします。
ただ、テンプレート外注した場合など、idを下手にいじるとデザインに影響が及んだり、もあるわけで。

そんなときはテスト専用のクラスをつけてしまえば良いと開発者の@davertさんはアドバイスしています。

In order to simplify locators used in functional or acceptance tests we add special locator classes into HTML of web pages. So not to confuse them with classes needed by CSS or JavaScript we use lc- prefix. Thus, if you find locator to be to complex, it would be much easier to edit HTML and add a new class into it.

http://phptest.club/t/codeception-tips-tricks/14/8

他にCSSセレクタをシンプルにするため、

  • フォームであればラベルでfillField, seeInField利用できるように、for属性、id属性の対応を適切にしておく
  • 一覧系であれば主キーにもとづいたid属性やクラスをtrに付与しておく

などを実践するようにしています。


テストコードの統一性を保つコツ

Functional Test, Acceptance Testは基本的にCeptフォーマットで記述しています。
開発者によって、

  • wantTo
  • wantToTest
  • expect
  • expectTo
  • comment

に対する理解の違い、使いどころの違いのため、テストコードの統一性が損なわれるという経験をしました。

そこで、現在では以下のようなテンプレートを導入しています。

ぼくのかんがえるさいきょうのCeptてんぷれーと
<?php
$I = new FunctionalGirl($scenario);
$I->wantToTest('テストしたい機能名');
$I->comment('どのようなテストを行うのかの説明、前提事項など');

$I->amGoingTo('操作内容');
$I->comment('特記事項');
$I->amOnPage(...);
$I->fillField(...);
$I->click(...);

$I->expect('操作の結果、期待すること1');
$I->comment('特記事項');
$I->see(...);
$I->seeInField(...)

$I->expect('操作の結果、期待すること1');
$I->comment('特記事項');
$I->see(...);
$I->seeInField(...)

このように、1つのamGoingToに対して複数のexpectを記述する形式にすると頭の中身をそのまま記述していけると考えています。
いまのところ、これが「さいきょう」だと思ってますが、、、、異論は認める。

<?php
$I = new FunctionalGirl($scenario);
$I->wantToTest('[管理画面] ユーザー登録 入力画面');
$I->comment('このテストではユーザー登録用のフォームが正常に動作することを確認します。');

$I->amGoingTo('ログインしてユーザ登録画面へアクセスする');
$I->amLoggedAs(['email' => 'admin@example.com', 'password' => 'password']);
$I->amOnPage("admin/users/create");

$I->expect('フォームの初期値に何も設定されていないこと');
$I->seeInField('ユーザー名', '');
$I->seeInField('メールアドレス', '');
$I->seeInField('パスワード', '');


$I->amGoingTo('何も入力せずに送信ボタンをクリックする');
$I->click("送信");

$I->expect('登録画面が再表示されること');
$I->seeCurrentUrlEquals('admin/users/create');

$I->expect('エラーメッセージが表示されること');
$I->see('入力してください', '#username');
$I->see('入力してください', '#email');
$I->see('入力してください', '#password');

最終手段!モジュールを拡張する方法

CodeceptionはActorへの機能追加を実現するためのHelperという仕組みを提供しています。
Modules and Helpers - Codeception - Documentation

しかしながら、要件によってはモジュール+ヘルパーではテストができないケースがあるかもしれません。
そんなときには最終手段としてモジュール自体を拡張するという方法が残っています。

Laravel4モジュールを例に、拡張の手順を説明します。
(パス等はプロジェクトに合わせて読み替えてください。)

モジュールの拡張手順

1. app/lib/ext/Codeception/ModuleにLaravel4Extクラスを作成

<?php
namespace Codeception\Module;

class Laravel4Ext extends Laravel4
{
}

2. Composerのautoloadに登録

  "psr-0": {
    "Codeception": "app/lib/ext"
  }

3. テスト設定ファイルを書き換える
たとえば、tests/functional.suite.yml

  • 変更前
class_name: FunctionalGirl
modules:
    enabled: [Filesystem, FunctionalHelper, Laravel4]
    config:
        Laravel4:
            cleanup: false
            unit: true
            environment: 'testing'
            filters: true

この、Laravel4をLaravel4Extに変更します。

  • 変更後
class_name: FunctionalGirl
modules:
    enabled: [Filesystem, FunctionalHelper, Laravel4Ext]
    config:
        Laravel4Ext:
            cleanup: false
            unit: true
            environment: 'testing'
            filters: true


これで準備は完了です。
あとはLaravel4Extクラスで既存のメソッドをオーバーライドして好きなようにできます。

注意

Codeceptionに限らず「フレームワークの処理をオーバーライドする」というのは、
他をすべて考え尽くしたあとの最終手段として採用すべき、だと個人的には考えています。
将来的にフレームワーク側のバージョンアップに追随できなくなる可能性がある、というのが一番の理由です。

実際に拡張をした例

Laravel4モジュールの場合、DBに対する変更を確認するためのAPIとして、

  • seeRecord
  • dontSeeRecord

があります。

とあるアプリケーションにて、アプリケーションが使用するユーザーとは別のユーザーが所有するテーブルにデータを永続化する、という機能がありました。
さらに、セキュリティポリシーによりアプリケーションデフォルトのユーザーは永続先テーブルに対する権限は何もありません。

そんな機能をテストするためには、テスト中にデータベースのコネクションを切り替えられるようにしなければなりません。

  • Helperを使いswitchDatabaseConnectionを追加する
  • seeRecord, dontSeeRecordにコネクション名を指定できるようにする

という2つの選択肢を考えました。
前者ではコネクションを戻すのを忘れた場合に後続のテストに影響がある(=うっかりミスで開発効率を下げる可能性がある)ため、後者を採用し、以下のようにしました。
(「コレをしたらアレを必ず」ルールが増えるのは大変です)

<?php
public function seeRecord($model, $attributes = array(), $connection = self::DEFAULT_CONNECTION)
{
    \DB::setDefaultConnection($connection);
    parent::seeRecord($model, $attributes);
    \DB::setDefaultConnection(self::DEFAULT_CONNECTION);
}
?>

1年半前から使いはじめたCodeception、当時のバージョンは1.6.3.1。現在のバージョンは2.0.9。
もっと日本のPHPerに広めたいですね。来年こそはphpconに。。。


以上となります。