Laravel5.7: TravisCIでテストしてCodecovでカバレッジを取得する

LaravelアプリをGitHubへプッシュしたら、同時にテストとカバレッジ取得を行うようにします。
そして、下記のようなバッジを`README.md`で表示させます。

![001.png](https://qiita-image-store.s3.amazonaws.com/0/27816/bf34939e-3b4f-ebae-426f-e76cb698c66c.png)


# 親記事
[Laravel 5.7で基本的なCRUDを作る - Qiita](https://qiita.com/sutara79/items/ef30fcdfb7afcb2188ea)


# 準備
プッシュする前に、TravisCIとCodecovにGitHubアカウントでログインして、リポジトリを有効にしておいてください。
いっつも忘れちゃうんですよね…。

- [Travis CI](https://travis-ci.org/)  
  ![003.png](https://qiita-image-store.s3.amazonaws.com/0/27816/120f9aa5-c6c7-8576-cc03-e2f5b389e42f.png)

- [Codecov](https://codecov.io/)  
  ![002.png](https://qiita-image-store.s3.amazonaws.com/0/27816/05b36f8f-981b-d484-95da-ab740c4c6310.png)

# テスト用の環境設定
:link: Qiita: [TravisClでLaravel5.2のテストを自動化してみる](https://qiita.com/ProjectEuropa/items/f4983b14db8d4cd3727f)

下記の内容の`.env.travis`を作成します。
:page_facing_up: [.env.travis](https://github.com/sutara79/demo-laravel-crud/blob/5.7/.env.travis)

ファイル名を`.env.testing`にしてしまうと、ローカルの`testing`環境でも適用されてしまいます。
なので、`.env.travis`としたほうがいいです。

DBはローカルではMySQLを、本番のHerokuではPostgreSQLを使っています。
TravisCIではPostgreSQLでDBを作ってテストすることにします。

`APP_URL`の`http://localhost:8000`とは、`php artisan serve`で起動するPHPの組込み開発サーバへのURLです。
CIでのブラウザテストではこれが必要です。
:link: readouble.com: [ローカル開発サーバ](https://readouble.com/laravel/5.7/ja/installation.html#installing-laravel)
:link: PHP公式ドキュメント: [ビルトインウェブサーバー](https://secure.php.net/manual/ja/features.commandline.webserver.php)

ちなみに、ホスト名とポート番号の初期値は下記の箇所で設定されています。

:page_facing_up: [src/Illuminate/Foundation/Console/ServeCommand.php](https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Console/ServeCommand.php#L49)

```php:
    protected function serverCommand()
    {
        // PHPのビルトインウェブサーバーをそのまま利用するコマンドを生成している。
        // 「php -S localhost:8000」のような形式。
        return sprintf('%s -S %s:%s %s',
            ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)),
            $this->host(),
            $this->port(),
            ProcessUtils::escapeArgument(base_path('server.php'))
        );
    }

// (中略)

    protected function getOptions()
    {
        // ホストは「127.0.0.1」、ポートは「8000」が初期値。
        return [
            ['host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on', '127.0.0.1'],
            ['port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on', 8000],
        ];
    }
```

なお、PHPのビルトインウェブサーバーはSSLには対応していないようです。
:link: Stack Overflow: [openSSL not working with PHP built-in webserver](https://stackoverflow.com/questions/12946486/openssl-not-working-with-php-built-in-webserver)


# TravisCIの設定
:link: Travis CIの公式ドキュメント: [PostgreSQL](https://docs.travis-ci.com/user/database-setup/#postgresql)
:link: readouble.com: [Travis CI](https://readouble.com/laravel/5.7/ja/dusk.html#running-tests-on-travis-ci)

下記の内容の`.travis.yml`を作成します。
:page_facing_up: [.travis.yml](https://github.com/sutara79/demo-laravel-crud/blob/5.7/.travis.yml)

### `travis_retry`の意味
タイムアウトが原因で依存パッケージのインストールに失敗しても、3回までリトライしてくれます。
:link: Travis CIの公式ドキュメント: [Timeouts installing dependencies](https://docs.travis-ci.com/user/common-build-problems/#timeouts-installing-dependencies)

### コマンドの末尾の`&`の意味
:link: [バックグラウンドでプログラムを実行する（&amp;アンド）](http://kazmax.zpp.jp/linux_beginner/process_background.html)
末尾に`&`をつけると並列で処理します。

### `bash <(curl -s https://codecov.io/bash) -cF php`の意味
Codecovで、PHPとJavaScriptなど複数の言語のカバレッジをひとつにまとめるためのコマンドです。
下記の例をそのまま真似しました。
:page_facing_up: Codecov公式サンプル: [.travis.yml](https://github.com/codecov/example-php/blob/master/.travis.yml)
:link: Codecov公式ドキュメント: [Multiple Languages](https://docs.codecov.io/docs/merging-reports#section-multiple-languages)

https://codecov.io/bash の中身はシェルスクリプトで、それをcURLのサイレントモード(`curl -s`)でダウンロードしています。
:link: [Linuxコマンド集 - 【curl】ファイルのダウンロードやアップロードを行う：ITpro](http://itpro.nikkeibp.co.jp/atcl/column/14/230520/080400003/)

そして、ダウンロードしたスクリプトを入力リダイレクト(`<`)でbashとして実行しています。
:link: [Linuxコマンド集 - 【 < 】 入力のリダイレクト：ITpro](http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230692/)

その際、オプションとして`-cF php`を渡しています。
https://codecov.io/bash を見ると下記のように説明されています。

```
    -c           Move discovered coverage reports to the trash
    -F flag      Flag the upload to group coverage metrics

                 -F unittests        This upload is only unittests
                 -F integration      This upload is only integration tests
                 -F ui,chrome        This upload is Chrome - UI tests
```

要するに、「これから送るカバレッジに"php"のフラグを付けるので、そちらでグループに分けて管理してください」ということのようです。
これで、PHPとJavaScriptなど複数のカバレッジの結果を同時に送ることができ、しかもそれらを総合してひとつのスコアとして表示してくれます。

# Laravel側の設定
Travis CI用に、Laravel側で追加で設定しなければならないことがあります。

## DuskServiceProviderを登録する
```diff:app/Providers/AppServiceProvider.php
+use Laravel\Dusk\DuskServiceProvider;
 
 class AppServiceProvider extends ServiceProvider
 {
     function register()
     {
+        // 本番環境以外で、Duskサービスプロバイダを登録する
+        if ($this->app->environment('local', 'testing')) {
+            $this->app->register(DuskServiceProvider::class);
+        }
     }
```

[5.7の公式ドキュメント](https://readouble.com/laravel/5.7/ja/dusk.html#installation)には、登録は自動で行われると書いてあります。

> Duskをインストールしたら、Laravel\Dusk\DuskServiceProviderサービスプロバイダを登録する必要があります。通常、Laravelの自動サービスプロバイダ登録により、自動的に行われます。

しかし、上の記述なしでTravis CIでテストを実行すると「duskというコマンドは存在しない」と警告されます。
よって、上のように手動による登録が必要です。

```text:TravisCIのログ
$ php artisan dusk
chrome: command not found
                                  
  Command "dusk" is not defined.  
                                  
Laravel development server started: <http://127.0.0.1:8000>
The command "php artisan dusk" exited with 1.
```

ちなみに、上のコードは[5.4の公式ドキュメント](https://readouble.com/laravel/5.4/ja/dusk.html)に記載されていたものです。

## Duskをdiscoverの対象から外す
```diff:composer.json
     "extra": {
         "laravel": {
             "dont-discover": [
+                "laravel/dusk"
             ]
         }
     },
```

上の記述を追加しないと、Travis CIでのテストの準備段階でパッケージをインストールする際にエラーとなってしまいます。

```text:TravisCIのログ
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover
In DuskServiceProvider.php line 43:
                                           
  It is unsafe to run Dusk in production.  
                                           
Script @php artisan package:discover handling the post-autoload-dump event returned with error code 1
The command "composer install --no-interaction --prefer-dist --no-suggest" failed. Retrying, 3 of 3.
```

:link: [Laracastの質問と回答](https://laracasts.com/discuss/channels/forge/it-is-unsafe-to-run-dusk-in-production-error-when-trying-to-deploy)


# `README.md`にバッジを載せる
Travis CIでテストする前に、あらかじめバッジを設置しておきます。
すでに存在する`readme.me`(小文字)を削除して、このプロジェクト用の`README.me`を下記の内容で作ります。
:page_facing_up: [.env.travis](https://github.com/sutara79/demo-laravel-crud/blob/5.7/README.md)

大事なのは、冒頭でバッジを表示する箇所です。
```markdown
[![Build Status](https://travis-ci.org/sutara79/demo-laravel-crud.svg?branch=5.7)](https://travis-ci.org/sutara79/demo-laravel-crud)
[![codecov](https://codecov.io/gh/sutara79/demo-laravel-crud/branch/5.7/graph/badge.svg)](https://codecov.io/gh/sutara79/demo-laravel-crud)
```

ユーザー名、リポジトリ名、ブランチ名を皆さんの環境に合わせて修正してください。

:link: [Travis CIのバッジの取得方法](https://docs.travis-ci.com/user/status-images/)

Codecovの場合
![001.png](https://qiita-image-store.s3.amazonaws.com/0/27816/590f14b6-bca7-2b8f-4903-c7aba21626ad.png)


# プッシュする
以上の準備を終えたら、GitHubへプッシュします。
自動でTravis CIでテストが始まり、それが成功したらCodecovでカバレッジを取得します。
エラーがあった場合などにはメールで通知が来ます。