LoginSignup
64
55

More than 3 years have passed since last update.

誰でも簡単! CircleCI で PHPUnit を実行してみよう!!

Last updated at Posted at 2019-12-20

この記事は、今年イチ!お勧めしたいテクニック 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接続情報に合わせて変更してください。

.env.testing
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 を使用すると、各テスト前後にマイグレーションとロールバックを実行してくれます

tests/Feature/UserRegisterTest.php
<?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 にデフォルトで搭載されているユーザー登録機能をルーティングに追加します。

routes/web.php
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 し、その情報でユーザー登録できているかを確認するテストを作成します。

tests/Feature/UserRegisterTest.php
<?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.phpcircle_testing という名前で追加します。
名前は自由に設定できます。
今回はベタ書きしていますが、実際のプロジェクトでは必要に応じて .env.testing などの設定ファイルに定義して読み込んでください。

  • ホスト: 127.0.0.1
  • ポート: 3306
  • DB名: circle_test
  • ユーザー名: root
  • パスワード: 無し

参考: Maria DB Dockerfile

config/database.php
'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 ボタンをクリック

add-project.png

セットアップ画面へ遷移します。
おそらくデフォルトで問題ないかと思いますが

  • Operating SystemLinux
  • LanguagePHP

と設定されていることを確認してください。

次に、ページ中程にある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 して終了、お疲れさまでした。
circleci.com_add-projects_gh_KeisukeKudo (1).png
としたいのですが、このままでは PHPUnit が実行されません。
また、今回のケースでは必要がない処理まで実行されてしまいます。
一旦コピペした内容は削除し、必要な処理を記述していきます。
フィールド毎に簡単に解説していきますが、全ては解説しません。
もっと詳しく知りたい方はドキュメントを参照してください。
「細けえこたあいいんだよ」というかたはこちらをどうぞ。

jobs > build

このフィールド配下に書いた内容が、 GitHub に push した際に実行されることになります。

docker

Docker イメージを指定します。
今回は PHP 7.3Maria 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 さん、情報ありがとうございました。

circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: circleci/php:7.3.0-node-browsers
      - image: circleci/mariadb:10.4
environment

環境変数を設定できます。
config/database.phpcircle_testing という名前でDB接続情報を設定したので、その値を使います。

circleci/config.yml
environment:
  - DB_CONNECTION: circle_testing

DB_CONNECTION は、 Laravel の config/database.php > default で読み込む値です。
通常は .env/.env.* に設定していることが多いと思いますが、 environment のほうが優先順位は高いので、DB接続はこちらの値が使用されます。
他にも CircleCI 用に設定しなければいけない場合は、続けて - ENV_NAME: value のように記述してください。

working_directory

以降の作業を行うディレクトリを作成、移動します。
1プロジェクトだけで利用するのなら設定しなくても問題なさそうですが、大抵複数のプロジェクトで利用すると思うので、プロジェクト毎に実行ディレクトリを作成したほうが無難です。

circleci/config.yml
working_directory: ~/ci-demo
steps

ここからプロジェクト実行環境の作成処理と PHPUnit 実行処理を設定していきます。
とはいえ、とても単純です。

checkout

リポジトリのデータを取得します。
おそらく git clone or git pull が実行されている。

circleci/config.yml
steps:
  - checkout
run (Update apt-get)

Linux OSのパッケージリストを更新します。
今回の場合は実行しなくても問題ありませんが、一応書きました。
画像の操作などが必要な場合は、このあとに apt-get install -y libpng-dev など、インストール処理を記述するのがベターでしょうか。

circleci/config.yml
- run:
    name: Update apt-get
    command: sudo apt-get update
run (Docker php extensions install)

イメージに同梱されている PHP 拡張モジュールをインストールします。

circleci/config.yml
- 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" }} を探して、なければその次に設定されているキーを探す......
というような動きになります。
ひとまず今回はあまり深く考えず、「はいはいキャッシュキャッシュ」くらいの認識で次へ進んでください。

circleci/config.yml
- restore_cache:
    keys:
      - v1-dependencies-{{ checksum "composer.json" }}
      - v1-dependencies-
run (Install PHP libraries)

composer でライブラリをインストールします。
-n は対話型のメッセージ省略、 --prefer-dist で圧縮ファイルをダウンロードするようにします。

circleci/config.yml
- 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 ディレクトリの内容をリストアできるようになります。

circleci/config.yml
- save_cache:
    paths:
      - ./vendor
    key: v1-dependencies-{{ checksum "composer.json" }}
run (Run PHPUnit)

PHPUnit の実行処理を書きます。
とは言ってもたったこれだけ。

circleci/config.yml
- run:
    name: Run PHPUnit
    command: vendor/bin/phpunit
補足

キャッシュは複数設定可能です。
composer installnpm 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行程度です。
すごい
簡単

circleci/config.yml
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.ymlcommit & push します。

CI を実行

セットアップ画面の STEP5 にある Start building をクリック
ビルド画面に遷移し、しばらくすると蒼き清浄なる画面が表示されるはずです。

circleci.com_add-projects_gh_KeisukeKudo (3).png

おめでとう! CircleCI 利用実績を解除しました🎉

「CircleCI はつよい人が使ってるイメージがあってなんか難しそう」 という人を結構見るんですが、ひとつひとつ分割して、小さい範囲で見れば、そこまで難しいことはあまりないです(あまりないです)。
また、一度動かした、使ってみた、という経験があれば、全く使ったことがなかったあの日あの時より、圧倒的に次のステップに進みやすくなっているはずです。
是非これを機に CircleCI を使ってみてください。

おまけ

内容としては以上なんですが、せっかくなので CircleCI の小ネタ(?)を2つ紹介します。

GitHub Pull request でテストが失敗したら merge できないようにする

ブランチ作成

GitHub でブランチを作成してください。
ローカルで作っても構いません。

github.com_KeisukeKudo_laravel-circleci-demo_tree_fails-demo (1).png

作成したブランチをチェックアウトします。

$ git fetch
$ git switch branch-name
# or
# git checkout branch-name

失敗するテストを書く

PHPUnit を失敗させたいので、わざと失敗するテストを書いて push します。
あくまでデモ用です。

$ php artisan make:test FailingTest
tests/Feature/FailingTest.php
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 のジョブページをみて、テストが失敗していることを確認します。
circleci.com_build-insights_gh_KeisukeKudo.png

ちゃんと失敗していますね。

Pull request を作成する

GitHub リポジトリページのヘッダーに表示されるブランチ名横のアイコンをクリックして pull request を作成します。
タイトルやコメントは適当でいいです。
github.com_KeisukeKudo_laravel-circleci-demo_tree_master (2).png

ブランチを保護する

pull request の画面を見てみると、テストは失敗のステータスになっているにも関わらず、 merge できてしまいます。
これでは CircleCI でテストを実行した意味が無い......
github.com_KeisukeKudo_laravel-circleci-demo_pull_3.png

そこでリポジトリの設定を行います。
Settings > Branches > Branch protection rulesAdd rule をクリックして、以下のように設定してください。

保護するブランチを設定する

Branch name patternmaster と入力する。

ステータスチェックをパスした場合のみ 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 のみ許容する、という場合以外はチェックを付けないほう無難。
github.com_KeisukeKudo_laravel-circleci-demo_settings_branch_protection_rules_new.png

Create ボタン押下で設定を保存したら、 pull request に戻って確認してみましょう。
merge ボタンがグレーアウトしているはずです。

github.com_KeisukeKudo_laravel-circleci-demo_pull_3 (1).png

Include administrators にチェックを入れなかった場合

github.com_KeisukeKudo_laravel-circleci-demo_pull_3 (2).png

バッジを設定する

GitHub でこんな感じのアイコンを見たことはありませんか?
CircleCI
こういうのが README にあると、なんかすごくそれっぽい雰囲気が出てきますよね。
知らない方でもなんとなくわかると思いますが、これは CircleCI のステータスを表しているものです。
エラーはこんな感じ
CircleCI

バッジの画像 URL を取得する

CircleCI の対象プロジェクト設定ページを開きます。
INSIGHTS のリポジトリパネル右上の歯車アイコンから遷移できます。
circleci.com_build-insights_gh_KeisukeKudo (1).png

NOTIFICATIONS > Status BadgesEmbed Code にバッジ表示用の Markdown が表示されます。
これはブランチ単位で取得できます。
その値をコピーして README に貼り付ければそれっぽい README が完成です!
2019-12-17_14h32_07.png
github.com_KeisukeKudo_laravel-circleci-demo (1).png
以上です。
それでは皆さん、 Merry npm xmas やで :christmas_tree::santa:

参考

64
55
13

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
55