2016.12.15

Codeceptionの導入事例

Pocket

こんにちは。次世代システム研究室のS.Iです。
次世代システム研究室ではシステム開発を行う上で様々な取り組みを行っています。
現在進行中のプロジェクトでは、Codeceptionを利用して生産性や品質を向上させる取り組みを行っています。ここではその導入事例について触れていきたいと思います。

Codeceptionによるテストの自動化

概要

CodeceptionはテストフレイムワークでLaravel5やsymfony、Yii2、Zend FrameworkなどのPHP Frameworkをサポートしています。また、コードジェネレータが提供されているため、簡単なサンプルのテストコードを素早く生成することが出来ます。

コードを生成する

「./codecept list」で以下のように各コマンドを表示することができます。まずは「generate:suite」設定ファイルを作成し、各テストプログラムを生成していきます。
./codecept list
generate
  generate:cept         Generates empty Cept file in suite
  generate:cest         Generates empty Cest file in suite
  generate:environment  Generates empty environment config
  generate:feature      Generates empty feature file in suite
  generate:groupobject  Generates Group subscriber
  generate:helper       Generates new helper
  generate:pageobject   Generates empty PageObject class
  generate:phpunit      Generates empty PHPUnit test without Codeception additions
  generate:scenarios    Generates text representation for all scenarios
  generate:stepobject   Generates empty StepObject class
  generate:suite        Generates new test suite
  generate:test         Generates empty unit test file in suite

導入事例

方針を検討する

テストプログラムは品質を維持し続けるために継続的なメンテナンスが必要です。細かい実装をしてしまうとメンテナンスにコストがかかったり、実装者しかわからない状態となり結果的に利用されなくなってしまいます。そこで、今回は自動化する範囲を以下に限定し導入することにしました。

画面系
アクセプタンステスト
※画面の描画と一連の入力から完了までのフローが正常に動作できることを確認する

API系
ファンクションテスト
※各APIごとに正常系と返却される全てのエラーコードが想定どおり返却されることを確認する
ユニットテスト
※各APIごとの入力のヴァリデーションが想定どおり実行できることを確認する

導入時のポイント

進めていく上でわかってきたポイントを以下でいくつか紹介します。

ポイント1:WebdriverはPhp Browserを利用すべし

要否に応じて使い分けが必要になりますが、「phantomjs」や「Selenium2」などの Webdriverを利用する場合は高機能な反面、インストールや実行時のプロセスの管理などが面倒なのと、実行が遅いのが気になります。シンプルなことしかしないとわりきって「Php Browser」を利用するのがおすすめです。

Php Browser
メリット:PHP,Curlさえあればすぐ実行できる、実行が早い
デメリット:Javascriptが利用できない

ポイント2:CeptよりCestを利用すべし

テストプログラムの実装パターンで「Cept」と「Cest」があります。CeptはシンプルにCestはオブジェクト指向的な実装になります。

Cept
<?php    
$I = new AcceptanceTester($scenario);
$I->wantTo('perform actions and see result');
$I->amOnPage('/login');
$I->fillField('user_id','aaa');
$I->fillField('password','aaa');
$I->click('ログイン');
$I->see('TOP');
Cest
<?php    
class LoginCest
{
    public function login(\AcceptanceTester $I) 
    {
        $I->wantTo('perform actions and see result');
        $I->amOnPage('/login');
        $I->fillField('user_id','aaa');
        $I->fillField('password','aaa');
        $I->click('ログイン');
        $I->see('TOP');     
    }
}
Cestではアノテーションを利用し、より体系だったテストプログラムを実装することができます。たとえば、以下の例ではDataproviderを利用して、あるメソッドに対して指定したパラメータ分の実行を行うことができます。また、他にも@beforeや@afterを利用することで前提条件などを設定することができ、非常に便利です。※二つ目のメソッドはメソッド名に「_」を利用することでテスト実行が不要なことをあらわしています。

サンプル実装
    /**
     * 正常系テスト
     * @param ApiTester $I
     * @dataprovider _type
     */
    public function check(ApiTester $I, \Codeception\Example $example)
    {
        $I->wantTo('api execute test. type ' . $example['type']);
        $I->haveHttpHeader('Content-Type', 'application/json');
        $I->sendPOST('api/a', ['type' => $example['type']]);
        $I->seeResponseCodeIs(200);
        $I->seeResponseIsJson();
        $I->seeResponseContainsJson(['result' => 'success']);
        $I->seeResponseContainsJson(['data1' => 'a']);
        $I->seeResponseContainsJson(['data2' => 'b']);
    }

    public function _type()
    {
        return [
            ['type'=>"a"],
            ['type'=>"b"],
            ['type'=>"c"],
            ['type'=>"d"],
            ['type'=>"e"],
        ];
    }
実行結果
Codeception PHP Testing Framework v2.2.7
Powered by PHPUnit 4.8.31 by Sebastian Bergmann and contributors.

Api Tests (5) ------------------------------------------
✔ TestGetCest: Api execute test. type a (0.54s)
✔ TestGetCest: Api execute test. type b (0.15s)
✔ TestGetCest: Api execute test. type c (0.15s)
✔ TestGetCest: Api execute test. type d (0.14s)
✔ TestGetCest: Api execute test. type e (0.14s)
--------------------------------------------------------

ポイント3:APIの実行結果を解析すべし

JSONでやり取りするようなAPIは「seeResponseContainsJson」で実行の確認と「grabDataFromResponseByJsonPath」で、API Aの実行結果をパースして、API Bの実行に利用することができます。
$I->wantTo('perform actions and see result');
$I->haveHttpHeader('Content-Type', 'application/json');
//api a
$I->sendPOST('/api/a', []);
$id = $I->grabDataFromResponseByJsonPath('$.data')[0]['id'];
//api b
$I->sendPOST('/api/b', ['id'=>$id]);
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContainsJson(['result' => 'success']);

最後に

実装時にテストプログラムを作成しておいたおかけで、全体的に影響のある修正をした際にもすぐに確認を取ることができました。引き続き、効果的な手法がないか追っていければと思います。

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。