この記事は、今年イチ!お勧めしたいテクニック by ゆめみ feat.やめ太郎 Advent Calendar 2019 21日目の記事です。
昨日は、「業務で使うツール(iTerm2,SequelPro,Chrome)をShellScriptでハイパーテクニックする」でした。
シェルスクリプトすごい。まさに効率化
是非ご一読ください。
はじめに
この記事では、CircleCi でテストコード(PHPUnit)を実行する方法を紹介します。
なお、記事の内容やスクリーンショットは、 2019年12月時点のものとなります。
ソースは以下
CircleCi とは
継続的インテグレーション/デリバリー を提供する SaaS です。
Git ホスティングサービスへの push
イベントやスケジューリングした時間に、任意の処理を実行できます。
主に xUnit の実行、ソースコードのビルド、ビルド成果物のデプロイを自動化する目的で利用されます。
処理の内容は YAML
に記述しなければならないため、初学者の方々は二の足を踏みがち(私の観測範囲では)ですが、単純にテストを実行するだけならそれほど難しくありません。
費用面も、フリープランを使えば、並行ビルドはできないですが、毎週2,500クレジットが提供されるため、1週間あたり250分間の実行までなら無料で利用できます。 詳しくはこちらを参照
参考程度ですが、私が関わっていたプロジェクトで、データベースが絡むテストを100件程度実行すると、大体1~2分ほどで全て完了します。
多めに見積もり、ビルドに毎回3分かかるとして、1週間に約83回実行できる計算になります。
趣味や勉強程度なら、複数プロジェクトで使うにしても、十分フリープランで賄えそうです。
ちなみにクレジットカードは登録しなくても利用できます。
前提
- Laravel 実行環境が整っている
- MySQL or Maria DB が使える環境が整っている
- GitHub のアカウントを持っている or 取得できる
- テストコードを書きたい or 書いている
- public リポジトリでの解説
- private でも実行可能ですが、 user key などの設定が必要
- 今回は解説しません。詳しくはドキュメントを参照
環境
- PHP 7.3.12
- Laravel 6.5.0
- PHPUnit 8.4.3
- Maria DB 10.4.11
下準備
今回は Laravel を使います。
ちなみに私は Docker Compose で実行環境を作りました。
まず新規プロジェクトを作成します。
$ composer create-project laravel/laravel="6.*" laravel-circleci-demo
Composer で必要なパッケージをインストールします。
$ composer install
テストコードを書く
なにはともあれ、テストコードを書かないことには始まりません。
なんでもいいのですが、せっかくなので Laravel のユーザー登録機能に対してテストを書いてみたいと思います。
テスト用データベースの接続情報を設定する
テスト用のデータベースを用意してください。
今回は Maria DB を使用しますが、 MySQL はもちろん、 PostgreSQL 等でもほぼ同一の手順で設定できると思います。
まず、テスト用の .env
ファイルを作成します。
$ cp .env.example .env.testing
もちろん GUI でコピーしても構いません。
.env.testing
を作成したら、APP_KEY
を生成します。
$ php artisan key:generate --env=testing
APP_KEY
を生成したら、.env.testing
を以下のように変更します。
設定値はご自身のDB接続情報に合わせて変更してください。
APP_ENV=testing
DB_CONNECTION=mysql
# 自身の環境に合わせた IP アドレスやコンテナ名を指定
DB_HOST=laravel-mariadb
DB_PORT=3306
DB_DATABASE=app-test
DB_USERNAME=root
DB_PASSWORD=root
テストコード作成
以下を実行し、テストクラスを作成します。
$ php artisan make:test UserRegisterTest
ついでに Laravel が自動で作成するテストは不要なので消しちゃいましょう。
$ rm ./tests/Feature/ExampleTest.php
$ rm ./tests/Unit/ExampleTest.php
# Unit ディレクトリが無いと CI 実行時エラーになる
$ touch ./tests/Unit/.gitkeep
作成したテストクラスに RefreshDatabase
トレイトを追加します。
※ RefreshDatase
を使用すると、各テスト前後にマイグレーションとロールバックを実行してくれます。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserRegisterTest extends TestCase
{
// 追加
use RefreshDatabase;
......
}
PHPUnit を実行します。
$ vendor/bin/phpunit --testdox
PHPUnit 8.4.3 by Sebastian Bergmann and contributors.
User Register (Tests\Feature\UserRegister)
✔ Example
Time: 1.14 seconds, Memory: 18.00 MB
OK (1 test, 1 assertion)
テストが成功(上記のような出力がされている)していれば問題なく設定できているはずです。
ユーザー登録機能をテストする
Laravel にデフォルトで搭載されているユーザー登録機能をルーティングに追加します。
Route::post('/register', 'Auth\RegisterController@register')->name('register');
$ php artisan route:list | grep register
| | POST | register | register | App\Http\Controllers\Auth\RegisterController@register | web,guest |
/register
にフォーム情報を POST し、その情報でユーザー登録できているかを確認するテストを作成します。
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserRegisterTest extends TestCase
{
use RefreshDatabase;
/**
* @test
*/
public function ユーザー登録できる()
{
$email = 'email@example.com';
$this->post(route('register'), [
'name' => 'user',
'email' => $email,
'password' => 'password',
'password_confirmation' => 'password'
])
->assertStatus(302);
$this->assertDatabaseHas('users', ['email' => $email]);
}
}
テストを実行して、以下のような出力がされていればOK
$ vendor/bin/phpunit --testdox
PHPUnit 8.4.3 by Sebastian Bergmann and contributors.
User Register (Tests\Feature\UserRegister)
✔ ユーザー登録できる
Time: 2 seconds, Memory: 26.00 MB
OK (1 test, 2 assertions)
このテストコードを CircleCI で実行します。
CircleCI 上で使うデータベース接続情報を設定する
今回は CircleCI が提供する Maria DB の Docker イメージを使うので、その接続情報を config/database.php
に circle_testing
という名前で追加します。
名前は自由に設定できます。
今回はベタ書きしていますが、実際のプロジェクトでは必要に応じて .env.testing
などの設定ファイルに定義して読み込んでください。
- ホスト: 127.0.0.1
- ポート: 3306
- DB名: circle_test
- ユーザー名: root
- パスワード: 無し
'connections' => [
// 末尾に追加
// CircleCI テスト用
'circle_testing' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'database' => 'circle_test',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
],
],
以上の内容を一旦 GitHub に push
してください。
CircleCI で PHPUnit を実行する
アカウントを持っていない場合、 CircleCI のトップページから、アカウントを作成してください。
その際には、 GitHub アカウントを使って作成すると連携が楽です。
以降は GitHub との連携が済んでいる前提で説明していきます。
※ GitHub アカウントで作成していない場合はこちらの連携方法を参照
CircleCI の設定
ダッシュボードから ADD PROJECTS
を選択すると、 GitHub のリポジトリ一覧が表示されます。
上記で作成したリポジトリの Set Up Project
ボタンをクリック
セットアップ画面へ遷移します。
おそらくデフォルトで問題ないかと思いますが
-
Operating System
→Linux
-
Language
→PHP
と設定されていることを確認してください。
次に、ページ中程にある1~5のステップを実行します。
プロジェクトに .circleci/config.yml
を配置する
CircleCI の設定ファイルです。
Laravel のプロジェクトルートに移動し、ファイルを作成します。
$ mkdir .circleci && touch .circleci/config.yml
config.yml
を修正
ベースは STEP2 で示される Sample.yml File
の内容です。
STEP2 にある Copy to Clipboard
を押下し、 config.yml
に貼り付け、 push
して終了、お疲れさまでした。
としたいのですが、このままでは PHPUnit が実行されません。
また、今回のケースでは必要がない処理まで実行されてしまいます。
一旦コピペした内容は削除し、必要な処理を記述していきます。
フィールド毎に簡単に解説していきますが、全ては解説しません。
もっと詳しく知りたい方はドキュメントを参照してください。
「細けえこたあいいんだよ」というかたはこちらをどうぞ。
jobs > build
このフィールド配下に書いた内容が、 GitHub に push
した際に実行されることになります。
docker
Docker イメージを指定します。
今回は PHP 7.3
と Maria DB 10.4
を指定します。
以下を参照し、環境に合ったイメージを使用してください。
https://circleci.com/docs/2.0/circleci-images/
https://hub.docker.com/u/circleci
2021/02/25 追記:
イメージタグの指定が
circleci/php:7.3-node-browsers
から
circleci/php:7.3.0-node-browsers
に変更になっていたようです。
@araiyusuke さん、情報ありがとうございました。
version: 2
jobs:
build:
docker:
- image: circleci/php:7.3.0-node-browsers
- image: circleci/mariadb:10.4
environment
環境変数を設定できます。
config/database.php
に circle_testing
という名前でDB接続情報を設定したので、その値を使います。
environment:
- DB_CONNECTION: circle_testing
DB_CONNECTION
は、 Laravel の config/database.php > default
で読み込む値です。
通常は .env/.env.*
に設定していることが多いと思いますが、 environment
のほうが優先順位は高いので、DB接続はこちらの値が使用されます。
他にも CircleCI 用に設定しなければいけない場合は、続けて - ENV_NAME: value
のように記述してください。
working_directory
以降の作業を行うディレクトリを作成、移動します。
1プロジェクトだけで利用するのなら設定しなくても問題なさそうですが、大抵複数のプロジェクトで利用すると思うので、プロジェクト毎に実行ディレクトリを作成したほうが無難です。
working_directory: ~/ci-demo
steps
ここからプロジェクト実行環境の作成処理と PHPUnit 実行処理を設定していきます。
とはいえ、とても単純です。
checkout
リポジトリのデータを取得します。
おそらく git clone or git pull
が実行されている。
steps:
- checkout
run (Update apt-get)
Linux OSのパッケージリストを更新します。
今回の場合は実行しなくても問題ありませんが、一応書きました。
画像の操作などが必要な場合は、このあとに apt-get install -y libpng-dev
など、インストール処理を記述するのがベターでしょうか。
- run:
name: Update apt-get
command: sudo apt-get update
run (Docker php extensions install)
イメージに同梱されている PHP 拡張モジュールをインストールします。
- run:
name: Docker php extensions install
command: sudo docker-php-ext-install pdo_mysql
restore_cache
ここでは、「任意のプレフィックス + composer.json
の SHA256 ハッシュ値」をキーにして検索し、見つかったキャッシュをリストアしています。
キャッシュの内容は、後述する save_cache
を御覧ください。
また、 keys
とあることから、複数指定が可能です。
まず v1-dependencies-{{ checksum "composer.json" }}
を探して、なければその次に設定されているキーを探す......
というような動きになります。
ひとまず今回はあまり深く考えず、「はいはいキャッシュキャッシュ」くらいの認識で次へ進んでください。
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
run (Install PHP libraries)
composer
でライブラリをインストールします。
-n
は対話型のメッセージ省略、 --prefer-dist
で圧縮ファイルをダウンロードするようにします。
- run:
name: Install PHP libraries
command: composer install -n --prefer-dist
save_cache
キャッシュを作成します。
paths
にはキャッシュ対象のパスを指定します。
複数指定可能です。
今回は、 composer install
でライブラリがインストールされる vendor
ディレクトリを指定、 key
には restore_cache
で指定したキーと同じ値を設定します。
※ というよりも、ここで設定したキーを restore_cache
で指定するというほうが正しい。
一度キャッシュしてしまえば、以降は composer.json
に変更がない限り、該当する vendor
ディレクトリの内容をリストアできるようになります。
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "composer.json" }}
run (Run PHPUnit)
PHPUnit の実行処理を書きます。
とは言ってもたったこれだけ。
- run:
name: Run PHPUnit
command: vendor/bin/phpunit
補足
キャッシュは複数設定可能です。
composer install
と npm install
の実行結果をキャッシュしたい場合は以下のように設定してください。
# composer
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
# name, command は付けなくても実行できる
- run: composer install -n --prefer-dist
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "composer.json" }}
# npm
- restore_cache:
keys:
- v1-node-{{ checksum "package.json" }}
- v1-node-
- run: npm install
- save_cache:
paths:
- ./node_modules
key: v1-node-{{ checksum "package.json" }}
.circleci/config.yml の全体
面倒ならこれをコピペでOK
たった30行程度です。
すごい
簡単
version: 2
jobs:
build:
docker:
- image: circleci/php:7.3.0-node-browsers
- image: circleci/mariadb:10.4
environment:
- DB_CONNECTION: circle_testing
working_directory: ~/ci-demo
steps:
- checkout
- run:
name: Update apt-get
command: sudo apt-get update
- run:
name: Docker php extensions install
command: sudo docker-php-ext-install pdo_mysql
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
- v1-dependencies-
- run:
name: Install PHP libraries
command: composer install -n --prefer-dist
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "composer.json" }}
- run:
name: Run PHPUnit
command: vendor/bin/phpunit
GitHub に push
.circleci/config.yml
を commit & push
します。
CI を実行
セットアップ画面の STEP5 にある Start building
をクリック
ビルド画面に遷移し、しばらくすると蒼き清浄なる画面が表示されるはずです。
おめでとう! CircleCI 利用実績を解除しました🎉
「CircleCI はつよい人が使ってるイメージがあってなんか難しそう」 という人を結構見るんですが、ひとつひとつ分割して、小さい範囲で見れば、そこまで難しいことはあまりないです(あまりないです)。
また、一度動かした、使ってみた、という経験があれば、全く使ったことがなかったあの日あの時より、圧倒的に次のステップに進みやすくなっているはずです。
是非これを機に CircleCI を使ってみてください。
おまけ
内容としては以上なんですが、せっかくなので CircleCI の小ネタ(?)を2つ紹介します。
GitHub Pull request でテストが失敗したら merge
できないようにする
ブランチ作成
GitHub でブランチを作成してください。
ローカルで作っても構いません。
作成したブランチをチェックアウトします。
$ git fetch
$ git switch branch-name
# or
# git checkout branch-name
失敗するテストを書く
PHPUnit を失敗させたいので、わざと失敗するテストを書いて push
します。
あくまでデモ用です。
$ php artisan make:test FailingTest
namespace Tests\Feature;
use Tests\TestCase;
class FailingTest extends TestCase
{
/**
* @test
*/
public function failing()
{
// 期待値 true なのに実際の値は false なので失敗する
$this->assertTrue(false);
}
}
実行して、失敗することを確認します。
$ vendor/bin/phpunit tests/Feature/FailingTest.php --testdox
PHPUnit 8.4.3 by Sebastian Bergmann and contributors.
Failing (Tests\Feature\Failing)
✘ Failing
┐
├ Failed asserting that false is true.
│
╵ /var/www/html/tests/Feature/FailingTest.php:15
┴
Time: 1.05 seconds, Memory: 18.00 MB
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
失敗することを確認したら、 GitHub に push
CircleCI のジョブページをみて、テストが失敗していることを確認します。
ちゃんと失敗していますね。
Pull request を作成する
GitHub リポジトリページのヘッダーに表示されるブランチ名横のアイコンをクリックして pull request
を作成します。
タイトルやコメントは適当でいいです。
ブランチを保護する
pull request
の画面を見てみると、テストは失敗のステータスになっているにも関わらず、 merge
できてしまいます。
これでは CircleCI でテストを実行した意味が無い......
そこでリポジトリの設定を行います。
Settings > Branches > Branch protection rules
の Add rule
をクリックして、以下のように設定してください。
保護するブランチを設定する
Branch name pattern
に master
と入力する。
ステータスチェックをパスした場合のみ merge
を許可するよう設定する
以下の項目全てにチェックをつける。
Require status checks to pass before merging
Require branches to be up to date before merging
ci/circleci
管理者にもルールを適用する merge
させない場合はチェック
以下にチェックすることで、テストが失敗した場合、管理者だとしても merge
できなくなります。
Include administrators
master
に直接 push
できなくなるので、管理者も含めて pull request
からの merge
のみ許容する、という場合以外はチェックを付けないほう無難。
Create
ボタン押下で設定を保存したら、 pull request
に戻って確認してみましょう。
merge
ボタンがグレーアウトしているはずです。
Include administrators
にチェックを入れなかった場合
バッジを設定する
GitHub でこんな感じのアイコンを見たことはありませんか?
こういうのが README
にあると、なんかすごくそれっぽい雰囲気が出てきますよね。
知らない方でもなんとなくわかると思いますが、これは CircleCI のステータスを表しているものです。
エラーはこんな感じ
バッジの画像 URL を取得する
CircleCI の対象プロジェクト設定ページを開きます。
INSIGHTS
のリポジトリパネル右上の歯車アイコンから遷移できます。
NOTIFICATIONS > Status Badges
の Embed Code
にバッジ表示用の Markdown が表示されます。
これはブランチ単位で取得できます。
その値をコピーして README
に貼り付ければそれっぽい README
が完成です!
以上です。
それでは皆さん、 Merry npm xmas
やで