RitoLabo

Laravel&CircleCIで継続的インテグレーション。ユニットテスト、静的コード解析、E2Eテストなど。

  • 公開:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,PHPUnit,CircleCI,Github,E2ETesting

2Laravelで構築したwebアプリケーションをGithubにてソース管理を行う事も多いですが、CI(continuous integration=継続的インテグレーション)と呼ばれる、ビルドやテストを継続的に行っていきたいところです。

今回はCircleCIとGithubを連携させてLaravelアプリケーションをビルド&テストするまでを見ていきます。

アジェンダ
  1. 開発環境
  2. GithubとCircleCIの連携
    1. GithubアカウントとCircleCIの連携
    2. 任意のリポジトリのCIを開始する
  3. 設定ファイルの作成
    1. 環境構築の定義
  4. DBコネクションの設定
  5. 動作確認
  6. テストの定義
    1. ユニットテスト
    2. コーディング規約チェック
    3. 静的コード解析
    4. E2Eテスト(Dusk)
  7. 動作確認
  8. バージョンによる設定ファイル記法の違い
    1. ver 2.0
    2. var 2.1

開発環境

今回の開発環境については以下の通りです。

  • PHP 7.3
  • MySQL 8.0
  • Laravel 5.8
  • Circle CI 2.0 - 2.1

laravelアプリケーションのルートディレクトリを laravel/ としています。

GithubとCircleCIの連携

GithubからCircleCIを動作させるには、2つのアカウントを連携させてやる必要があります。

GithubアカウントとCircleCIの連携

CircleCIへアクセスし、Githubアカウントでサインインします。

Github上でCircleCIとの連携承認画面になるので、承認するとアカウントの連携が行われ、CircleCIの管理画面へ遷移します。

任意のリポジトリのCIを開始する

CircleCIの管理画面から「Add Projects」に進むと自身のリポジトリの一覧が表示されるので、そこからCIを回したいリポジトリを選んで(Set Up Project)アクティブにする事で、リモートリポジトリに変更が起こるとCircleCIが動作するようになります。(初めての場合は、サインイン後にリポジトリの一覧が既に表示されたような気もするので、その場合はそこから選択してもOK)

ここまでで、Githubのリモートリポジトリへソースをプッシュした際にCircleCIが動作するようになります。

設定ファイルの作成

CIを回すために、設定ファイルである config.yml を作成します。

今回はアプリケーションソースとCIのファイルを分けて管理するので、以下のようなディレクトリ構成にします。

project_root
├── .circleci
| └── config.yml
|
└── laravel
(laravelのソース群)

環境構築の定義

まずは、Laravelアプリケーションを動作させる為の環境構築部分を定義します。

laravel/.circleci/config.yml
version: 2
jobs:
build:
docker:
- image: circleci/php:7.3-stretch-node-browsers
- image: circleci/mysql:8.0.17
command: mysqld --default-authentication-plugin=mysql_native_password
environment:
- APP_DEBUG: true
- APP_ENV: testing
- APP_KEY: base64:YlIJx6uH3OUb3hxN+PAiJKlC+EGZ2KYi8VHxsfdJpLk=
- DB_CONNECTION: circleci

working_directory: ~/repo

steps:
- checkout

- run:
name: install dockerize
command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
environment:
DOCKERIZE_VERSION: v0.6.1

- run:
name: Install PHP Extensions
command: sudo docker-php-ext-install pdo_mysql

- restore_cache:
keys:
- v1-dependencies-{{ checksum "laravel/composer.json" }}
- v1-dependencies-

- run:
name: composer install
working_directory: laravel
command: composer install -n --prefer-dist

- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "laravel/composer.json" }}

- run:
name: Wait for db
command: dockerize -wait tcp://localhost:3306 -timeout 1m

- run:
name: Migration & Seeding
working_directory: laravel
command: php artisan migrate --seed

以下、詳細です。

docker:
- image: circleci/php:7.3-stretch-node-browsers
- image: circleci/mysql:8.0.17
command: mysqld --default-authentication-plugin=mysql_native_password

PHP7.3とMySQL8.0のイメージを導入しています。 MySQLはver8.0の為、認証方式を変更しています。

environment:
- APP_DEBUG: true
- APP_ENV: testing
- APP_KEY: base64:YlIJx6uH3OUb3hxN+PAiJKlC+EGZ2KYi8VHxsfdJpLk=
- DB_CONNECTION: circleci

環境変数を定義しています。主にテストを走らせる為に必要な設定を行っています。

以下、runコマンドベースです。

install dockerize
MySQLコンテナが立ち上がった事を確認してからマイグレーションやシーディングを実行したいので、そのためのdockerizeのインストールを行っています。(導入は任意)
Install PHP Extensions
PHP拡張をインストールしています。
composer install
Laravelの依存パッケージをインストールしています。
Wait for MySQL
MySQLコンテナが立ち上がるまでここでWaitします。
Migration & Seeding
マイグレーションとシーディングを行っています。

restore_cache/save_cache はジョブを高速化するためのキャッシュ設定です。詳しくは公式ドキュメントを参照してください。

依存関係のキャッシュ
https://circleci.com/docs/ja/2.0/caching/

DBコネクションの設定

Circle CIでDBを使用するので、専用のコネクション設定をLaravel側に定義しておきます。

laravel/config/database.php
'connections' => [
'circleci' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'circle_test',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],

動作確認

ここまでで一旦コミットしてリモートリポジトリにプッシュしてみます。Circle CIが動作するので、管理画面「job」から経過や結果を確認する事ができます。

ステータスが「SUCCESS」になったら成功です。ここまでで、環境の構築が完了しました。

テストの定義

環境構築が出来たので、テストを動作させるように定義していきます。一通りの設定を列挙していますが、それぞれ導入済みの前提で、採用は任意でどうぞ。

ユニットテスト

ユニットテストを走らせる為に、PHPUnitが動作するように定義します。

laravel/.circleci/config.yml
- run:
name: Unittest
working_directory: laravel
command: ./vendor/bin/phpunit

これ以降の記述もそうですが、今回はアプリケーションソースとcircleciの設定ファイルを分けているので、プロジェクトルートから見たアプリケーションルートを working_directory で指定しています。

コーディング規約チェック

PHP_snifferを用いたコーディング規約チェックを走らせます。

laravel/.circleci/config.yml
- run:
name: Coding rules check
working_directory: laravel
command: ./vendor/bin/phpcs --standard=phpcs.xml ./

静的コード解析

Larastanを用いた静的コード解析を走らせます。

laravel/.circleci/config.yml
- run:
name: test - Static code analysis
working_directory: laravel
command: php artisan code:analyse --paths='app,tests' --level=max

E2Eテスト(Dusk)

Laravel Duskでブラウザテストを走らせます。

laravel/.circleci/config.yml
- run:
name: Install Chrome Driver
working_directory: laravel
command: php artisan dusk:chrome-driver 76

- run:
name: Start Chrome Driver
working_directory: laravel
command: ./vendor/laravel/dusk/bin/chromedriver-linux
background: true

- run:
name: Start Laravel Server
working_directory: laravel
command: php artisan serve
background: true

- run:
name: Run Dusk
working_directory: laravel
command: php artisan dusk
environment:
APP_URL: http://localhost:8000
Install Chrome Driver
任意のバージョンのChromeDriverをインストールしています。ここではver.76をしていしていますが、環境に応じて適切なバージョンを指定してください。
Start Chrome Driver
ChromeDriverを起動しています。
Start Laravel Server
PHP組み込みWebサーバを起動しています。
test - Dusk
Duskでブラウザテストを実行しています。

動作確認

テストの定義を行ったら再度コミットしてリモートリポジトリへプッシュします。CircleCIが動作しテストが行われている事が確認できると思います。

こちらも、ステータスが「SUCCESS」になったら成功です。これで、CircleCIを連携させた自動テストの実行環境が構築できました。

バージョンによる設定ファイル記法の違い

ここまででconfig.ymlの記述については断片的に表示していましたが、バージョンによって若干の違いがある(バージョンアップによる新機能を使わなければ同じ記法では書けます)ので、ここでバージョン別のconfig.ymlを記します。

ver 2.0

これまではこのバージョンで紹介してきました。全体像としては以下になります。

laravel/.circleci/config.yml
version: 2.0
jobs:
build:
docker:
- image: circleci/php:7.3-stretch-node-browsers
- image: circleci/mysql:8.0.17
command: mysqld --default-authentication-plugin=mysql_native_password
environment:
- APP_DEBUG: true
- APP_ENV: testing
- APP_KEY: base64:YlIJx6uH3OUb3hxN+PAiJKlC+EGZ2KYi8VHxsfdJpLk=
- DB_CONNECTION: circleci

working_directory: ~/repo

steps:
- checkout

- run:
name: install dockerize
command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
environment:
DOCKERIZE_VERSION: v0.6.1

- run:
name: Install PHP Extensions
command: sudo docker-php-ext-install pdo_mysql

- restore_cache:
keys:
- v1-dependencies-{{ checksum "laravel/composer.json" }}
- v1-dependencies-

- run:
name: composer install
working_directory: laravel
command: composer install -n --prefer-dist

- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "laravel/composer.json" }}

- run:
name: Wait for db
command: dockerize -wait tcp://localhost:3306 -timeout 1m

- run:
name: Migration & Seeding
working_directory: laravel
command: php artisan migrate --seed

- run:
name: Coding rules check
working_directory: laravel
command: ./vendor/bin/phpcs --standard=phpcs.xml ./

- run:
name: Static code analysis
working_directory: laravel
command: php artisan code:analyse --paths='app,tests' --level=max

- run:
name: Unittest
working_directory: laravel
command: ./vendor/bin/phpunit

- run:
name: Install Chrome Driver
working_directory: laravel
command: php artisan dusk:chrome-driver 76

- run:
name: Start Chrome Driver
working_directory: laravel
command: ./vendor/laravel/dusk/bin/chromedriver-linux
background: true

- run:
name: Start Laravel Server
working_directory: laravel
command: php artisan serve
background: true

- run:
name: Run Dusk
working_directory: laravel
command: php artisan dusk
environment:
APP_URL: http://localhost:8000

var 2.1

本記事執筆時点での最新バージョンです。

laravel/.circleci/config.yml
version: 2.1
executors:
default:
working_directory: ~/repo
docker:
- image: circleci/php:7.3-stretch-node-browsers
- image: circleci/mysql:8.0.17
command: mysqld --default-authentication-plugin=mysql_native_password
environment:
- APP_DEBUG: true
- APP_ENV: testing
- APP_KEY: base64:YlIJx6uH3OUb3hxN+PAiJKlC+EGZ2KYi8VHxsfdJpLk=
- DB_CONNECTION: circleci
commands:
install-dockerize:
steps:
- run:
name: Install dockerize
command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
environment:
DOCKERIZE_VERSION: v0.6.1
install-php-extensions:
steps:
- run:
name: Install PHP Extensions
command: sudo docker-php-ext-install pdo_mysql
restore-cache-composer:
steps:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "laravel/composer.json" }}
- v1-dependencies-
composer-install:
steps:
- run:
name: composer install
working_directory: laravel
command: composer install -n --prefer-dist
save-cache-composer:
steps:
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "laravel/composer.json" }}
wait-for-mysql:
steps:
- run:
name: Wait for MySQL
command: dockerize -wait tcp://localhost:3306 -timeout 1m
migration-seeding:
steps:
- run:
name: Migration & Seeding
working_directory: laravel
command: php artisan migrate --seed
test-static-code-analysis:
steps:
- run:
name: Coding rules check
working_directory: laravel
command: ./vendor/bin/phpcs --standard=phpcs.xml ./

- run:
name: Static code analysis
working_directory: laravel
command: php artisan code:analyse --paths='app,tests' --level=max
test-unittest:
steps:
- run:
name: Unittest
working_directory: laravel
command: ./vendor/bin/phpunit
test-e2etest:
steps:
- run:
name: Install Chrome Driver
working_directory: laravel
command: php artisan dusk:chrome-driver 76
- run:
name: Start Chrome Driver
working_directory: laravel
command: ./vendor/laravel/dusk/bin/chromedriver-linux
background: true
- run:
name: Start Laravel Server
working_directory: laravel
command: php artisan serve
background: true
- run:
name: Run Dusk
working_directory: laravel
command: php artisan dusk
environment:
APP_URL: http://localhost:8000
jobs:
build:
executor:
name: default
steps:
- checkout
- install-dockerize
- install-php-extensions
- restore-cache-composer
- composer-install
- save-cache-composer
- wait-for-mysql
- migration-seeding
- test-static-code-analysis
- test-unittest
- test-e2etest
executors
実行環境を定義する事ができ定義を再利用する事が出来ます。
commands
ステップシーケンスを予め定義出来るようになっています。これもexecutors同様、コマンドの再利用が出来て便利です。

見て分かる通り、jobsがとてもシンプルになっています。ワークフローを定義する際にも、2.1ではよりすっきり書くことが出来ます。

laravel/.circleci/config.yml
version: 2.1
executors:
default:
working_directory: ~/repo
docker:
- image: circleci/php:7.3-stretch-node-browsers
environment:
- APP_ENV: testing
testing-image:
working_directory: ~/repo
docker:
- image: circleci/php:7.3-stretch-node-browsers
- image: circleci/mysql:8.0.17
command: mysqld --default-authentication-plugin=mysql_native_password
environment:
- APP_DEBUG: true
- APP_ENV: testing
- APP_KEY: base64:YlIJx6uH3OUb3hxN+PAiJKlC+EGZ2KYi8VHxsfdJpLk=
- DB_CONNECTION: circleci
commands:
install-dockerize:
steps:
- run:
name: Install dockerize
command: wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
environment:
DOCKERIZE_VERSION: v0.6.1
install-php-extensions:
steps:
- run:
name: Install PHP Extensions
command: sudo docker-php-ext-install pdo_mysql
restore-cache-composer:
steps:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "laravel/composer.json" }}
- v1-dependencies-
composer-install:
steps:
- run:
name: composer install
working_directory: laravel
command: composer install -n --prefer-dist
save-cache-composer:
steps:
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "laravel/composer.json" }}
wait-for-mysql:
steps:
- run:
name: Wait for MySQL
command: dockerize -wait tcp://localhost:3306 -timeout 1m
migration-seeding:
steps:
- run:
name: Migration & Seeding
working_directory: laravel
command: php artisan migrate --seed
test-static-code-analysis:
steps:
- run:
name: Coding rules check
working_directory: laravel
command: ./vendor/bin/phpcs --standard=phpcs.xml ./

- run:
name: Static code analysis
working_directory: laravel
command: php artisan code:analyse --paths='app,tests' --level=max
test-unittest:
steps:
- run:
name: Unittest
working_directory: laravel
command: ./vendor/bin/phpunit
test-e2etest:
steps:
- run:
name: Install Chrome Driver
working_directory: laravel
command: php artisan dusk:chrome-driver 76
- run:
name: Start Chrome Driver
working_directory: laravel
command: ./vendor/laravel/dusk/bin/chromedriver-linux
background: true
- run:
name: Start Laravel Server
working_directory: laravel
command: php artisan serve
background: true
- run:
name: Run Dusk
working_directory: laravel
command: php artisan dusk
environment:
APP_URL: http://localhost:8000
jobs:
build:
executor:
name: default
steps:
- checkout
- install-php-extensions
- restore-cache-composer
- composer-install
- save-cache-composer
- run:
name: npm install
working_directory: laravel
command: npm install
test:
executor:
name: testing-image
steps:
- checkout
- install-dockerize
- install-php-extensions
- restore-cache-composer
- composer-install
- save-cache-composer
- wait-for-mysql
- migration-seeding
- test-static-code-analysis
- test-unittest
- test-e2etest
workflows:
build-and-test:
jobs:
- build
- test:
requires:
- build

まとめ

Laravelアプリケーションのテストを中心に見ていきました。 config.ymlはこれ以外にも記法があるので、最適な記法を用いてシンプル状態で管理していきたいですね。

サンプルコード