使用 Github Actions 构建 PHP CI/CD 流水线:Composer 缓存与并行测试优化
大家好,今天我们来聊聊如何使用 Github Actions 构建一套高效的 PHP CI/CD 流水线,重点关注 Composer 依赖缓存和并行测试优化,以提升构建速度和开发效率。
一、 CI/CD 流水线基础概念回顾
在深入细节之前,我们先简单回顾一下 CI/CD 的核心概念。
- CI (Continuous Integration,持续集成): 指的是频繁地将代码集成到共享仓库,并在每次集成后运行自动化构建和测试,以便尽早发现和解决集成问题。
- CD (Continuous Delivery/Deployment,持续交付/持续部署): 指的是自动化的将代码变更发布到测试环境(持续交付)或生产环境(持续部署)。 持续交付确保代码随时可以发布,而持续部署则是在代码通过所有测试后自动部署到生产环境。
二、 Github Actions 简介
Github Actions 是 Github 提供的 CI/CD 服务,它允许你自动化软件开发工作流程。 Actions 基于 YAML 文件配置,存储在仓库的 .github/workflows 目录下。 一个 Actions workflow 由以下几个关键概念组成:
- Workflow: 一个完整的自动化流程,由一个或多个 jobs 组成。
- Job: 一个 Workflow 中的一个独立任务,运行在一台虚拟机上。
- Step: 一个 Job 中的一个步骤,可以是一个 shell 命令,也可以是一个 Action。
- Action: 一个可重用的代码块,封装了特定的功能,例如 checkout 代码、安装依赖、运行测试等等。
- Runner: 执行 Job 的虚拟机,可以是 Github 提供的,也可以是自托管的。
- Event: 触发 Workflow 运行的事件,例如 push 代码、创建 pull request 等。
三、 搭建基础 PHP CI 流水线
我们从一个最简单的 PHP CI 流水线开始,逐步添加 Composer 缓存和并行测试。
-
创建 Workflow 文件: 在你的 Github 仓库中创建
.github/workflows/php.yml文件。 -
基本 Workflow 配置:
name: PHP CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2' # 或者你需要的PHP版本
extensions: dom, curl, libxml, mbstring, intl, mysql, zip, gd, openssl, PDO, pdo_mysql, bcmath, soap, xmlrpc, xsl
- name: Install Dependencies
run: composer install -n --prefer-dist
- name: Run Tests
run: ./vendor/bin/phpunit
代码解释:
name: PHP CI: Workflow 的名称,会在 Github Actions 界面显示。on:: 定义触发 Workflow 运行的事件。 这里配置为 push 到main分支和创建到main分支的 pull request。jobs:: 定义 Workflow 中的 jobs。 这里只有一个名为build的 job。runs-on: ubuntu-latest: 指定 job 运行的虚拟机环境为 Ubuntu 最新版。steps:: 定义 job 中的步骤。uses: actions/checkout@v3: 使用actions/checkout@v3Action 将代码检出到虚拟机。uses: shivammathur/setup-php@v2: 使用shivammathur/setup-php@v2Action 设置 PHP 环境。php-version指定 PHP 版本,extensions指定需要安装的 PHP 扩展。run: composer install -n --prefer-dist: 运行composer install命令安装依赖。-n表示非交互模式,--prefer-dist表示优先从 dist 仓库下载。run: ./vendor/bin/phpunit: 运行 PHPUnit 测试。
四、 Composer 依赖缓存
每次构建都重新下载 Composer 依赖是非常耗时的。 Github Actions 提供了缓存机制,可以缓存 Composer 依赖,加快构建速度。
- 修改 Workflow 文件,添加 Composer 缓存:
name: PHP CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: dom, curl, libxml, mbstring, intl, mysql, zip, gd, openssl, PDO, pdo_mysql, bcmath, soap, xmlrpc, xsl
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install -n --prefer-dist
- name: Run Tests
run: ./vendor/bin/phpunit
代码解释:
- Get Composer Cache Directory: 使用
composer config cache-files-dir命令获取 Composer 缓存目录,并将目录路径输出到$GITHUB_OUTPUT变量中。 - Cache dependencies: 使用
actions/cache@v3Action 缓存 Composer 依赖。path: 指定要缓存的路径,这里使用了上一步获取的 Composer 缓存目录。key: 指定缓存的 key。 这里使用了操作系统的类型 (runner.os)、字符串composer和composer.lock文件的 hash 值。 当composer.lock文件发生变化时,缓存 key 也会发生变化,从而触发缓存更新。restore-keys: 指定恢复缓存的 key。 这里使用了操作系统的类型和字符串composer。 当缓存 key 不存在时,会尝试使用restore-keys中的 key 恢复缓存。
缓存 Key 的设计:
- 使用操作系统类型作为 key 的一部分,可以避免不同操作系统之间的缓存冲突。
- 使用
composer.lock文件的 hash 值作为 key 的一部分,可以确保只有当依赖发生变化时才更新缓存。composer.lock文件记录了项目中确切的依赖版本,确保环境一致性。
五、 并行测试优化
如果你的项目有大量的测试用例,运行所有测试用例会耗费大量时间。 Github Actions 提供了并行测试的功能,可以将测试用例分配到多个虚拟机上并行运行,从而缩短测试时间。
- 修改 Workflow 文件,添加并行测试:
name: PHP CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
test-group: [1, 2, 3]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: dom, curl, libxml, mbstring, intl, mysql, zip, gd, openssl, PDO, pdo_mysql, bcmath, soap, xmlrpc, xsl
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install -n --prefer-dist
- name: Run Tests
run: ./vendor/bin/phpunit --group ${{ matrix.test-group }}
代码解释:
- strategy: 定义 Job 的运行策略。
- matrix: 定义一个矩阵,用于创建多个 Job。 这里定义了一个名为
test-group的矩阵,其值为[1, 2, 3]。 这意味着将会创建 3 个 Job,每个 Job 的test-group值分别为 1、2 和 3。
- matrix: 定义一个矩阵,用于创建多个 Job。 这里定义了一个名为
- Run Tests: 修改
phpunit命令,使用--group参数指定要运行的测试组。${{ matrix.test-group }}表示当前 Job 的test-group值。
测试分组:
为了实现并行测试,你需要将测试用例分成多个组。 你可以使用 PHPUnit 的 @group 注解将测试用例分配到不同的组。
<?php
namespace TestsUnit;
use PHPUnitFrameworkTestCase;
/**
* @group group1
*/
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function test_that_true_is_true()
{
$this->assertTrue(true);
}
}
在这个例子中,ExampleTest 类被分配到 group1 测试组。
注意事项:
- 你需要根据你的项目情况,合理地将测试用例分成多个组,以确保每个 Job 的测试时间大致相同。
- 确保不同的测试组之间没有依赖关系,否则并行测试可能会导致错误。
- 使用并行测试后,你需要修改测试报告的生成方式,将多个 Job 的测试结果合并成一个报告。
六、 更进一步:使用服务容器进行测试
如果你的测试依赖于数据库或其他服务,你可以使用 Github Actions 的服务容器功能。 服务容器允许你在 Job 中启动一个或多个 Docker 容器,用于提供测试所需的依赖服务。
- 修改 Workflow 文件,添加服务容器:
name: PHP CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: test_db
ports:
- 3306:3306
options: --health-check-interval=10s --health-check-timeout=5s --health-check-retries=3
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: dom, curl, libxml, mbstring, intl, mysql, zip, gd, openssl, PDO, pdo_mysql, bcmath, soap, xmlrpc, xsl
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install Dependencies
run: composer install -n --prefer-dist
- name: Wait for MySQL to be ready
run: sleep 30s # 简单的等待,实际应该使用更可靠的健康检查机制
- name: Run Tests
run: ./vendor/bin/phpunit
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: test_db
DB_USERNAME: root
DB_PASSWORD: password
代码解释:
- services: 定义 Job 中使用的服务容器。
- mysql: 定义一个名为
mysql的服务容器。- image: 指定 Docker 镜像为
mysql:8.0。 - env: 设置容器的环境变量。 这里设置了
MYSQL_ROOT_PASSWORD、MYSQL_DATABASE等环境变量。 - ports: 将容器的 3306 端口映射到虚拟机的 3306 端口。
- options: 设置容器的选项。 这里设置了健康检查参数,确保 MySQL 服务启动成功。
- image: 指定 Docker 镜像为
- mysql: 定义一个名为
- Wait for MySQL to be ready: 由于容器启动需要时间,我们需要等待 MySQL 服务启动完成后才能运行测试。 这里使用了
sleep 30s命令简单地等待 30 秒。 更可靠的做法是使用健康检查机制,例如使用nc命令检查端口是否可用。 - Run Tests: 设置测试运行时的环境变量,例如
DB_CONNECTION、DB_HOST、DB_PORT等,以便测试程序可以连接到 MySQL 数据库。
七、 其他优化技巧
- 使用 Composer 的
--optimize-autoloader选项: 这个选项可以优化 Composer 的自动加载器,提高性能。 可以在composer install命令中添加--optimize-autoloader选项。 - 使用静态分析工具: 使用静态分析工具,例如 PHPStan、Psalm 等,可以在不运行代码的情况下检测代码中的错误和潜在问题。 可以在 CI 流水线中添加静态分析步骤,尽早发现问题。
- 使用代码风格检查工具: 使用代码风格检查工具,例如 PHP CS Fixer、EasyCodingStandard 等,可以自动修复代码风格问题,保持代码风格一致。
- 监控构建时间: 使用 Github Actions 的 Insights 功能监控构建时间,可以帮助你发现性能瓶颈,并采取相应的优化措施。
八、 一个更完整的例子
name: PHP CI/CD
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.1', '8.2']
test-group: [1, 2]
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: test_db
ports:
- 3306:3306
options: --health-check-interval=10s --health-check-timeout=5s --health-check-retries=3
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: dom, curl, libxml, mbstring, intl, mysql, zip, gd, openssl, PDO, pdo_mysql, bcmath, soap, xmlrpc, xsl
- name: Get Composer Cache Directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php-version }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php-version }}-
- name: Install Dependencies
run: composer install -n --prefer-dist --optimize-autoloader
- name: Run Static Analysis (PHPStan)
run: ./vendor/bin/phpstan analyse
- name: Run Code Style Checks (PHP CS Fixer)
run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
- name: Wait for MySQL to be ready
run: sleep 30s
- name: Run Tests
run: ./vendor/bin/phpunit --group ${{ matrix.test-group }}
env:
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: test_db
DB_USERNAME: root
DB_PASSWORD: password
这个例子展示了一个更完整的 PHP CI/CD 流水线,包含了以下功能:
- 支持多个 PHP 版本。
- 使用 Composer 缓存。
- 运行静态分析 (PHPStan)。
- 运行代码风格检查 (PHP CS Fixer)。
- 使用 MySQL 服务容器进行测试。
- 并行测试。
九、 常见问题与排查
在使用 Github Actions 时,可能会遇到各种问题。 以下是一些常见问题和排查方法:
- Workflow 运行失败:
- 查看 Workflow 的运行日志,找到错误信息。
- 检查 YAML 文件是否存在语法错误。
- 检查 Actions 是否正确配置。
- 检查测试代码是否存在错误。
- Composer 依赖安装失败:
- 检查
composer.json文件是否存在语法错误。 - 检查网络连接是否正常。
- 检查 Composer 镜像源是否可用。
- 清除 Composer 缓存,重新安装依赖。
- 检查
- 缓存未生效:
- 检查缓存 key 是否正确。
- 检查缓存路径是否正确。
- 检查是否正确配置了
restore-keys。
- 并行测试失败:
- 检查测试组的划分是否合理。
- 检查不同的测试组之间是否存在依赖关系。
- 检查测试报告的生成方式是否正确。
在排查问题时,可以尝试以下方法:
- 简化 Workflow: 将 Workflow 简化到最小的可用状态,逐步添加功能,以便更容易找到问题所在。
- 本地调试: 在本地环境中重现问题,以便更方便地进行调试。
- 查阅文档: 查阅 Github Actions、Composer、PHPUnit 等相关文档,了解更多信息。
- 搜索社区: 在 Stack Overflow、Github Discussions 等社区搜索相关问题,看看是否有人遇到过类似的问题。
十、 总结一下,精简高效的 CI/CD 是关键
搭建 PHP CI/CD 流水线需要精心设计 Workflow,利用 Composer 缓存和并行测试等优化手段可以显著提升构建速度。 此外,持续的监控和问题排查也是确保 CI/CD 流水线稳定运行的重要组成部分。