CakePHP3 のアプリケーションを Behat でテストする（update編）

CakePHP Advent Calendar 2017の9日目です（ギリ間に合った...
@o0h さんの昨日の記事は、[CakePHP3のコントローラーのテストはどうするか](https://qiita.com/o0h/items/ea8d1b3604c37c3a8287)でした。

CakePHP3でBDDをするには、 [Behat](http://behat.org/) を使うことでシナリオテストを記述することができます。
BehatはE2Eテストをするのに適したテスティングフレームワークで、自然言語（つまり日本語）で記述できるのが特徴です。

CakePHP2でBDDをしたい方は、私作ではありますが [CakePHP2 BDDプラグイン](https://github.com/sizuhiko/Bdd)がありますので、ご利用ください。

# きっかけ

実は最近BehatとかBDDとか言ってなかったのですが、このツイートが飛んできました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr"><a href="https://twitter.com/sizuhiko?ref_src=twsrc%5Etfw">@sizuhiko</a> Kenichiro-san, Thank you for the <a href="https://twitter.com/cakephp?ref_src=twsrc%5Etfw">@cakephp</a> 3 BDD tutorial. I am also viewing your CakeFest 2012 presentation. <a href="https://t.co/4X3qxePvmw">https://t.co/4X3qxePvmw</a></p>&mdash; Ed Barnard (@ewbarnard) <a href="https://twitter.com/ewbarnard/status/936323445461274624?ref_src=twsrc%5Etfw">2017年11月30日</a></blockquote>

うわー、2年以上もメンテしてなかった[CakePHP3のBDDチュートリアル](https://github.com/sizuhiko/cakephp3-bdd-example)です。

そしてインターネット怖いね、CakePHP + Behatについて[CakeFestで発表した動画 - 2014年](https://www.youtube.com/watch?v=V8FMj9IMJuw)まであるよ。

`これ、最新版のPHPとかCakePHPでも動くのかな....`

と心配になったのは言うまでもありません。

# ２年前の記憶

こういうBlogを日本語でも書いていました。
[CakePHP3 のアプリケーションを Behat でテストする](http://blog.open.tokyo.jp/2015/05/17/cakephp3-behat.html)

今回は、このブログ記事で書いていた内容を、現時点のバージョンにアップグレードしてみたよ編としてお送ります。

# アップデートしてみた

## PHP7.1 へのアップグレード

- Vagrant/Virtual Boxを最新版にする
- まっさらな状態から手順を進めてみる
- cakeboxのサイトにPHP 7.1へのアップグレード手順があったのでやってみる

さて、ここでPHP7.1 のアップグレードで失敗します。
どうもアップグレードシェルでPHP7をインストールするのにPPAリポジトリを追加するため `add-apt-repository` コマンドを使っているのですが、見つからないとエラーになります。

これを予めインストールするように README.mdに追記しました。

```
localhost:cakebox $ vagrant ssh
vagrant@cakebox $ sudo apt-get update
vagrant@cakebox $ sudo apt-get install software-properties-common python-software-properties
vagrant@cakebox $ /cakebox/bash/ubuntu-16.sh
vagrant@cakebox $ exit
localhost:cakebox $ vagrant reload
```

アップグレードが終わるとPHPのバージョンが7.1.12になっています。

```
localhost:cakebox $ vagrant ssh
vagrant@cakebox:~$ php -v
PHP 7.1.12-2+ubuntu16.04.1+deb.sury.org+2 (cli) (built: Dec  7 2017 20:12:04) ( NTS )
```

## 依存関係のアップグレード

いったん以下の手順まで進めます。
```
vagrant@cakebox:~/Apps/blog-tutorial.app$ composer install 
```

これで `composer.lock` ファイルから2年前のバージョンがインストールされます。

`composer.json` をみると、そこそこ書き換わってくれそうですので、 `composer update` で全アップデートをかけます（update された部分だけ抜粋しています）。

```
vagrant@cakebox:~/Apps/blog-tutorial.app$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 24 installs, 53 updates, 6 removals
  - Updating cakephp/plugin-installer (0.0.11 => 1.0.0): Downloading (100%)         
  - Updating mobiledetect/mobiledetectlib (2.8.12 => 2.8.28): Downloading (100%)         
  - Updating psr/log (1.0.0 => 1.0.2): Downloading (100%)         
  - Updating cakephp/cakephp (3.0.1 => 3.4.1): Downloading (100%)         
  - Updating robmorgan/phinx (v0.4.3 => v0.8.1): Downloading (100%)         
  - Updating cakephp/migrations (1.1.0 => 1.7.1): Downloading (100%)         
  - Updating vlucas/phpdotenv (v1.1.0 => v1.1.1): Downloading (100%)         
  - Updating jakub-onderka/php-console-highlighter (v0.3.1 => v0.3.2): Downloading (100%)         
  - Updating nikic/php-parser (v1.2.2 => v3.1.2): Downloading (100%)         
  - Updating psy/psysh (v0.4.4 => v0.8.15): Downloading (100%)         
  - Updating cakephp/debug_kit (3.0.1 => 3.11.2): Downloading (100%)         
  - Updating cakephp/bake (1.0.4 => 1.3.7): Downloading (100%)         
  - Updating behat/mink (v1.6.1 => v1.7.1): Downloading (100%)         
  - Updating behat/transliterator (v1.0.1 => v1.2.0): Downloading (100%)         
  - Updating behat/gherkin (v4.3.0 => v4.5.1): Downloading (100%)         
  - Updating behat/behat (v3.0.15 => v3.4.3): Downloading (100%)         
  - Updating behat/mink-extension (v2.0.1 => 2.3.0): Downloading (100%)         
  - Updating guzzlehttp/guzzle (5.2.0 => 6.3.0): Downloading (100%)         
  - Updating fabpot/goutte (v2.0.3 => v3.2.2): Downloading (100%)         
  - Updating behat/mink-browserkit-driver (v1.2.0 => v1.3.2): Downloading (100%)         
  - Updating behat/mink-goutte-driver (v1.1.0 => v1.2.1): Downloading (100%)         
  - Updating sebastian/version (1.0.5 => 1.0.6): Downloading (100%)         
  - Updating sebastian/global-state (1.0.0 => 1.1.1): Downloading (100%)         
  - Updating sebastian/recursion-context (1.0.0 => 1.0.5): Downloading (100%)         
  - Updating sebastian/exporter (1.2.0 => 1.2.2): Downloading (100%)         
  - Updating sebastian/environment (1.2.2 => 1.3.8): Downloading (100%)         
  - Updating sebastian/diff (1.3.0 => 1.4.3): Downloading (100%)         
  - Updating sebastian/comparator (1.1.1 => 1.2.4): Downloading (100%)         
  - Updating phpunit/php-text-template (1.2.0 => 1.2.1): Downloading (100%)         
  - Updating doctrine/instantiator (1.0.4 => 1.1.0): Downloading (100%)         
  - Updating phpunit/phpunit-mock-objects (2.3.1 => 2.3.8): Downloading (100%)         
  - Updating phpunit/php-timer (1.0.5 => 1.0.9): Downloading (100%)         
  - Updating phpunit/php-file-iterator (1.4.0 => 1.4.5): Downloading (100%)         
  - Updating phpunit/php-token-stream (1.4.1 => 1.4.12): Downloading (100%)         
  - Updating phpunit/php-code-coverage (2.0.16 => 2.2.4): Downloading (100%)         
  - Updating phpdocumentor/reflection-docblock (2.0.4 => 4.2.0): Downloading (100%)         
  - Updating phpspec/prophecy (v1.4.1 => 1.7.3): Downloading (100%)         
  - Updating phpunit/phpunit (dev-master 13a957b => 4.8.36):  Checking out 46023de9a9
  - Updating fzaninotto/faker (v1.4.0 => v1.7.1): Downloading (100%)         
  - Updating sizuhiko/fabricate (2.0.0 => 2.0.3): Downloading (100%)         
  - Updating sizuhiko/cake_fabricate dev-master (1b445ef => ed1f8d4):  Checking out ed1f8d4751
symfony/var-dumper suggests installing ext-symfony_debug ()
Writing lock file
Generating autoload files
```

いろいろ新しくなりました。
あとは元の手順を進め（PHPのバージョンが変わった部分を適時置き換え）、緊張の瞬間です。

# Behatのテストを実行する

```
vagrant@cakebox:~/Apps/blog-tutorial.app$ vendor/bin/behat
Feature:
  In order to tell the masses what's on my mind
  As a user
  I want to read articles on the site

  Background:                # features/articles.feature:7
    Given there is a post:   # ArticlesContext::thereIsAPost()
      | Title              | Body                          |
      | The title          | This is the post body.        |
      | A title once again | And the post body follows.    |
      | Title strikes back | This is really exciting! Not. |
    And there is a user:     # UsersContext::thereIsAUser()
      | Username | Password | FirstName | LastName |
      | alice    | ecila    | Alice     | Smith    |
      | bob      | obo      | Bob       | Johnson  |
    And there is a category: # CategoriesContext::thereIsACategory()
      | Name      |
      | Events    |
      | Computers |
      | Foods     |

  Scenario: Show articles                 # features/articles.feature:23
    When I am on "TopPage"                # WebContext::visit()
    Then I should see "The title"         # WebContext::assertPageContainsText()
    And I should see "A title once again" # WebContext::assertPageContainsText()
    And I should see "Title strikes back" # WebContext::assertPageContainsText()

  Scenario: Show the article                       # features/articles.feature:29
    Given I am on "TopPage"                        # WebContext::visit()
    When I follow "A title once again"             # WebContext::clickLink()
    Then I should see "And the post body follows." # WebContext::assertPageContainsText()

  Scenario: Add new article                         # features/articles.feature:34
    Given I am on "TopPage"                         # WebContext::visit()
    And I follow "Add"                              # WebContext::clickLink()
    And I login "bob" "obo"                         # UsersContext::iLogin()
    When I post article form :                      # ArticlesContext::iPostArticleForm()
      | Label      | Value                 |
      | Categories | Events                |
      | Title      | Today is Party        |
      | Body       | From 19:30 with Alice |
    And I should see "Your article has been saved." # WebContext::assertPageContainsText()
    And I should see "Today is party"               # WebContext::assertPageContainsText()

  Scenario: Remove article                     # features/articles.feature:46
    Given I am on "TopPage"                    # WebContext::visit()
    When I delete article "Title strikes back" # ArticlesContext::iDeleteArticle()
    Then I should not see "Title strikes back" # WebContext::assertPageNotContainsText()

4 scenarios (4 passed)
28 steps (28 passed)
0m12.62s (32.10Mb)
```

おー、全部成功しています。

# 何が言いたかったのか

さて、皆さんは現場で `composer update` なんて実行しますか？
まぁ普通はしませんよね。
私は今回やりました。もちろんサンプルアプリだし、仕事のアプリじゃないから、というのも言えるでしょう。
とはいえ、サンプルアプリでも動作確認をしないといけませんし、うまく動かなかったら...
このサンプルアプリには単体テストはありませんが、E2Eテストは存在していました（まぁE2Eのチュートリアルなので当然ですが...）。
全部アップデートしても、エンドツーエンドでちゃんと動くことがコマンド１つで確認できます。

単体テストももちろん大事ですが、E2Eテストがあるときの安心感は今回改めて感じました。

# さいごに

あいかわらず単体テスト（PHPSpec）に関しては未着手なのですが、いろいろ考えてPHPUnitベースでBDDスタイルに記述できる [Codeception/Specify](https://github.com/Codeception/Specify) を使うと良いのかなーと思います。これだとPHPUnitのコードと共存できるし、少しずつ書き換えたりできるな、と。

[CakePHP3のBDDチュートリアル](https://github.com/sizuhiko/cakephp3-bdd-example)は、最新版で試せるように変更は反映されています。

明日は @ryoichi-u さんの「Phinxの話か、Formヘルパーのエスケープ周りか」が予定されています。楽しみですね。