robiningelbrecht/continuous-integration-example

使用GitHub工作流和操作构建的CI/CD示例

dev-master 2022-04-02 11:18 UTC

README

CI/CD

CI/CD codecov.io License PHPStan Enabled PHP

本仓库旨在使用GitHub工作流和操作构建一个相当完整的CI/CD示例。

请注意,本仓库中使用的工具集并非构建稳健工作流的唯一解决方案。我相信还有很多我从未听说的工具可以完成这项工作 💅。

如果您喜欢这个教程,请考虑给它一个 ⭐

注意:本教程不会解释GitHub工作流和操作的完整内部工作原理,因此需要一些基本知识。

注意2:由于我是一个PHP开发者,本教程中的所有示例都是基于PHP的。将工作流转换为用于“非PHP”代码库应该是相当容易的。

🐣 设置仓库

在我们深入了解技术细节之前,我们首先需要设置我们的仓库。我们想要做的最主要的事情是设置默认分支分支保护规则

默认分支

默认分支被认为是您仓库中的“基本”分支,所有拉取请求和代码提交都会自动针对此分支进行,除非您指定了不同的分支。

您可以通过导航到https://github.com/username/repository/settings/branches来配置默认分支。您可以将其设置为任何您想要的名称,但通常使用“main”或“master”。

分支保护规则

分支保护规则允许您禁用强制推送,防止删除分支,并在合并前可选地要求状态检查。这些检查对于确保代码质量和构建稳健的CI非常重要。目前,我们将配置最基本的设置,但我们稍后会回到这个话题。

导航到https://github.com/username/repository/settings/branches并添加一个新的分支保护规则,设置如下:

  • 分支名称模式:您的默认分支名称
  • ✅ 在合并前要求拉取请求
  • ✅ 要求批准
  • 合并前所需的批准数量:1
  • ✅ 在合并前要求状态检查通过
  • ✅ 在合并前要求分支是最新的

所有其他选项应保持未选中。

这些规则将基本上禁用向您的默认分支推送的能力,并强制您使用拉取请求和代码审查进行工作。

配置问题 & PR模板

通过问题和拉取请求模板,您可以自定义并标准化当贡献者在您的仓库中打开问题和拉取请求时希望包含的信息。

虽然这不是设置您的工作流所必需的步骤,但始终标准化用户向您提供关于新功能和错误反馈的方式是一个好主意。是否要使用此功能取决于您(和您的团队)。

💎 配置CI工作流

下一步是配置CI工作流。本例中使用的工作流包含两个作业,这两个作业应该能够确保代码质量。它会在所有拉取请求上触发。

on:
  pull_request:
  workflow_dispatch:

由于我们配置了代码更改只能通过拉取请求提交到默认分支,所以我们确信测试套件将为每行新的/更改的代码运行。

运行测试套件

让我们更仔细地看看在这个作业中配置的所有步骤。

为了使单元测试能够运行,我们需要安装PHP(duh)。稍后我们还需要Xdebug来检查和确保代码覆盖率。

  # https://github.com/marketplace/actions/setup-php-action
  - name: Setup PHP 8.1 with Xdebug 3.x
    uses: shivammathur/setup-php@v2
    with:
      php-version: '8.1'
      coverage: xdebug

🔥 高级技巧 🔥

如果您想针对多个PHP版本和/或操作系统运行测试套件,可以通过使用矩阵设置来做到这一点。

  name: Test suite PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }}
  runs-on: ${{ matrix.operating-system }}
  strategy:
    matrix:
      operating-system: ['ubuntu-latest', 'ubuntu-18.04']
      php-versions: [ '7.4', '8.0', '8.1' ]
  steps:
    - name: Setup PHP ${{ matrix.php-versions }} with Xdebug 3.x
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php-versions }}
        coverage: xdebug

这应该在矩阵中产生所有可能的组合的工作流程运行。

CI matrix

下一步是拉取代码并安装所有依赖项。

  # https://github.com/marketplace/actions/checkout
  - name: Checkout code
    uses: actions/checkout@v2

  - name: Install dependencies
    run: composer install --prefer-dist

之后,测试终于可以运行。

  - name: Run test suite
    run: vendor/bin/phpunit --testsuite unit --fail-on-incomplete  --log-junit junit.xml --coverage-clover clover.xml

您可能已经注意到运行测试的命令中包含一些选项。每个选项都有其目的。

  • --fail-on-incomplete:强制PHPUnit在测试不完整时失败。
  • --log-junit junit.xml:生成XML文件,以便稍后发布测试结果。
  • --coverage-clover clover.xml:生成XML文件,以便稍后检查测试覆盖率。

运行测试后,我们可以将它们可视化和作为评论发布在拉取请求中。

  # https://github.com/marketplace/actions/publish-unit-test-results
  - name: Publish test results
    uses: EnricoMi/publish-unit-test-result-action@v1.31
    if: always()
    with:
      files: "junit.xml"
      check_name: "Unit test results"

Unit test results

我们还将生成的clover.xml报告发送到codecov.io

Codecov在确保公司交付高质量代码时,可以在需要的时候提供有意义的覆盖率洞察。

Codecov.io基本上允许您检查代码覆盖率并找到未测试的代码。它是通过提供花哨的图表和图形来实现的。

  # https://github.com/marketplace/actions/codecov
  - name: Send test coverage to codecov.io
    uses: codecov/codecov-action@v2.1.0
    with:
      files: clover.xml
      fail_ci_if_error: true # optional (default = false)
      verbose: true # optional (default = false)

codecov操作也会在每个拉取请求上添加评论。

Codecov.io results

最后但同样重要的是,我们确保整个项目的最低测试覆盖率至少为90%。如果未达到最低覆盖率,作业将失败。这是通过使用这个测试覆盖率检查器来实现的。

  - name: Check minimum required test coverage
    run: |
      CODE_COVERAGE=$(vendor/bin/coverage-checker clover.xml 90 --processor=clover-coverage)
      echo ${CODE_COVERAGE}
      if [[ ${CODE_COVERAGE} == *"test coverage, got"* ]] ; then
        exit 1;
      fi

静态代码分析与编码标准

由于静态代码分析和应用编码标准不需要Xdebug或其他花哨的依赖项,因此它们在单独的作业中配置。

为了运行这些任务,我们将使用PHPStanPHP编码标准修复器

PHPStan专注于在没有实际运行代码的情况下查找代码中的错误。它甚至可以在您为代码编写测试之前捕获整个类的错误。

PHP编码标准修复器(PHP CS Fixer)工具将修复您的代码以遵循标准;无论您是想遵循PSR-1、PSR-2等中定义的PHP编码标准,还是其他社区驱动的标准,如Symfony。

我们再次需要安装PHP,检出代码并安装依赖项。

  # https://github.com/marketplace/actions/setup-php-action
  - name: Setup PHP 8.1
    uses: shivammathur/setup-php@v2
    with:
      php-version: '8.1'

  # https://github.com/marketplace/actions/checkout
  - name: Checkout code
    uses: actions/checkout@v2
    
  - name: Install dependencies
    run: composer install --prefer-dist

然后运行静态代码分析器。

  - name: Run PHPStan
    run: vendor/bin/phpstan analyse

并检查编码标准。

  - name: Run PHPcs fixer dry-run
    run: vendor/bin/php-cs-fixer fix --dry-run --stop-on-violation --config=.php-cs-fixer.dist.php

如果这两个任务中的任何一个失败,作业将失败。

现在CI工作流程已经配置完毕,我们可以回到仓库分支保护规则,并通过配置额外的必需状态检查来加强它们。

Protected branch settings

这些设置要求CI工作流程中的作业都成功后才能合并PR。

示例拉取请求

有一些示例拉取请求,展示了PR可能失败的不同原因以及使其通过所需的内容。

🚀 配置构建和部署工作流程

在这个阶段,新功能和错误修复可以“安全地”合并到主分支,但它们仍然需要部署到远程服务器。本例中使用的工作流程包含两个负责部署的任务。这将手动触发。

  on:
    workflow_dispatch:

创建构建

我们将通过使用工件来创建构建。在开始之前,我们首先需要检查所选分支是否允许部署

  build:
    if: github.ref_name == 'master' || github.ref_name == 'development'
    name: Create build ${{ github.run_number }} for ${{ github.ref_name }}

如果选择的是除masterdevelopment之外的任何分支,则工作流程将被中止。

要创建包含必要文件的构建,我们首先必须再次拉取依赖项

  # https://github.com/marketplace/actions/checkout
  - name: Checkout code
    uses: actions/checkout@v2

  # https://github.com/marketplace/actions/setup-php-action
  - name: Setup PHP 8.1
    uses: shivammathur/setup-php@v2
    with:
      php-version: '8.1'

  - name: Install dependencies
    run: composer install --prefer-dist --no-dev

之后,我们可以创建一个包含部署所需文件的工件。

  # https://github.com/marketplace/actions/upload-a-build-artifact
  - name: Create artifact
    uses: actions/upload-artifact@v3
    with:
      name: release-${{ github.run_number }}
      path: |
        src/**
        vendor/**

在工作流程期间创建的所有工件都可以从工作流程摘要页面下载。这可以方便地“调试”工件并检查实际包含哪些文件。

Artifacts

部署到远程服务器

下一步和最后一步是将我们之前创建的构建部署。在我们可以这样做之前,我们首先需要配置一个环境

导航到https://github.com/username/repository/settings/environments进行此操作。在本例中,我们将有一个针对masterdevelopment的环境,我们将配置以下内容

Environment settings

这些设置将强制仅将development分支部署到开发环境。该环境中配置的秘密将用于在部署期间连接到远程服务器。

现在我们准备好开始配置部署作业。我们首先从以下内容开始

  • 引用构建步骤。在构建完成之前,我们不能部署。
  • 引用要部署的环境。这将
    1. 允许我们使用在该环境中配置的秘密
    2. 允许GitHub验证是否已将正确的分支部署到该环境
    3. 允许GitHub指示PR是否已部署(或未部署)

Branch deploy info

注意:${{ github.ref_name }}包含工作流程初始化时使用的分支或标签。

  needs: build
  environment:
    name: ${{ github.ref_name }}
    url: https://${{ github.ref_name }}.env

通过设置concurrency,我们确保一次只能运行一个部署(每个环境)。

  concurrency: ${{ github.ref_name }}

此作业的第一步将下载我们之前作业中创建的工件。它包含需要传输到远程服务器的所有文件。

  # https://github.com/marketplace/actions/download-a-build-artifact
  - name: Download artifact
    uses: actions/download-artifact@v3
    with:
      name: release-${{ github.run_number }}

接下来,我们将使用rsync将所有下载的文件传输到服务器。此步骤使用我们在存储库的环境上配置的秘密进行身份验证。

  # https://github.com/marketplace/actions/rsync-deployments-action
  - name: Rsync build to server
    uses: burnett01/rsync-deployments@5.2
    with:
      switches: -avzr --delete
      path: .
      remote_path: /var/www/release-${{ github.run_number }}/
      remote_host: ${{ secrets.SSH_HOST }}
      remote_user: ${{ secrets.SSH_USERNAME }}
      remote_key: ${{ secrets.SSH_KEY }}

文件传输完成后,我们需要执行的最后一步是运行部署脚本。此脚本可以执行许多操作,具体取决于您使用的堆栈。在本例中,我们将运行一些数据库更新并安装新的crontab。

  # https://github.com/marketplace/actions/ssh-remote-commands
  - name: Run remote SSH commands
    uses: appleboy/ssh-action@master
    with:
      host: ${{ secrets.HOST }}
      username: ${{ secrets.USERNAME }}
      key: ${{ secrets.KEY }}
      port: ${{ secrets.PORT }}
      script: |
        RELEASE_DIRECTORY=/var/www/release-${{ github.run_number }}
        CURRENT_DIRECTORY=/var/www/app
        
        # Remove symlink.
        rm -r "${CURRENT_DIRECTORY}"
        
        # Create symlink to new release.
        ls -s "${RELEASE_DIRECTORY}" "${CURRENT_DIRECTORY}"
        
        # Run database migrations
        ${CURRENT_DIRECTORY}/bin/console doctrine:migrations:migrate
        
        # Install updated crontab
        crontab ${RELEASE_DIRECTORY}/crontab
        
        # Clear cache
        ${CURRENT_DIRECTORY}/bin/console cache:clear

到此为止,新功能和/或错误修复已部署到您的远程服务器。您可以继续重复此循环。

🍔 想要了解更多吗?

本例仅涉及持续集成和持续开发的几个方面。还有很多其他内容我可以涵盖,但我希望保持内容简洁明了。

集成测试

集成测试是软件测试阶段,其中将各个软件模块组合成一组进行测试。

目前有多种框架提供集成测试的工具集,其中之一是 codeception

端到端测试

端到端测试是一种从软件产品开始到结束进行测试的技术,以确保应用程序流程按预期行为。

https://codecept.io/ 是提供端到端测试框架的许多工具之一。

视觉回归测试

视觉回归测试通过比较部署前后的截图,来检查在执行任何代码更改后用户将看到的内容。

BackstopJS 是一个开源工具,允许您实施此类检查。

合并时自动部署

此示例将部署作为手动操作处理,但可以自动化此过程。假设您希望在每次合并时进行部署,您可以配置工作流程以如下方式触发

  on:
    push:
      branches:
        - master
        - develop

加快测试套件速度

随着您的应用程序以及测试套件的增长,您的流程完成所需的时间会越来越长。有一些技巧可以加快您的测试套件

  • 使用 Paratest 并行运行测试
  • 缓存您的供应商依赖项
  • 对于影响数据库的测试,使用内存中的 SQLite 数据库
  • 如果不需要测试覆盖率,请禁用 Xdebug

组合操作

组合操作 可用于将工作流程拆分为更小、可重用的组件。我可以告诉您所有关于它们的信息,但 这篇博客文章 完美地解释了如何定义和使用它们。向作者 James Wallis 👌 表示敬意

🌈 反馈和问题

如我在开头所述,这仅是您如何设置 CI/CD 和部署流程的一种方法。这只是让您入门的一个示例。如果您有任何反馈或建议以改进此教程,请告知我。我总是乐于学习新的方法和了解新的工具。

如果您有任何问题,请随时 📭 联系我,我将很高兴为您提供帮助。