codeceptionをcakephp3で扱ってみるチュートリアル


cakephp3アドベントカレンダー15日めはcodeceptionを使ったテスト事始めです！
年末になると何もないのに楽しみでそわそわしますね！

14日目は@opparaさんの記事になります。
[CakePHP Migrations の limit オプションについて](https://qiita.com/oppara/items/a903e174136049aa9137)

## codeceptionとは
phpによるテストフレームワークです。
ユニットテスト、ファンクショナルテスト、受け入れテストとの全てに対応し、直感的な書き方ができます。ドキュメントも充実しているので、一度お試ししてみてください。
http://codeception.com/

## 試した環境
- vagrant
- centos7
- php7.1
- google chrome
- chrome driver

**注意**
phantomjsを使うこともできますが、動かない関数があるのでおすすめしません。

### google chrome + chromedriver導入
以下のURLを参考に、ご自身の環境にchromeとchromedriverを導入して下さい。

- https://qiita.com/yuki-k/items/716bd05f087fc0f835bf
- https://sites.google.com/a/chromium.org/chromedriver/downloads
- chrome driverは適当にbin/フォルダにでも入れておきます。 

## アプリケーション作成
テストするアプリケーションがないと始まらないので、一から作っていきましょう。
`composer create-project --prefer-dist cakephp/app codeception`

### DB設定
`config/app.php`を自身のDB設定に合わせて設定します。

```
'username' => 'app_test',
'password' => 'app_test_P@ssw0rd',
'database' => 'app_test',
```

### モデル、テンプレート作成
Pagesというモデルを適当に作成します。

```
bin/cake bake migration CreatePages title:string content:text
bin/cake migrations migrate
bin/cake bake all pages
chmod 777 tmp/
chmod -R 666 tmp/*
```

`config/routes.php`を以下のように修正

```
$routes->connect('/', ['controller' => 'Pages', 'action' => 'index', 'home']);
$routes->resources('Pages');
```

ここまでで基本的なアプリケーションの雛形が完成しました
## codeception導入
いよいよcodeceptionを導入し、テストを書いていきます。

`composer require "codeception/codeception" --dev`

初期に作られたtestsフォルダは必要ないので、一旦削除してください

- codeceptionの初期ファイル生成を行います。
`vendor/bin/codecept bootstrap`
これで、testsフォルダと必要なファイルが生成されます。

ここで、以下を実行すると、次のような画面になります。
`vendor/bin/codecept run functional`

<img width="541" alt="Screen Shot 2017-12-14 at 21.28.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/123028/ca635dfa-5e8a-8892-90d7-1dac370a8c09.png">


### 設定ファイルへの記述
chrome driverを使ってフォームの入力等を行いたいので、初期設定を記述します。
`codeception.yml`

```
paths:
    tests: tests
    output: tests/_output
    data: tests/_data
    support: tests/_support
    envs: tests/_envs
actor_suffix: Tester

extensions:
    enabled:
      - Codeception\Extension\RunFailed
modules:
  config:
    Db:
      dsn: 'mysql:host=localhost;dbname=your database name'
      user: 'your database name'
      password: 'your database pass'

```

`tests/functional.suite.yml`

```
actor: FunctionalTester
extensions:
    enabled:
        - Codeception\Extension\Recorder:
            delete_successful: false
        - Codeception\Extension\RunProcess:
            0: bin/chromedriver --url-base=wd/hub
            sleep: 3


modules:
    enabled:
        # add a framework module here
        - \Helper\Functional
        - Db
        # - DataFactory:
        #     depends: Doctrine2
        # - \Helper\Factories

        - WebDriver:
            url: 'http://localhost'
            browser: chrome
            port: 9515
            window_size: 1024x768
            capabilities:
              chromeOptions:
                args: ["--headless", "--disable-gpu", "--disable-extensions"]
                binary: "/usr/bin/google-chrome"

```
### 基本テスト作成
ではテストを書いていきましょう！
まず、テストの雛形を生成します

- `vendor/bin/codecept g:cest functional PagesCest`
- 以下のように編集します

`tests/functional/PagesCest.php`

```
<?php


class PagesCest
{
    public function _before(FunctionalTester $I)
    {
    }

    public function _after(FunctionalTester $I)
    {
    }

    // tests
    public function addPage(FunctionalTester $I)
    {
        $I->amOnPage('/pages/add');
        $I->fillField('title', 'アドベントカレンダー初参加です');
        $I->fillField('content', 'お手柔らかによろしくお願いします！');
        $I->click('Submit');
        $I->see('アドベントカレンダー初参加です');
    }
}
```

テストを実行した後に記事が作成されていることがわかります。
<img width="801" alt="Screen Shot 2017-12-14 at 22.13.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/123028/797cf10a-4721-649f-8e77-d9f3e22a4e7d.png">


### 削除メソッドへの対応
今度は作成した項目を削除してみましょう。
先程のファイルに以下のメソッドを追加します

```
    public function deletePage(FunctionalTester $I)
    {
        $I->amOnPage('/pages');
        $I->acceptPopup($I->click('Delete'));
        $I->see('The page has been deleted.');
    }
```
acceptPopup関数でポップアップの際にOKボタンを押しています。

### データベースの中身を直接チェック
次はデータベースの中身を直接チェックしましょう。

```
    public function seeRecord(FunctionalTester $I)
    {
        $I->seeInDatabase('pages', ['title' => 'アドベントカレンダー初参加です']);
    }
```
タイトルが"アドベントカレンダー初参加です"になっているレコードが存在すればテストが通ります。

### ログインの必要なテスト
その他にも、codeceptionではログイン情報を保持しながらテストをしていくことが可能です。以下のように書くことで、セッションを維持しながらテストを行うことができます。
作った関数を各テストファイルの`_after`の中に書いて使います。

`tests/_support/FunctionalTester.php`

```
    public function login($username, $password)
    {
        $I = $this;
        // セッションがあったらログイン処理をスキップする
        if ($I->loadSessionSnapshot('login')) {
            return;
        }
        $I->amOnPage('/users/login');
        $I->fillField('username', $username);
        $I->fillField('password', $password);
        $I->click('ログイン');
        // ログイン状態を保持する
        $I->saveSessionSnapshot('login');
    }
```