Drupal 10:如何使用工作流、Makefile、DDEV 等在 GitHub 上运行测试

Drupal 10:使用工作流在 GitHub 上运行 Drupal 测试

在 Drupal 开发中,有许多不同的工具可用于验证和测试 Drupal 网站。检查自定义代码能让我们遵循编码标准,还能确保消除常见的编码问题。而添加测试可以保证 Drupal 网站的功能正常运行。

如果你的 Drupal 项目中有测试用例,理想情况下需要在开发工作流程的某个阶段运行这些测试。让 GitHub 在你推送代码或创建拉取请求时运行测试,这样你就可以放心,测试套件会在工作流程的某个环节被执行。同时,你也希望能轻松地在本地运行测试,而不必记住大量命令行参数。

在本文中,我将展示如何针对 Drupal 网站设置验证和测试,以及如何在你创建拉取请求时让 GitHub 执行这些步骤。本文假设你有一个通过 Composer 管理的 Drupal 10 项目。

让我们从使用 Makefile 创建一个运行器开始。

一、Makefile

Makefile 是一种自动化工具,它允许开发人员创建任务的依赖结构,然后使用“make”命令来运行这些任务。这种文件格式最初是为了帮助编译复杂项目而开发的,但它可以轻松用于执行任何所需的自动化脚本。

例如,假设我们希望运行一个带有多个不同参数的命令。这可能是一个 curl 命令,甚至是一个 rsync 命令,其中参数的顺序至关重要。为此,你需要创建一个名为“Makefile”的文件,并添加以下内容。

sync-files:
    rsync -avzh source/directory destination/directory

要运行这个命令,只需输入“make”,后面跟上命令的名称。

make sync-files

现在你有了一个可重复执行的任务,每次都会以相同的方式运行一组 bash 脚本。

与为每个操作创建单个 shell 脚本相比,使用 Makefile 更可取,因为你可以为每个任务创建依赖关系。因此,在上面的示例中,我们可以规定在运行 rsync 命令之前需要创建目标目录。我们所要做的就是创建另一个执行此操作的任务,并将其设置为 sync-files 命令的先决条件。

create-destination-directory:
    mkdir -p destination/directory

sync-files: create-destination-directory
    rsync -avzh source/directory destination/directory

我经常使用的一个技巧是在命令开头使用“@”符号。这告诉 make 运行命令,但不要在命令行上打印正在运行的命令。这会使输出简洁一些,但这实际上取决于个人喜好。以下是添加了此选项的相同 rsync 命令。

sync-files:
    @rsync -avzh source/directory destination/directory

Makefile 的内容远不止我在这里介绍的这些,但这基本上是基本设置。虽然掌握 Makefile 的语法有点棘手,但它对于快速运行那些原本需要查找参数或从有用命令文本文件中复制的任务很有用。

在这里使用 Makefile 的想法是简化在 GitHub 上运行命令的过程,同时也让开发人员更轻松地运行相同的命令。Makefile 可以利用先决条件功能轻松地将所有内容组合在一个命令下。这样做可以让你只需使用一个命令就可以安装 Drupal 并运行整个测试套件。当然,在 Drupal 模块开发等场景中,合理利用 Makefile 也能起到很好的辅助作用。

或者,你可以使用 Composer 操作或其他一些自动化脚本来执行任务,不过 Composer 操作不支持依赖关系,因此你可能需要创建一系列 bash 脚本来执行这些操作。也可以使用类似 Robo 的工具来为你运行任务,我过去也对此进行过实验。最终,在运行 PHP 依赖项之前,你需要一种方法来安装它们,这意味着在你的工作流程中某个地方需要一个 Makefile 或脚本。

无论你选择哪种技术,简化 GitHub 工作流程的关键是将更多命令权重放在 Makefile 一侧,这意味着你的 GitHub 操作可以简洁明了。

二、DDEV

为了简化正在运行的任务(以及运行这些任务的环境),我倾向于使用 DDEV。使用这个平台可以提供一个一致且可重复的环境,你可以轻松配置不同的设置。本文的其余示例将酌情使用“ddev”命令,该命令将在 DDEV 创建的 Docker 环境中执行命令。

使用 Docker 环境还意味着,在运行该环境的每台机器上,系统的所有路径都是相同的,这有助于简化设置过程。

当你想要进行更新并需要确保它们能正常工作时,DDEV 非常有用。例如,如果你想查看你的网站在新版本的 PHP 上是否能正常运行,只需在配置中进行更改并创建一个拉取请求。GitHub 操作会找到新的配置,并考虑使用新版本来执行你的测试。在 Drupal 升级时,DDEV 同样能帮助我们更方便地进行环境配置和测试。

三、安装 Drupal

任何设置的首要任务都是安装 Drupal,首先要安装 Composer 包和我们可能需要的任何 Node 包。当我们启动 DDEV 环境时,它会自动将 Drupal 的 settings.php 文件复制到正确的位置,所以这里我们无需担心这个问题。

一旦 Drupal 代码库就位,你就可以安装 Drupal 并编译所需的任何主题资产。以下 make 命令将安装 Composer 和 Node 包,然后将 Drupal 安装和主题编译任务交给二级 make 命令。

setup-drupal:  ## Install dependencies, install Drupal, and compile the theme.
    @ddev composer install --prefer-dist --no-progress
    @ddev exec --dir=/var/www/html/docroot/themes/custom/my_custom_theme npm install
    @ddev exec npm install
    $(MAKE) site-install
    ${MAKE} themebuild

站点安装命令将使用 Drush 安装 Drupal。根据我的经验,完全删除并重新安装数据库有助于确保环境干净。例如,在运行迁移测试时,你可能会发现,如果不首先删除所有表,那么在重新安装站点后,一些迁移表仍然会存在。我们还会清除缓存并额外导入配置,以确保站点是最新的。

site-install: ## Install the Drupal site.
    @ddev drush sql-drop --yes
    @ddev drush si standard --existing-config --yes --account-name=admin --account-pass=admin
    @ddev drush cr
    @ddev drush cim -y

这假设你使用标准安装配置文件来安装站点(并非总是如此),并且你有一些配置需要导入。如果你使用多站点设置,则需要更改此命令以安装一个或多个变体的站点进行测试。

一旦该任务完成,Drupal 站点就会运行起来。

此时,你可能需要考虑使用 Default Content Deploy 将一些测试内容注入到你的站点中。这不是必需的,除非你要对站点进行任何行为或回归测试。对于这些类型的测试,存在内容是至关重要的,而 Default Content Deploy 是我发现的实现这一目标的最佳方式。

这里的最后一步是构建主题资产,这将完全取决于你用于管理主题的包。我在几个项目中使用 Grunt,所以这是一个使用 Grunt 编译主题资产的示例。

themebuild: ## Build the theme.
    @ddev exec --dir=/var/www/html/docroot/themes/custom/my_custom_theme npx grunt

我要指出的是,在运行 npm 或 npx 之前不需要执行额外的安装步骤,因为这些包在 DDEV 中是预安装的。

四、验证

在开始测试代码之前,我们需要确保代码是有效的。我通常会将验证和测试工作流程分开,因为如果代码库中的某些代码无效,那么运行完整的测试套件就是在浪费时间。

为了确保 Drupal 代码库有效,我们可以做很多事情,首先是验证 Composer 文件。

(一)Composer 验证

我们可以运行的最简单的验证任务是验证主 composer.json 和 composer.lock 文件,这可以通过命令“composer validate”来实现。

composer-validate: ## Validate Drupal composer.json and composer.lock.
    @ddev composer validate

Composer 文件无效通常意味着在 Composer 工作流程中出现了问题,并且在你再次尝试更新 Composer 包时可能会导致后续问题。

(二)PHP 代码检查器

PHP 代码检查器允许你检查 Drupal 自定义代码是否符合 Drupal 编码标准。在项目中使用编码标准有很多原因,其中一个重要原因是确保在代码进入生产环境之前纠正常见的错误和安全问题。PHP 代码检查器还会检查你的 Drupal YAML 配置文件,以确保没有发现常见问题。

你可以按照相关文章详细介绍的步骤进行操作,在 Drupal 代码库中安装 PHP 代码检查器。

安装完成后,你可以运行 phpcs 命令来检查你的 Drupal 代码库。由于这需要相当多的参数才能完成,我们创建一个 make 命令来为我们执行此操作。

phpcs: ## Run phpcs analysis.
    @ddev exec vendor/bin/phpcs --standard=Drupal,DrupalPractice --exclude=SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses --extensions=php,module,inc,install,test,profile,theme,info,txt,yml --ignore=node_modules,bower_components,vendor web/modules/custom web/themes/custom web/profiles

请记住,我们只关心自己编写的 PHP 代码,这意味着我们专门将 phpcs 命令指向自定义代码库。检查整个 Drupal 核心和贡献代码库没有意义,因为这些代码已经由 drupal.org 上的工具进行过检查。

PHP 代码检查器还附带了 PHP 代码美化和修复工具,可以使用 phpcbf 命令运行。

phpcbf: ## Run phpcbf.
    @ddev exec vendor/bin/phpcbf --standard=Drupal,DrupalPractice --extensions=php,module,inc,install,test,profile,theme,info,txt,yml web/modules/custom web/themes/custom web/profiles

phpcbf 工具可以快速修复许多编码标准错误,因此将其添加到你的 make 文件中很有用,这样你就可以轻松运行它。

请注意,上述命令中的所有路径必须存在,工具才能正常运行。如果你不在网站上使用安装配置文件,可以删除“web/profiles”。

(三)PHPStan

PHPStan 是一个工具,它可以对 PHP 代码进行静态分析,查找可能导致错误的常见问题。安装该工具需要几个辅助包,你可以参考相关文章了解如何在 Drupal 代码库中安装和使用 PHPStan。你还需要创建一个 phpstan.neon 配置文件,工具运行时会自动读取该文件。

安装和配置完成后,可以通过 make 运行该工具。

phpstan: ## Run PHPStan analysis.
    @ddev exec vendor/bin/phpstan

要充分利用 PHPStan,你需要设置正确的级别,这一切都在 phpstan.neon 文件中处理,因此 make 命令只需要运行该工具。我的建议是从级别 0 开始,解决工具发现的所有问题。然后,你需要与团队其他成员商定要达到的级别,以便大家保持一致。

(四)Eslint

Eslint 是一个 JavaScript 静态分析工具,它可以分析你的 JavaScript 代码,促进最佳实践并查找潜在的错误。它还可以用于验证 YAML 文件的语法,捕捉 PHP 代码检查器可能遗漏的问题。

Drupal 提供了使用 Eslint 所需的一切,并且开始时的 setup-drupal 命令在“npm install”命令中安装了该工具。

你需要在项目根目录下创建一个 .eslintrc.json 文件(如果该文件尚未存在)来配置该工具。该文件的“rules”部分允许你关闭某些检查标准,如果你想在自定义代码中使用诸如“++”之类的东西,这会很有用。

以下是我在项目中经常使用的一个 .eslintrc.json 文件。文件中还添加了对 React 版本的自动检测,用于纠正工具运行时出现的一个小警告。

{
  "extends": "./web/core/.eslintrc.json",
  "rules": {
    "no-plusplus": "off"
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

拥有一个忽略文件也是个好主意,你可以用它来跳过任何不想进行 lint 检查的内容。如果代码库中有任何包含第三方包的供应商目录,就会用到这个。

docroot/modules/custom/my_custom_theme/js/vendor/

一旦设置好这些,你就可以运行 eslint 工具,使用 -c 标志传入配置文件,使用 --ignore-path 传入忽略文件。

eslint: ## Run eslint.
    @ddev exec npx eslint -c .eslintrc.json --ignore-path .eslintignore web/modules/custom
    @ddev exec npx eslint -c .eslintrc.json --ignore-path .eslintignore web/themes/custom

为了在本地协助你的团队,你可以添加一个“eslint-fix”任务,它会尝试修复工具发现的任何编码标准问题。

eslint-fix: ## Run eslint with the --fix flag.
    @ddev exec npx eslint -c .eslintrc.json web/modules/custom --fix
    @ddev exec npx eslint -c .eslintrc.json web/themes/custom --fix

运行 eslint-fix 通常可以解决检测到的大多数问题,这意味着你可以专注于修复那些真正重要的问题。

同样,请注意这里的目录必须存在才能进行扫描。你可以在行首使用“#”将其注释掉。

五、测试

理想情况下,你的 Drupal 网站应该有多个测试用例。这些可以分为单元测试和行为测试,但至关重要的是,我们可以在任何需要的平台上运行它们。无论是 Drupal 开发还是 Drupal 模块开发,编写合理的测试用例都能保证代码质量。

(一)PHPUnit

Drupal 的内部测试系统由 PHPUnit 驱动,可用于测试单个函数、服务类或用户交互。

要在你的 Drupal 网站上运行 PHPUnit,你需要将 Drupal 安装目录下 core 目录中的 phpunit.xml.dist 文件复制到项目的根目录。文件中有一些设置需要更改,以确保它们指向正确的位置,但完成后,你可以将此文件提交到项目中。

测试本身可以在 DDEV 环境中轻松运行,但在运行测试之前,我们首先需要确保正确的输出目录已准备好(并具有正确的权限)。以下 make 命令处理此任务。

phpunit: ## Run the Drupal phpunit tests for custom code.
    @ddev exec mkdir -p /var/www/html/private/browsertest_output
    @ddev exec chmod -R 777 /var/www/html/private/browsertest_output
    @ddev exec mkdir -p web/sites/simpletest/browser_output
    @ddev exec chmod -R 777 web/sites/simpletest
    @ddev exec ./vendor/bin/phpunit web/modules/custom/

这将对项目中所有自定义模块运行单元测试。

(二)Cypress

Cypress 是一个行为测试系统,它的行为就像在你的 Drupal 网站上的用户一样,登录并与之交互。这类测试比较棘手,因为它们需要在本地环境中运行。嗯,严格来说并非如此,因为它们可以在任何环境的无头浏览器中运行,但我经常发现,在本地运行能获得最佳效果。

我经常在 Drupal 网站根目录旁边的“tests/cypress”目录中安装 Cypress,所以以下示例考虑了这一点。

cypress: ## Run Cypress tests
    cd tests/cypress && npx cypress run

Cypress 测试无法访问 Drupal 数据库,因此还存在管理测试环境本身的问题。我发现,在某些环境中重新安装 Drupal 可能会导致超时错误,所以我倾向于使用 Default Content Deploy 重新导入内容。我在之前的文章中写过关于使用 Cypress 和 Default Content Deploy 的内容。

我还包含的一个命令是 Cypress GUI 的快捷方式,这是一个强大的开发工具,可以实时显示正在运行的测试。

cypress-gui: ## Open the Cypress GUI
    cd tests/cypress && npx cypress open

六、Makefile 元步骤

为了加快速度,你应该在 Makefile 中创建元步骤,这样就可以使用先决条件功能一次性运行多个操作。我们刚刚设置了一系列用于验证和测试代码库的任务,逐个运行这些任务并不明智。使用先决条件功能意味着我们可以创建简单的 make 任务,只运行我们创建的任务。

为此,我们需要创建两个任务,一个用于验证,另一个用于测试。“validation” 这个 make 命令可能是最繁忙的:

validate: composer-validate phpcs phpstan eslint ## Validate the project.

然后可以使用 “test” make 命令一次性运行所有测试。

test: phpunit cypress ## Run all of the tests.

有了这些任务,我们现在可以创建 GitHub 工作流了。

七、GitHub 工作流

此时,你应该能够使用 Makefile 运行全部或部分安装和测试过程。关于 GitHub 工作流文件,我不会详细介绍,因为已经有关于该文件本身的很好的文档。我将介绍如何创建一个工作流文件,该文件将为我们的 Drupal 网站运行所有验证和测试。

工作流 YAML 文件需要放在 “.github/workflows/” 目录中。我通常将我的文件命名为 “test.yml”,因为它准确地描述了该文件的作用。

文件开头包含工作流的名称以及运行时间的详细信息。可以让 GitHub 在平台上的各种不同事件上运行你的工作流。

以下是一个典型的 GitHub 工作流文件的开头,详细说明了名称和一些操作。在这种情况下,当提交推送到以 “feature/” 开头的分支,或者针对 “main” 或 “stage” 分支创建拉取请求时,我们将运行该工作流。

name: Run tests

on:
  push:
    branches:
      - 'feature/**'
  pull_request:
    branches:
      - main
      - stage
      - prod

接下来是详细说明此工作流必须运行的作业的部分。下面详细说明的 “test” 工作流将在最新版本的 Ubuntu 上运行,并包含多个步骤。

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
       # Steps go here...

让我们定义几个步骤。

首先,我们需要检出要测试的代码库,这可以使用 “actions/checkout” 包来完成。此包有更多可用选项,但我们的目的只需要默认选项。

- name: Check out repository code.
  uses: actions/checkout@v4

由于我们使用的是 DDEV,我们还需要包含一个步骤,让 GitHub 了解 DDEV。这可以使用 ddev/github-action-setup-ddev 包来完成。同样,这个系统有很多可用选项,但由于 DDEV 环境将自动运行,我们在这里不需要做其他任何事情。

- name: Include ddev runner.
  uses: ddev/github-action-setup-ddev@v1

DDEV 环境准备好后,我们现在可以开始安装网站,这可以使用我们一开始创建的 “make setup-drupal” 命令来完成。此任务完成后,网站将在 GitHub 上的 DDEV 环境中完全运行。

- name: Setup drupal for testing.
  run: make setup-drupal

在运行测试之前,我们需要使用 “make validate” 命令运行验证任务。

- name: Run validate handler.
  run: make validate

这里我们的工作流与本地环境略有不同。由于 Cypress 测试的运行方式,PHPUnit 测试和 Cypress 测试需要在单独的任务中运行(稍后会详细介绍)。要运行 PHPUnit 测试,我们只需调用 “make phpunit” 命令。

- name: Run test handler.
  run: make phpunit

我发现在 GitHub 上运行 Cypress 测试的最佳方法是使用 cypress-io/github-action 包。这会为我们的 Cypress 测试准备好所需的一切,我们只需要包含 “working-directory” 指令,因为 Cypress 测试不在项目的根目录中。

- name: Run cypress tests.
  uses: cypress-io/github-action@v6
  with:
    working-directory: tests/cypress

此任务将自动触发我们的 Cypress 测试,如果其中一个测试失败,将返回正确的失败状态。

这就是我们需要添加到 GitHub 工作流文件中的全部内容,以下是完整的文件内容。

name: Run tests

on:
  push:
    branches:
      - 'feature/**'
  pull_request:
    branches:
      - main
      - stage

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repository code.
        uses: actions/checkout@v4

      - name: Include ddev runner.
        uses: ddev/github-action-setup-ddev@v1

      - name: Setup drupal for testing.
        run: make setup-drupal

      - name: Run validate handler.
        run: make validate

      - name: Run test handler.
        run: make phpunit

      - name: Run cypress tests.
        uses: cypress-io/github-action@v6
        with:
          working-directory: tests/cypress

我们在这里创建的文件故意很短,因为我们将复杂性添加到了 Makefile 中,而不是这个文件中。这也意味着系统的配置是代码库的一部分,而不是工作流的一部分。

现在一切就绪,你应该在 GitHub 中检查项目的操作权限,以确保你实际上可以运行工作流。这些是你应该关注的主要权限。

“允许所有操作” 是一个比较开放的权限,但它允许我们使用来自不同仓库的操作来检出代码、运行 DDEV 和执行 Cypress 测试。

有了这些设置,你现在可以通过推送到 “feature/x” 分支或针对 main 或 stage 分支创建拉取请求来对 Drupal 代码库进行验证和测试检查。这对于后续 Drupal 升级到 Drupal 11 等操作的代码验证也很有帮助。

八、结论

掌握了这种技术,你现在可以将代码推送到 GitHub,并自动对代码运行验证和测试步骤。这为你的代码提供了可靠的安全保障,让你可以确信,每次向系统添加更改时,一切都符合规范且能正常工作。

我想尽可能详细地介绍,以便任何人都能在几分钟内创建自己的工作流和操作,开始在 GitHub 上进行持续集成。即使你的 Drupal 项目中没有测试,你也可以从代码验证开始,然后根据这里提供的详细信息开始编写测试。如果你在任何部分遇到问题,欢迎向我反馈。

工作流的添加还能很好地与 GitHub 界面集成。所有通过的工作流都会收到一个绿色小勾,表示它们通过了工作流中的所有验证和检查。

可以将 GitHub 工作流文件的功能扩展得比我在这里展示的更多,但我发现,给这个文件添加复杂性在调试工作流问题时往往会导致问题。如果你能够在本地使用一两个 make 命令从空白状态过渡到一个完全验证和测试过的环境,那么很有可能在 GitHub 上也能如此。

GitHub 工作流也可以向其他方向发展。例如,你还可以创建一个工作流,触发将代码部署到你选择的平台。同样,我建议将实际的构建过程交给另一个应用程序,如 Ansible 或 Deployer,而不是将这种复杂性添加到 GitHub 工作流文件中。

有意将项目设置和验证/测试步骤的复杂性添加到 Makefile 中,也使我们能够相对轻松地将这种技术移植到其他系统。例如,如果我们想使用 GitLab,我们可以创建一个 “.gitlab-ci.yml” 文件,并将所需的 make 命令添加到该文件中,以便在该平台上触发相同的操作。你需要考虑该环境中 DDEV 的存在,但如果需要,有办法绕过使用 DDEV 包装器,而选择使用纯 Docker 命令。