こんにちは、かつコーチです。
前回まで、FactoryとRefreshDatabaseを使ってテストデータを整える方法を解説してきました。
ローカル環境で php artisan test を実行すれば、テストが通るかどうかは確認できます。
ですが、チーム開発になると「自分の手元では動いたのに、他の人のPRを取り込んだらテストが落ちていた」ということが必ず起こります。
そこで今回は、GitHub Actionsを使って、プッシュやプルリクエストのたびに自動でテストを実行する仕組みを作っていきます。
CIとは?なぜLaravel開発に必要なのか
CI(継続的インテグレーション)の役割
CI(Continuous Integration)とは、コードの変更をリポジトリに取り込むたびに、自動でビルドやテストを実行する仕組みのことです。
人がテストを実行するのを忘れても、CIが必ず実行してくれるので、「テストが落ちたままマージされてしまう」という事故を防げます。
手動テストだけに頼る場合の限界
私がまだCIを導入していなかったチームで開発していたとき、こんなことがありました。
レビュー担当者が「テスト通ってますよね?」と確認し、開発者が「通ってます」と口頭で答えてマージする、という運用をしていたのですが、ある日そのまま本番相当の環境でエラーが出ました。
原因を調べると、開発者が古いブランチのままテストを実行していて、実際にマージされるコードでは別のテストが失敗する状態になっていたのです。
「人間の申告」に依存したチェックは、悪気がなくても抜け漏れが起きます。
CIを導入してからは、GitHubのプルリクエスト画面に赤いバツ印が出るので、誰が見ても一目で「マージしてはいけない状態」だと分かるようになりました。
GitHub Actionsの基本構成
ワークフローファイルの置き場所
GitHub Actionsの設定は、リポジトリの .github/workflows/ ディレクトリにYAMLファイルを置くだけで動き始めます。
.github/
└── workflows/
└── ci.yml
このファイル1つで、「いつ」「何を」実行するかをすべて定義します。
最小構成のワークフロー
まずは、pushとpull_requestをトリガーにテストを実行する最小構成を見てみましょう。
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: リポジトリをチェックアウト
uses: actions/checkout@v4
- name: PHPをセットアップ
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, pdo, pdo_sqlite
- name: Composerの依存関係をインストール
run: composer install --no-interaction --prefer-dist
- name: .envファイルを準備
run: cp .env.example .env
- name: アプリケーションキーを生成
run: php artisan key:generate
- name: テストを実行
run: php artisan test
on セクションでトリガーを、jobs セクションで実行内容を定義するのが基本の骨格です。
MySQLを使ったテストをCI上で動かす
SQLiteだけでは検知できない不具合がある
先ほどの例では pdo_sqlite を使いましたが、本番がMySQLの場合、SQLite特有の挙動の違いでバグを見逃すことがあります。
私が実際に遭遇したのは、SQLiteでは通っていた日付比較のテストが、MySQL環境に切り替えたとたんに失敗したケースです。
原因は、SQLiteが文字列としての日付比較にかなり寛容な一方、MySQLの DATE 型は厳密に型を見るため、比較のフォーマットがずれていたことでした。
本番と同じデータベースエンジンでCIを回しておかないと、こういう「環境差分のバグ」に気づけません。
GitHub ActionsでMySQLサービスコンテナを使う
GitHub Actionsには、テスト用のデータベースを一時的に起動できる services という機能があります。
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_DATABASE: laravel_test
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, pdo, pdo_mysql
- name: Composerの依存関係をインストール
run: composer install --no-interaction --prefer-dist
- name: .envファイルを準備
run: cp .env.example .env
- name: アプリケーションキーを生成
run: php artisan key:generate
- name: DB接続情報を上書き
run: |
echo "DB_CONNECTION=mysql" >> .env
echo "DB_HOST=127.0.0.1" >> .env
echo "DB_PORT=3306" >> .env
echo "DB_DATABASE=laravel_test" >> .env
echo "DB_USERNAME=root" >> .env
echo "DB_PASSWORD=password" >> .env
- name: マイグレーションとテストを実行
run: |
php artisan migrate --force
php artisan test
services に定義したコンテナは、ジョブの実行中だけ裏側で立ち上がり、127.0.0.1 の指定ポートでアクセスできるようになります。
options の health-cmd は、MySQLの起動が完了するまでジョブの開始を待つためのヘルスチェック設定です。
これを入れておかないと、MySQLの起動が終わる前にマイグレーションが走ってしまい、接続エラーで失敗することがあります。
つまずきやすいポイント:CIだけ環境変数が反映されない
.env がリポジトリに含まれていないことが原因
CIを組んだ初日、私は「ローカルでは通るのに、CI上でだけ APP_KEY が空です、というエラーで落ちる」という現象にハマりました。
❌ Before:.env の準備を忘れる
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
- run: composer install --no-interaction --prefer-dist
- run: php artisan test
.env はセキュリティ上の理由で .gitignore に含まれているため、リポジトリをチェックアウトしただけでは存在しません。
その状態で php artisan test を実行すると、APP_KEY が未設定のまま暗号化処理が呼ばれ、RuntimeException が発生します。
✅ After:.env の準備と key:generate を明示的に行う
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
- run: composer install --no-interaction --prefer-dist
- run: cp .env.example .env
- run: php artisan key:generate
- run: php artisan test
.env.example をコピーしてから key:generate で暗号化キーを発行する、という2ステップを必ずワークフローの中に書いておく必要があります。
ローカルでは php artisan install や手動セットアップの過程で当たり前にやっていることほど、CIの設定では抜け落ちやすいので要注意です。
Composerのキャッシュで実行時間を短縮する
毎回ゼロから依存関係を入れるのは非効率
前述の構成のままでも動きますが、プッシュのたびにComposerの依存関係をゼロからダウンロードしていると、リポジトリが大きくなるにつれてCIの実行時間がどんどん延びていきます。
actions/cache でvendorディレクトリをキャッシュする
actions/cache を使うと、前回のジョブでインストールした内容を再利用できます。
- name: Composerキャッシュディレクトリを取得
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Composerキャッシュを復元
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Composerの依存関係をインストール
run: composer install --no-interaction --prefer-dist
key に composer.lock の内容のハッシュ値を含めることで、依存関係が変わっていなければキャッシュがそのまま使われ、変わっていれば自動的に再インストールされる仕組みになっています。
応用:mainブランチへのマージをデプロイの条件にする
テストが通ったコードだけを次の工程に進める
CIでテストを自動化しておくと、次のステップとして「テストが通ったコードだけを自動でデプロイする」という流れに発展させられます。
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: デプロイ処理を実行
run: echo "ここにデプロイコマンドを書く"
needs: test を指定すると、test ジョブが成功した場合のみ deploy ジョブが実行されます。
具体的なデプロイの流れについては、次回の記事で詳しく解説します。
まとめ
この記事のポイント
- CIを導入すると、プッシュやPRのたびに自動でテストが実行され、テストが落ちたコードのマージを防げる
- GitHub Actionsは
.github/workflows/にYAMLファイルを置くだけで動き出す - 本番と同じDBエンジンでテストするには、
servicesでMySQLコンテナを起動する .envの準備とkey:generateはCI特有の落とし穴なので、忘れずワークフローに含めるactions/cacheでComposerの依存関係をキャッシュすると実行時間を短縮できる
次に読むべき記事
CIでテストが自動化できたら、次はいよいよ実際のアプリケーションを本番環境に届ける番です。
→ 次の記事:Laravelアプリを本番環境にデプロイする流れ