使用Github Actions构建PHP CI/CD流水线:Composer缓存与并行测试优化

使用 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 缓存和并行测试。

  1. 创建 Workflow 文件: 在你的 Github 仓库中创建 .github/workflows/php.yml 文件。

  2. 基本 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@v3 Action 将代码检出到虚拟机。
    • uses: shivammathur/setup-php@v2: 使用 shivammathur/setup-php@v2 Action 设置 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 依赖,加快构建速度。

  1. 修改 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@v3 Action 缓存 Composer 依赖。
    • path: 指定要缓存的路径,这里使用了上一步获取的 Composer 缓存目录。
    • key: 指定缓存的 key。 这里使用了操作系统的类型 (runner.os)、字符串 composercomposer.lock 文件的 hash 值。 当 composer.lock 文件发生变化时,缓存 key 也会发生变化,从而触发缓存更新。
    • restore-keys: 指定恢复缓存的 key。 这里使用了操作系统的类型和字符串 composer。 当缓存 key 不存在时,会尝试使用 restore-keys 中的 key 恢复缓存。

缓存 Key 的设计:

  • 使用操作系统类型作为 key 的一部分,可以避免不同操作系统之间的缓存冲突。
  • 使用 composer.lock 文件的 hash 值作为 key 的一部分,可以确保只有当依赖发生变化时才更新缓存。 composer.lock 文件记录了项目中确切的依赖版本,确保环境一致性。

五、 并行测试优化

如果你的项目有大量的测试用例,运行所有测试用例会耗费大量时间。 Github Actions 提供了并行测试的功能,可以将测试用例分配到多个虚拟机上并行运行,从而缩短测试时间。

  1. 修改 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。
  • 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 容器,用于提供测试所需的依赖服务。

  1. 修改 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_PASSWORDMYSQL_DATABASE 等环境变量。
      • ports: 将容器的 3306 端口映射到虚拟机的 3306 端口。
      • options: 设置容器的选项。 这里设置了健康检查参数,确保 MySQL 服务启动成功。
  • Wait for MySQL to be ready: 由于容器启动需要时间,我们需要等待 MySQL 服务启动完成后才能运行测试。 这里使用了 sleep 30s 命令简单地等待 30 秒。 更可靠的做法是使用健康检查机制,例如使用 nc 命令检查端口是否可用。
  • Run Tests: 设置测试运行时的环境变量,例如 DB_CONNECTIONDB_HOSTDB_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 流水线稳定运行的重要组成部分。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注