Docker 中微服务的自动化测试以实现持续集成

在微服务架构开发相关的项目中,CI/CD从可喜的机会范畴走向了迫切需要的范畴。 自动化测试是持续集成不可或缺的一部分,一种有效的方法可以让团队与家人和朋友度过许多愉快的夜晚。 否则,该项目就有永远无法完成的风险。

可以通过模拟对象的单元测试来覆盖整个微服务代码,但这只能部分解决问题,并留下许多问题和困难,特别是在使用数据进行测试时。 与往常一样,最紧迫的问题是测试关系数据库中的数据一致性、测试云服务的工作以及在编写模拟对象时做出错误的假设。

所有这些以及更多问题都可以通过在 Docker 容器中测试整个微服务来解决。 确保测试有效性的一个毫无疑问的优势是测试投入生产的相同 Docker 镜像。

这种方法的自动化带来了许多问题,下面将描述这些问题的解决方案:

  • 同一docker主机中并行任务的冲突;
  • 测试迭代期间数据库中的标识符冲突;
  • 等待微服务准备就绪;
  • 合并日志并输出到外部系统;
  • 测试传出 HTTP 请求;
  • Web 套接字测试(使用 SignalR);
  • 测试 OAuth 身份验证和授权。

本文基于 我的演讲 在 SECR 2019 上。所以对于那些懒得读书的人来说, 这是演讲录音.

Docker 中微服务的自动化测试以实现持续集成

在本文中,我将告诉您如何使用脚本在 Docker 中运行被测服务、数据库和 Amazon AWS 服务,然后在 Postman 上进行测试,完成后停止并删除创建的容器。 每次代码更改时都会执行测试。 通过这种方式,我们可以确保每个版本都能与 AWS 数据库和服务正确配合。

开发人员自己在 Windows 桌面上运行相同的脚本,并由 Linux 下的 Gitlab CI 服务器运行。

公平地说,引入新的测试不需要在开发人员的计算机上或在提交上运行测试的服务器上安装额外的工具。Docker 解决了这个问题。

由于以下原因,测试必须在本地服务器上运行:

  • 网络永远不会完全可靠。 一千个请求中,有一个可能会失败;
    在这种情况下,自动测试将不起作用,工作将停止,你必须在日志中查找原因;
  • 某些第三方服务不允许太频繁的请求。

此外,不宜使用支架,因为:

  • 支架不仅会因运行在其上的错误代码而损坏,还会因正确代码无法处理的数据而损坏;
  • 无论我们多么努力地尝试恢复测试期间所做的所有更改,都可能会出现问题(否则,为什么要测试?)。

关于项目和流程组织

我们公司开发了一个在 Amazon AWS 云中的 Docker 中运行的微服务 Web 应用程序。 项目中已经使用了单元测试,但经常发生单元测试未检测到的错误。 有必要测试整个微服务以及数据库和 Amazon 服务。

该项目使用标准的持续集成流程,其中包括在每次提交时测试微服务。 分配任务后,开发人员对微服务进行更改、手动测试并运行所有可用的自动化测试。 如有必要,开发人员会更改测试。 如果没有发现问题,则提交到该问题的分支。 每次提交后,测试都会自动在服务器上运行。 成功审核后,会合并到公共分支并对其启动自动测试。 如果共享分支上的测试通过,该服务将在 Amazon Elastic Container Service(工作台)上的测试环境中自动更新。 支架对于所有开发人员和测试人员来说都是必需的,不建议破坏它。 在此环境中的测试人员通过执行手动测试来检查修复或新功能。

项目架构

Docker 中微服务的自动化测试以实现持续集成

该应用程序由十多个服务组成。 其中一些是用 .NET Core 编写的,一些是用 NodeJs 编写的。 每个服务都在 Amazon Elastic Container Service 中的 Docker 容器中运行。 每个都有自己的Postgres数据库,有的还有Redis。 没有通用的数据库。 如果多个服务需要相同的数据,那么当这些数据发生变化时,会通过 SNS(简单通知服务)和 SQS(亚马逊简单队列服务)传输到每个服务,并且服务将其保存在自己单独的数据库中。

SQS 和 SNS

SQS 允许您使用 HTTPS 协议将消息放入队列并从队列中读取消息。

如果多个服务读取一个队列,则每条消息仅到达其中一个。 当运行同一服务的多个实例以在它们之间分配负载时,这非常有用。

如果希望每条消息被传递到多个服务,则每个接收者必须有自己的队列,并且需要SNS将消息复制到多个队列中。

在 SNS 中,您创建一个主题并订阅它,例如 SQS 队列。 您可以向主题发送消息。 在这种情况下,消息将发送到订阅该主题的每个队列。 SNS 没有读取消息的方法。 如果在调试或测试过程中您需要了解发送到 SNS 的内容,您可以创建一个 SQS 队列,订阅所需的主题并读取队列。

Docker 中微服务的自动化测试以实现持续集成

API网关

大多数服务不能直接从互联网访问。 访问是通过 API 网关进行的,该网关检查访问权限。 这也是我们的服务,也有测试。

实时通知

该应用程序使用 信号R向用户显示实时通知。 这是在通知服务中实现的。 它可以直接从 Internet 访问,并且本身可以与 OAuth 一起使用,因为与集成 OAuth 和通知服务相比,在网关中构建对 Web 套接字的支持是不切实际的。

众所周知的测试方法

单元测试用模拟对象替换数据库之类的东西。 例如,如果微服务尝试使用外键在表中创建记录,并且该键引用的记录不存在,则无法执行该请求。 单元测试无法检测到这一点。

В 文章来自微软 建议使用内存数据库并实现模拟对象。

内存数据库是实体框架支持的 DBMS 之一。 它是专门为测试而创建的。 此类数据库中的数据仅存储到使用该数据库的进程终止为止。 它不需要创建表,也不检查数据完整性。

模拟对象仅在测试开发人员了解其工作原理的范围内对它们要替换的类进行建模。

微软文章中没有具体说明如何让Postgres在运行测试时自动启动并执行迁移。 我的解决方案做到了这一点,此外,没有添加任何专门用于微服务本身测试的代码。

让我们继续讨论解决方案

在开发过程中,我们发现单元测试不足以及时发现所有问题,因此决定从不同的角度来解决这个问题。

设置测试环境

第一个任务是部署测试环境。 运行微服务所需的步骤:

  • 配置本地环境的待测服务,在环境变量中指定连接数据库和AWS的详细信息;
  • 启动 Postgres 并通过运行 Liquibase 执行迁移。
    在关系型 DBMS 中,在将数据写入数据库之前,需要创建数据模式,即表。 更新应用程序时,必须将表转换为新版本使用的形式,并且最好不要丢失数据。 这称为迁移。 在最初为空的数据库中创建表是迁移的一种特殊情况。 迁移可以内置到应用程序本身中。 .NET 和 NodeJS 都有迁移框架。 在我们的例子中,出于安全原因,微服务被剥夺了更改数据模式的权利,并且迁移是使用 Liquibase 执行的。
  • 启动 Amazon LocalStack。 这是在家运行的 AWS 服务的实现。 Docker Hub 上有一个现成的 LocalStack 镜像。
  • 运行脚本以在 LocalStack 中创建必要的实体。 Shell 脚本使用 AWS CLI。

用于项目测试 邮差。 它以前就存在,但它是手动启动的,并测试了展台上已经部署的应用程序。 该工具允许您发出任意 HTTP(S) 请求并检查响应是否符合预期。 查询被组合成一个集合,并且可以运行整个集合。

Docker 中微服务的自动化测试以实现持续集成

自动测试如何进行?

在测试过程中,Docker 中一切正常:被测服务、Postgres、迁移工具和 Postman,或者更确切地说是它的控制台版本 - Newman。

Docker 解决了很多问题:

  • 独立于主机配置;
  • 安装依赖:Docker从Docker Hub下载镜像;
  • 将系统恢复到原始状态:只需移除容器即可。

docker-compose 将容器联合成一个与互联网隔离的虚拟网络,容器通过域名相互查找。

测试由 shell 脚本控制。 为了在 Windows 上运行测试,我们使用 git-bash。 因此,一个脚本对于 Windows 和 Linux 来说就足够了。 项目中的所有开发人员都安装了 Git 和 Docker。 在 Windows 上安装 Git 时,会安装 git-bash,因此每个人也都安装了 git-bash。

该脚本执行以下步骤:

  • 构建 docker 镜像
    docker-compose build
  • 启动数据库和LocalStack
    docker-compose up -d <контейнер>
  • LocalStack的数据库迁移和准备
    docker-compose run <контейнер>
  • 启动正在测试的服务
    docker-compose up -d <сервис>
  • 运行测试(纽曼)
  • 停止所有容器
    docker-compose down
  • 在 Slack 中发布结果
    我们进行了一次聊天,其中包含带有绿色复选标记或红色十字以及日志链接的消息。

这些步骤涉及以下Docker镜像:

  • 正在测试的服务与生产服务的镜像相同。 测试的配置是通过环境变量进行的。
  • 对于 Postgres、Redis 和 LocalStack,使用 Docker Hub 中的现成映像。 还有 Liquibase 和 Newman 的现成图像。 我们在他们的框架上构建我们的框架,并在那里添加我们的文件。
  • 要准备 LocalStack,您可以使用现成的 AWS CLI 映像并创建一个包含基于该映像的脚本的映像。

运用 ,您不必仅仅为了将文件添加到容器而构建 Docker 映像。 但是,卷不适合我们的环境,因为 Gitlab CI 任务本身在容器中运行。 您可以从这样的容器控制 Docker,但卷只能挂载来自主机系统的文件夹,而不是来自其他容器的文件夹。

您可能遇到的问题

等待准备

当带有服务的容器正在运行时,这并不意味着它已准备好接受连接。 您必须等待连接继续。

有时可以使用脚本解决此问题 等待它.sh,等待建立 TCP 连接的机会。 但是,LocalStack 可能会抛出 502 Bad Gateway 错误。 此外,它由许多服务组成,如果其中一项服务准备就绪,则不会说明其他服务。

:等待来自 SQS 和 SNS 的 200 响应的 LocalStack 配置脚本。

并行任务冲突

多个测试可以在同一台 Docker 主机上同时运行,因此容器和网络名称必须是唯一的。 此外,来自同一服务的不同分支的测试也可以同时运行,因此将它们的名称写入每个 compose 文件中是不够的。

:脚本将 COMPOSE_PROJECT_NAME 变量设置为唯一值。

Windows功能

在 Windows 上使用 Docker 时,我想指出很多事情,因为这些经验对于理解错误发生的原因非常重要。

  1. 容器中的 Shell 脚本必须具有 Linux 行结尾。
    shell CR 符号是语法错误。 从错误消息中很难看出是这种情况。 在 Windows 上编辑此类脚本时,您需要合适的文本编辑器。 此外,版本控制系统必须正确配置。

这是 git 的配置方式:

git config core.autocrlf input

  1. Git-bash 模拟标准 Linux 文件夹,并且在调用 exe 文件(包括 docker.exe)时,将绝对 Linux 路径替换为 Windows 路径。 但是,这对于不在本地计算机上的路径(或容器中的路径)没有意义。 无法禁用此行为。

:在路径开头添加一个斜杠://bin 而不是 /bin。 Linux 能够理解这样的路径;对于它来说,多个斜杠与一个斜杠是相同的。 但 git-bash 无法识别此类路径,也不会尝试转换它们。

日志输出

运行测试时,我希望查看来自 Newman 和正在测试的服务的日志。 由于这些日志的事件是相互关联的,因此将它们组合在一个控制台中比两个单独的文件方便得多。 纽曼发射通过 docker-compose 运行,因此它的输出最终出现在控制台中。 剩下的就是确保服务的输出也到达那里。

原来的解决方案是做 码头工人组成 没有旗帜 -d,但是使用 shell 功能,将此进程发送到后台:

docker-compose up <service> &

这种方法一直有效,直到需要将日志从 Docker 发送到第三方服务为止。 码头工人组成 停止向控制台输出日志。 然而,团队努力了 码头工人附加.

:

docker attach --no-stdin ${COMPOSE_PROJECT_NAME}_<сервис>_1 &

测试迭代期间的标识符冲突

测试会进行多次迭代。 数据库未清除。 数据库中的记录具有唯一的 ID。 如果我们在请求中写下特定的ID,我们将在第二次迭代时遇到冲突。

为了避免这种情况,ID 必须是唯一的,或者必须删除测试创建的所有对象。 由于需要,某些对象无法删除。

:使用 Postman 脚本生成 GUID。

var uuid = require('uuid');
var myid = uuid.v4();
pm.environment.set('myUUID', myid);

然后在查询中使用该符号 {{myUUID}},它将被替换为变量的值。

通过 LocalStack 进行协作

如果正在测试的服务读取或写入 SQS 队列,则为了验证这一点,测试本身也必须使用该队列。

:Postman 向 LocalStack 发出请求。

AWS 服务 API 已记录,允许在没有 SDK 的情况下进行查询。

如果服务写入队列,那么我们会读取它并检查消息的内容。

如果服务发送消息到SNS,在准备阶段LocalStack也会创建一个队列并订阅这个SNS主题。 那么一切都归结为上面所描述的。

如果服务需要从队列中读取消息,那么在前面的测试步骤中我们会将此消息写入队列。

测试源自被测微服务的 HTTP 请求

某些服务通过 HTTP 与 AWS 以外的其他服务一起工作,并且某些 AWS 功能未在 LocalStack 中实现。

:在这些情况下它可以提供帮助 模拟服务器,其中有一个现成的图像 Docker中心。 预期的请求和对它们的响应由 HTTP 请求配置。 API 已记录,因此我们向 Postman 发出请求。

测试 OAuth 身份验证和授权

我们使用 OAuth 和 JSON Web令牌(JWT)。 该测试需要我们可以在本地运行的 OAuth 提供程序。

服务和 OAuth 提供者之间的所有交互都归结为两个请求:首先,请求配置 /.well-known/openid-configuration,然后在配置中的地址请求公钥 (JWKS)。 所有这些都是静态内容。

:我们的测试 OAuth 提供程序是一个静态内容服务器及其上的两个文件。 令牌生成一次并提交给 Git。

SignalR 测试的特点

Postman 不支持 websocket。 创建了一个特殊工具来测试 SignalR。

SignalR 客户端不仅仅是一个浏览器。 .NET Core 下有一个客户端库。 用 .NET Core 编写的客户端建立连接、进行身份验证并等待特定的消息序列。 如果收到意外消息或连接丢失,则客户端退出并返回代码 1。如果收到最后一条预期消息,则客户端退出并返回代码 0。

纽曼与客户同时工作。 启动多个客户端来检查消息是否已传递给每个需要它们的人。

Docker 中微服务的自动化测试以实现持续集成

要运行多个客户端,请使用该选项 - 规模 在 docker-compose 命令行上。

在运行之前,Postman 脚本会等待所有客户端建立连接。
我们已经遇到过等待连接的问题。 但是有服务器,这里是客户端。 需要采取不同的方法。

:容器中的客户端使用该机制 健康检查通知主机上的脚本其状态。 一旦建立连接,客户端就会在特定路径(例如 /healthcheck)创建一个文件。 docker 文件中的 HealthCheck 脚本如下所示:

HEALTHCHECK --interval=3s CMD if [ ! -e /healthcheck ]; then false; fi

团队 码头工人检查 显示容器的正常状态、健康状态和退出代码。

Newman 完成后,脚本检查客户端的所有容器是否已终止,代码为 0。

幸福存在

克服了上述困难后,我们有了一套稳定的运行测试。 在测试中,每个服务作为一个单元运行,与数据库和 Amazon LocalStack 交互。

这些测试可保护 30 多名开发人员组成的团队免受 10 多个微服务的复杂交互和频繁部署的应用程序中的错误的影响。

来源: habr.com

添加评论