使用 GitHub Actions 创建持续集成测试工作流
已发表: 2022-03-10在为 GitHub 和 Bitbucket 等版本控制平台上的项目做出贡献时,惯例是主分支包含功能代码库。 然后,还有其他分支,其中几个开发人员可以在主副本上工作,以添加新功能、修复错误等。 这很有意义,因为监控传入的更改将对现有代码产生的影响变得更加容易。 如果有任何错误,可以在将更改集成到主分支之前轻松跟踪和修复。 手动检查每一行代码以查找错误或错误可能非常耗时——即使对于一个小项目也是如此。 这就是持续集成的用武之地。
什么是持续集成 (CI)?
“持续集成 (CI) 是将来自多个贡献者的代码更改自动集成到单个软件项目中的做法。”
— Atlassian.com
持续集成 (CI) 背后的总体思路是确保对项目所做的更改不会“破坏构建”,即破坏现有代码库。 根据您设置工作流程的方式,在您的项目中实施持续集成,只要有人对存储库进行更改,就会创建一个构建。
那么,什么是构建?
在这种情况下,构建是将源代码编译为可执行格式。 如果成功,则意味着传入的更改不会对代码库产生负面影响,并且可以继续使用。 但是,如果构建失败,则必须重新评估更改。 这就是为什么建议在将项目合并到主代码库之前通过在不同分支上处理项目副本来对项目进行更改的原因。 这样,如果构建中断,将更容易找出错误来自哪里,并且它也不会影响您的主要源代码。
“越早发现缺陷,修复它们的成本就越低。”
— David Farley,持续交付:通过构建、测试和部署自动化实现可靠的软件发布
有多种工具可用于帮助为您的项目创建持续集成。 其中包括 Jenkins、TravisCI、CircleCI、GitLab CI、GitHub Actions 等。在本教程中,我将使用 GitHub Actions。
用于持续集成的 GitHub 操作
CI Actions 是 GitHub 上的一项相当新的功能,可以创建自动运行项目构建和测试的工作流。 工作流包含一个或多个可在事件发生时激活的作业。 此事件可能是推送到 repo 上的任何分支或创建拉取请求。 在我们继续进行时,我将详细解释这些术语。
让我们开始吧!
先决条件
这是一个面向初学者的教程,所以我将主要从表面上讨论 GitHub Actions CI。 读者应该已经熟悉使用 PostgreSQL 数据库创建 Node JS REST API、Sequelize ORM,以及使用 Mocha 和 Chai 编写测试。
您还应该在您的机器上安装以下内容:
- 节点JS,
- PostgreSQL,
- 新PM,
- VSCode(或您选择的任何编辑器和终端)。
我将使用我已经创建的名为countries-info-api
的 REST API。 这是一个简单的 api,没有基于角色的授权(在编写本教程时)。 这意味着任何人都可以添加、删除和/或更新国家/地区的详细信息。 每个国家/地区都有一个 id(自动生成的 UUID)、名称、首都和人口。 为此,我使用了 Node js、express js 框架和 Postgresql 作为数据库。
在开始编写测试覆盖率测试和持续集成工作流文件之前,我将简要说明如何设置服务器、数据库。
您可以克隆countries-info-api
存储库以跟进或创建您自己的 API。
使用的技术: Node Js、NPM(Javascript 的包管理器)、Postgresql 数据库、sequelize ORM、Babel。
设置服务器
在设置服务器之前,我从 npm 安装了一些依赖项。
npm install express dotenv cors npm install --save-dev @babel/core @babel/cli @babel/preset-env nodemon
我正在使用 express 框架并以 ES6 格式编写,所以我需要 Babeljs 来编译我的代码。 您可以阅读官方文档以了解有关它的工作原理以及如何为您的项目配置它的更多信息。 Nodemon 将检测对代码所做的任何更改并自动重新启动服务器。
注意:使用--save-dev
标志安装的 Npm 包仅在开发阶段需要,并且可以在package.json
文件的 devDependencies 下看到。
我在index.js
文件中添加了以下内容:
import express from "express"; import bodyParser from "body-parser"; import cors from "cors"; import "dotenv/config"; const app = express(); const port = process.env.PORT; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.get("/", (req, res) => { res.send({message: "Welcome to the homepage!"}) }) app.listen(port, () => { console.log(`Server is running on ${port}...`) })
这将我们的 api 设置为在.env
文件中分配给PORT
变量的任何内容上运行。 这也是我们将声明我们不希望其他人轻松访问的变量的地方。 dotenv
npm 包从.env
加载我们的环境变量。
现在,当我在终端中运行npm run start
时,我得到了这个:
如您所见,我们的服务器已启动并正在运行。 耶!
Web 浏览器中的此链接https://127.0.0.1:your_port_number/
应返回欢迎消息。 也就是说,只要服务器正在运行。
接下来,数据库和模型。
我使用 Sequelize 创建了国家模型,并连接到我的 Postgres 数据库。 Sequelize 是 Nodejs 的 ORM。 一个主要优点是它节省了我们编写原始 SQL 查询的时间。
由于我们使用的是 Postgresql,因此可以使用CREATE DATABASE database_name
命令通过 psql 命令行创建数据库。 这也可以在您的终端上完成,但我更喜欢 PSQL Shell。
在 env 文件中,我们将按照以下格式设置数据库的连接字符串。
TEST_DATABASE_URL = postgres://<db_username>:<db_password>@127.0.0.1:5432/<database_name>
对于我的模型,我遵循了这个 sequelize 教程。 它很容易理解并解释了有关设置 Sequelize 的所有内容。
接下来,我将为刚刚创建的模型编写测试,并在 Coverall 上设置覆盖范围。
编写测试和报告覆盖率
为什么要编写测试? 就个人而言,我相信编写测试可以帮助您作为开发人员更好地了解您的软件在用户手中的预期表现,因为这是一个头脑风暴的过程。 它还可以帮助您及时发现错误。
测试:
有不同的软件测试方法,但是,在本教程中,我使用了单元测试和端到端测试。
我使用 Mocha 测试框架和 Chai 断言库编写了我的测试。 我还安装了sequelize-test-helpers
来帮助测试我使用sequelize.define
创建的模型。
测试覆盖率:
建议检查您的测试覆盖率,因为结果显示我们的测试用例是否真正覆盖了代码,以及在我们运行测试用例时使用了多少代码。
我使用了 Istanbul(一个测试覆盖工具)、nyc(Instabul 的 CLI 客户端)和 Coveralls。
根据文档,Istanbul 使用行计数器检测您的 ES5 和 ES2015+ JavaScript 代码,以便您可以跟踪您的单元测试如何运行您的代码库。
在我的package.json
文件中,测试脚本运行测试并生成报告。
{ "scripts": { "test": "nyc --reporter=lcov --reporter=text mocha -r @babel/register ./src/test/index.js" } }
在此过程中,它将创建一个包含原始覆盖率信息的.nyc_output
文件夹和一个包含覆盖率报告文件的coverage
文件夹。 我的仓库中不需要这两个文件,所以我将它们放在.gitignore
文件中。
现在我们已经生成了一份报告,我们必须将其发送给 Coveralls。 关于 Coveralls(和其他覆盖工具,我假设)的一件很酷的事情是它如何报告您的测试覆盖率。 覆盖范围是逐个文件分解的,您可以查看相关覆盖范围、覆盖和遗漏的行以及构建覆盖范围的变化。
首先,安装工作服 npm 包。 您还需要登录工作服并将存储库添加到其中。
然后通过在您的根目录中创建一个coveralls.yml
文件来为您的 javascript 项目设置工作服。 此文件将保存您从设置部分获得的repo-token
,用于您的工作服 repo。
package.json 文件中需要的另一个脚本是覆盖脚本。 当我们通过 Actions 创建构建时,这个脚本会派上用场。
{ "scripts": { "coverage": "nyc npm run test && nyc report --reporter=text-lcov --reporter=lcov | node ./node_modules/coveralls/bin/coveralls.js --verbose" } }
基本上,它将运行测试、获取报告并将其发送到工作服进行分析。
现在到本教程的重点。
创建 Node JS 工作流文件
至此,我们已经设置了将在 GitHub Action 中运行的必要作业。 (想知道“工作”是什么意思?继续阅读。)
GitHub 通过提供起始模板使创建工作流文件变得容易。 正如在操作页面上看到的,有几个工作流模板服务于不同的目的。 对于本教程,我们将使用 Node.js 工作流程(GitHub 已经建议使用)。
您可以直接在 GitHub 上编辑该文件,但我将在我的本地存储库中手动创建该文件。 包含node.js.yml
文件的文件夹.github/workflows
将位于根目录中。
该文件已经包含一些基本命令,第一条注释解释了它们的作用。
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
我将对它进行一些更改,以便除了上述评论之外,它还运行覆盖。
我的.node.js.yml
文件:
name: NodeJS CI on: ["push"] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run coverage - name: Coveralls uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_GIT_BRANCH: ${{ github.ref }} with: github-token: ${{ secrets.GITHUB_TOKEN }}
这是什么意思?
让我们分解一下。
-
name
这将是您的工作流(NodeJS CI)或作业(构建)的名称,GitHub 将在您的存储库的操作页面上显示它。 -
on
这是触发工作流的事件。 我文件中的那一行基本上是告诉 GitHub 每当向我的仓库推送时触发工作流。 -
jobs
一个工作流可以包含至少一个或多个作业,每个作业都在runs-on
指定的环境中运行。 在上面的文件示例中,只有一个作业运行构建并运行覆盖,它在 Windows 环境中运行。 我还可以将它分成两个不同的工作,如下所示:
更新了 Node.yml 文件
name: NodeJS CI on: [push] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run test coverage: name: Coveralls runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: github-token: ${{ secrets.GITHUB_TOKEN }}
-
env
这包含可用于工作流中的所有或特定作业和步骤的环境变量。 在覆盖作业中,您可以看到环境变量已被“隐藏”。 它们可以在你的 repo 的 secrets 页面中的设置下找到。 -
steps
这基本上是运行该作业时要采取的步骤列表。 -
build
作业做了很多事情:- 它使用签出操作(v2 表示版本)从字面上签出您的存储库,以便您的工作流程可以访问它;
- 它使用 setup-node 操作来设置要使用的节点环境;
- 它运行我们的 package.json 文件中的安装、构建和测试脚本。
-
coverage
这使用了一个coverallsapp 操作,将您的测试套件的LCOV 覆盖率数据发布到coveralls.io 进行分析。
我最初推送了我的feat-add-controllers-and-route
分支并忘记将来自 Coveralls 的 repo_token 添加到我的.coveralls.yml
文件中,所以我得到了你可以在第 132 行看到的错误。
Bad response: 422 {"message":"Couldn't find a repository matching this job.","error":true}
一旦我添加了repo_token
,我的构建就能够成功运行。 如果没有这个标记,工作服将无法正确报告我的测试覆盖率分析。 好在我们的 GitHub Actions CI 在它被推送到主分支之前指出了错误。
注意:这些是在我将工作分成两份之前拍摄的。 此外,我能够在终端上看到覆盖摘要和错误消息,因为我在覆盖脚本末尾添加了--verbose
标志
结论
我们可以看到如何为我们的项目设置持续集成,并使用 GitHub 提供的操作集成测试覆盖率。 还有很多其他方法可以调整它以适应您的项目需求。 尽管本教程中使用的示例 repo 是一个非常小的项目,但您可以看到即使在更大的项目中持续集成也是如此重要。 现在我的工作已经成功运行,我有信心将分支与我的主分支合并。 我仍然建议您在每次运行后仔细阅读这些步骤的结果,以查看它是否完全成功。