継続的インテグレーション
継続的インテグレーション (CI) は、プッシュやプルリクエストのたびにチェックを実行し、壊れたテスト、 コーディング規約の違反、型のエラーを変更のマージ前に見つけます。このページでは、 pieceofcake2/app スケルトンで作ったアプリケーションのための GitHub Actions のワークフローを示し、各ジョブを、そのツールを詳しく説明しているページへのリンクとともに 紹介します。また、各ジョブの短いサマリーを実行結果のページに表示する方法も説明します。 Jenkins でテストを実行する方法は、最後の Jenkins で説明します。
実行するもの
CakePHP 2 のアプリケーションでは、通常、次のジョブを実行します。
| ジョブ | ツール | チェックする内容 | 詳細 |
|---|---|---|---|
| PHPUnit | vendor/bin/phpunit | ユニットテストと統合テスト (サポートする PHP のバージョンごと) | テスト、統合テスト |
| コーディング規約 | vendor/bin/phpcs | CakePHP のコーディング規約 | コーディング規約 |
| PHPStan | vendor/bin/phpstan | コードを実行せずに見つかるバグ | 静的解析 |
| E2E | vendor/bin/phpunit -c phpunit.e2e.xml.dist | 本物のブラウザーでのページの動作 | E2E テスト |
pieceofcake2/app スケルトンには、最初の 3 つの設定ファイル phpunit.xml.dist、phpcs.xml、 phpstan.neon が含まれており、PHPUnit、CakePHP のコードスニファー、PHPStan、 pieceofcake2/phpstan-cakephp2 が開発用の 依存パッケージになっています。E2E テストには pieceofcake2/e2e-test と少しのセットアップが必要です。 E2E テストを書いたら、このジョブを追加してください。
GitHub Actions のワークフロー
ワークフローを .github/workflows/ci.yml として保存します。4 つのジョブが並行して実行されます。
# .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# Test values. The real salts are not in the repository (config/.env is ignored by git).
SECURITY_SALT: ci-salt-for-tests-only-0123456789abcdef
SECURITY_CIPHER_SEED: '1234567890'
jobs:
phpunit:
name: PHPUnit (PHP ${{ matrix.php-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-version: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: cakephp_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_TEST_DATASOURCE: Database/Mysql
DB_TEST_HOST: 127.0.0.1
DB_TEST_PORT: 3306
DB_TEST_USERNAME: root
DB_TEST_PASSWORD: root
DB_TEST_DATABASE: cakephp_test
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl, pdo_mysql
coverage: pcov
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache Composer packages
uses: actions/cache@v6
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php${{ matrix.php-version }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: |
${{ runner.os }}-php${{ matrix.php-version }}-composer-
- name: Install Composer packages
run: composer install --no-interaction --no-progress
- name: Create config/database.php
run: cp config/database.php.default config/database.php
- name: Setup problem matchers for PHPUnit
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: Run PHPUnit
run: vendor/bin/phpunit --log-junit junit.xml --coverage-clover coverage.xml
phpcs:
name: Coding standard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, intl
coverage: none
tools: cs2pr
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache Composer packages
uses: actions/cache@v6
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.4-composer-${{ hashFiles('composer.lock') }}
restore-keys: |
${{ runner.os }}-php8.4-composer-
- name: Install Composer packages
run: composer install --no-interaction --no-progress
- name: Check the coding standard
id: phpcs
run: vendor/bin/phpcs --report-full --report-checkstyle=phpcs-report.xml
- name: Annotate the pull request
if: ${{ always() && steps.phpcs.outcome == 'failure' }}
run: cs2pr phpcs-report.xml
phpstan:
name: PHPStan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, intl
coverage: none
tools: cs2pr
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache Composer packages
uses: actions/cache@v6
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.4-composer-${{ hashFiles('composer.lock') }}
restore-keys: |
${{ runner.os }}-php8.4-composer-
- name: Install Composer packages
run: composer install --no-interaction --no-progress
- name: Run PHPStan
id: phpstan
run: vendor/bin/phpstan analyse --no-progress --error-format=checkstyle > phpstan-report.xml
- name: Annotate the pull request
if: ${{ always() && steps.phpstan.outcome == 'failure' }}
run: cs2pr phpstan-report.xml
e2e:
name: E2E
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: cakephp_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_TEST_DATASOURCE: Database/Mysql
DB_TEST_HOST: 127.0.0.1
DB_TEST_PORT: 3306
DB_TEST_USERNAME: root
DB_TEST_PASSWORD: root
DB_TEST_DATABASE: cakephp_test
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, intl, pdo_mysql
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache Composer packages
uses: actions/cache@v6
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php8.4-composer-${{ hashFiles('composer.lock') }}
restore-keys: |
${{ runner.os }}-php8.4-composer-
- name: Install Composer packages
run: composer install --no-interaction --no-progress
- name: Create config/database.php
run: cp config/database.php.default config/database.php
# Chrome for Testing and the matching chromedriver
- name: Setup Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@v2
with:
chrome-version: stable
install-chromedriver: true
- name: Run E2E tests
env:
PANTHER_CHROME_BINARY: ${{ steps.setup-chrome.outputs.chrome-path }}
PANTHER_CHROME_DRIVER_BINARY: ${{ steps.setup-chrome.outputs.chromedriver-path }}
PANTHER_NO_SANDBOX: 1
run: vendor/bin/phpunit -c phpunit.e2e.xml.dist
# Screenshots, HTML and server logs of the failed tests
- name: Upload E2E artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: e2e-artifacts
path: tests/E2E/artifacts/
retention-days: 7
if-no-files-found: ignoreすべてのジョブに共通する部分は次のとおりです。
- PHP: setup-php は、アプリケーションが必要とする 拡張とともに PHP をインストールします。
mbstringとintl、それにデータベースの PDO ドライバー (pdo_mysql、pdo_pgsql、pdo_sqliteまたはpdo_sqlsrv) です。テストでapcuやredisなどのキャッシュエンジンを使う場合は、その拡張も追加してください。PHPUnit のジョブはマトリックスの PHP のバージョンごとに実行します。ほかのジョブは 1 つのバージョンだけで十分です。 - Composer: キャッシュのステップは、ダウンロードしたパッケージを PHP のバージョンと
composer.lockをキーにして実行の間で保存します。composer.lockはコミットしてください。 ロックファイルはマトリックスのすべての PHP のバージョンでインストールできる必要があります。 新しい PHP でパッケージを更新する場合は、composer.jsonのconfig.platform.phpに最も古い バージョン (たとえば"8.0.30") を設定してください。 - データベース: データベースを使うジョブは、MySQL を サービスコンテナー として起動します。スケルトンでは
config/database.phpをコミットしないので、ジョブはconfig/database.php.defaultをコピーします。このファイルはtest接続の設定を環境変数DB_TEST_*から読み込みます (環境変数 を参照)。フィクスチャはこのデータベースに作られるので、事前にテーブルを用意する必要はありません。 - ソルト: スケルトンは
Security.saltとSecurity.cipherSeedをSECURITY_SALTとSECURITY_CIPHER_SEEDから読み込みます。ワークフローではテスト用の値をセットしています。 本番環境の値をワークフローに書いてはいけません。
PostgreSQL でテストする場合は、サービスと環境変数を次のように変更します。
services:
postgres:
image: postgres:17
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: cakephp_test
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready"
--health-interval=10s
--health-timeout=5s
--health-retries=5
env:
DB_TEST_DATASOURCE: Database/Postgres
DB_TEST_HOST: 127.0.0.1
DB_TEST_PORT: 5432
DB_TEST_USERNAME: postgres
DB_TEST_PASSWORD: postgres
DB_TEST_DATABASE: cakephp_testさらに、pdo_mysql の代わりに pdo_pgsql をインストールします。両方のデータベースでテストするには、 pieceofcake2/app の CI のように、マトリックスに db-type の軸を追加します。
従来の app/ 構成のアプリケーションでは、ファイルは app/ の中にあり (app/Config/database.php)、 データベースの設定が環境変数を読まないこともあります。その場合は、 Jenkins のように、 CI 用の database.php をステップの中で書き出してください。
ジョブ
PHPUnit
PHPUnit のジョブは、スケルトンの phpunit.xml.dist を使って tests/TestCase のテストを マトリックスの PHP のバージョンごとに実行します。テストとフィクスチャの書き方は テスト を、test 接続については テスト用データベースのセットアップ を参照してください。
--log-junit junit.xml はテストの結果を JUnit XML 形式で、--coverage-clover coverage.xml はコードカバレッジを Clover 形式で書き出します (コードカバレッジの生成 を参照)。 カバレッジにはカバレッジドライバーが必要です。setup-php の coverage: pcov で PCOV が インストールされます。どちらのファイルも、ジョブサマリー やカバレッジの サービスが読み込みます。使わない場合は、オプションを外して coverage: none にすると、 ジョブが速くなります。
カバレッジのアップロードは任意です。たとえば Codecov に送るには、 テストの後にステップを追加し、ジョブに id-token: write の権限を与えます (OIDC を使うと、 リポジトリにトークンを置く必要はありません)。
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
use_oidc: true
files: coverage.xml
flags: php${{ matrix.php-version }}統合テスト
IntegrationTestTrait を使う統合テストは、tests/TestCase/Controller にある普通の PHPUnit の テストなので、PHPUnit のジョブで一緒に実行されます。統合テストは、config/core.php の設定 (ワークフローでセットしたソルトを含む) のもとで、アプリケーションのルートを通してリクエストを ディスパッチします。テスト中に送信したメールは EmailTrait が受け取るので、実際には送信されません。 統合テスト を参照してください。
コーディング規約
コーディング規約のジョブは、スケルトンの phpcs.xml を使って PHP_CodeSniffer を実行します。 この設定は CakePHP 5 のコーディング規約をもとに、CakePHP 2 のコードに合わない規則を除外したものです。 --report-full は違反をログに表示し、--report-checkstyle は違反をファイルに書き出します。 チェックが失敗すると、setup-php の tools: cs2pr でインストールされる cs2pr が、そのファイルを プルリクエストの変更行へのアノテーションに変換します。ほとんどの違反は、手元で vendor/bin/phpcbf を実行すれば修正できます。 コーディング規約 を参照してください。
PHPStan
PHPStan のジョブは、 pieceofcake2/phpstan-cakephp2 拡張を読み込む スケルトンの phpstan.neon を使ってコードを解析します。--error-format=checkstyle は cs2pr が 読める形式でエラーを書き出し、--no-progress はログを短くします。このジョブにデータベースは 必要ありません。設定、ルールレベル、ベースラインについては 静的解析 を参照してください。
E2E テスト
E2E のジョブは、pieceofcake2/e2e-test のブラウザー テストを専用の phpunit.e2e.xml.dist で実行します。PHPUnit のジョブと同じくデータベースが必要で、 さらに chromedriver とバージョンの合った Chrome が必要です。これは setup-chrome がインストールします。 ほかのジョブより時間がかかるので、1 つの PHP のバージョンで実行してください。
テストが失敗すると、スクリーンショット、ページの HTML、サーバーのログが保存されます。アップロードの ステップは、それらを実行結果のアーティファクトとして 7 日間保存します。path は E2E の ブートストラップファイルの artifactsDir に合わせてください。ビルドで CSS や JavaScript の ファイルを生成する場合は、E2E テストの前にビルドしてください。
E2E テスト と e2e-test のドキュメントの CI の節 を参照してください。
ジョブサマリー
失敗したジョブのエラーは、ほかのステップの出力にはさまれてログに表示されます。 pieceofcake2/.github の ci-summary アクションを使うと、ジョブのレポートを ジョブサマリー に変換して、実行結果のページに表示できます。
- テスト (PHPUnit の JUnit XML): テスト、アサーション、失敗、スキップの数、実行時間、 カバレッジと、失敗したテストごとのテスト内の行とメッセージの表。アサーションの差分などの長い メッセージは、折りたたまれた節に全文が表示されます。
- コーディング規約 (phpcs の checkstyle): エラーと警告の数と、違反ごとの場所、ルール、 メッセージの表。
- PHPStan (checkstyle または JSON): エラーの数と、エラーごとの場所、識別子、メッセージの表。

アクションの実行にはランナーに PHP が必要ですが、これらのジョブではすでにセットアップしています。 トークンも権限も必要なく、アクションがジョブを失敗させることはありません。レポートがない場合は 警告になります。レポートを書き出すステップの後に if: ${{ !cancelled() }} を付けて追加すると、 そのステップが失敗したときにも実行されます。
PHPUnit
マトリックスの値をタイトルに入れると、実行結果のページで各ジョブのサマリーを区別できます。
- name: PHPUnit summary
if: ${{ !cancelled() }}
uses: pieceofcake2/.github/actions/ci-summary@v1
with:
type: junit
title: PHPUnit — PHP ${{ matrix.php-version }}
file: junit.xml
coverage: coverage.xmlジョブが Clover のレポートを書き出さない場合は、coverage を省いてください。
コーディング規約
- name: PHPCS summary
if: ${{ !cancelled() }}
uses: pieceofcake2/.github/actions/ci-summary@v1
with:
type: checkstyle
title: PHPCS
file: phpcs-report.xmlサマリーの最後には、vendor/bin/phpcbf の実行を勧めるヒントが表示されます。cs2pr のステップは そのまま残してください。cs2pr はプルリクエストにアノテーションを追加し、サマリーは一覧を表示します。
PHPStan
- name: PHPStan summary
if: ${{ !cancelled() }}
uses: pieceofcake2/.github/actions/ci-summary@v1
with:
type: phpstan
title: PHPStan
file: phpstan-report.xmlアクションは PHPStan の JSON 形式の出力 (--error-format=json) も読み込めます。
E2E テスト
E2E のステップで JUnit のレポートを書き出し、アップロードするアーティファクトの名前を artifact-name に渡します。テストが失敗した場合、サマリーには、スクリーンショットやログがある 実行結果のアーティファクトへのリンクの行が追加されます。
- name: Run E2E tests
env:
PANTHER_CHROME_BINARY: ${{ steps.setup-chrome.outputs.chrome-path }}
PANTHER_CHROME_DRIVER_BINARY: ${{ steps.setup-chrome.outputs.chromedriver-path }}
PANTHER_NO_SANDBOX: 1
run: vendor/bin/phpunit -c phpunit.e2e.xml.dist --log-junit e2e-junit.xml
# ... the upload step
- name: E2E summary
if: ${{ !cancelled() }}
uses: pieceofcake2/.github/actions/ci-summary@v1
with:
type: junit
title: E2E
file: e2e-junit.xml
artifact-name: e2e-artifacts
artifact-note: Screenshots, page HTML and server logs of the failed tests are in the {artifact} artifact.
名前はアップロードのステップと同じにしてください。E2E のジョブをマトリックスで実行する場合は、 両方のステップで、たとえば e2e-artifacts-php${{ matrix.php-version }} のように、ジョブごとに 別のアーティファクト名を付けてください。
バージョン
@v1 はアクションの最新の v1.x.y リリースを指します。修正や新しい任意の入力は追加されますが、 互換性を壊す変更はありません。特定のリリースに固定するには、@v1.0.0 またはコミットの SHA を 使ってください。すべての入力やほかのツールのレポートについては、 ci-summary の README を参照してください。
Jenkins
Jenkins は継続的インテグレーションサービスで、テストケースの自動化を 手助けしてくれます。これにより、すべてのテストをパスし続けていることを保証し、あなたのアプリケーションを いつでもデプロイできる状態にしてくれます。
CakePHP と Jenkins はかなり簡単にインテグレーションすることができます。ここでの解説は、すでに Unix ライクな環境に Jenkins がインストールされていて、管理者権限を持つことができる状態を前提とします。 また、ジョブの作成とビルドの方法も知っているものとします。もしわからない場合は Jenkins のドキュメント を参考にしてください。 同じ手順は他の CI サービスにも当てはまります。
ジョブの作成
アプリケーションのためのジョブを作成することから始めてください。次に、Jenkins があなたのコードに アクセスできるように、リポジトリと接続します。PHPUnit を含む依存パッケージをインストールする シェルスクリプトの実行 を加えてください。
composer install --no-interactionテスト用データベースの設定の追加
Jenkins のために別のデータベースを用意するのは、初歩的な問題を回避するためには良い考えです。 Jenkins がアクセスできる (通常は localhost の) データベースサーバーに新しくデータベースを 作成したら、以下のような シェルスクリプトの実行 をビルドに加えてください。
cat > config/database.php <<'DATABASE_PHP'
<?php
class DATABASE_CONFIG
{
public $test = [
'datasource' => 'Database/Mysql',
'host' => 'localhost',
'database' => 'jenkins_test',
'login' => 'jenkins',
'password' => 'cakephp_jenkins',
'encoding' => 'utf8mb4',
];
}
DATABASE_PHPこれにより、Jenkins が要求する正しいデータベース設定が常にあることを保証してくれます。 他の設定ファイルにも同じことをしておきましょう。ときどきビルドする前ごとに、データベースを drop し、 再度 create するとよいでしょう。 一度ビルドに失敗すると、立て続けに起きるであろう失敗の連鎖を断ち切ってくれるはずです。
さらに以下の シェルスクリプトの実行 をビルドに加えてください。
mysql -u jenkins -pcakephp_jenkins -e 'DROP DATABASE IF EXISTS jenkins_test; CREATE DATABASE jenkins_test';テストの追加
また別の シェルスクリプトの実行 をビルドに加えてください。このステップではアプリケーションのテストを実行します。 junit のログファイル作成、またはCloverのカバレッジにより、テストの結果を視覚的に確認できるようになります。
vendor/bin/phpunit \
--log-junit junit.xml \
--coverage-clover clover.xmlClover のカバレッジや JUnit の結果を使う場合は、Jenkins 側でもそれらを設定してください。 設定しないと結果を見ることができません。
ビルドを実行する
これでビルドを実行することができるようになりました。 コンソールの出力を確認して、ビルドをパスするように必要な変更を加えましょう。
