14、软件测试、持续交付与监控全解析

软件测试、持续交付与监控全解析

测试相关知识
  1. 测试规模
    • 测试规模反映了运行测试所需的计算资源,如节点数量,通常取决于测试运行环境的真实程度。测试的范围和规模虽相关,但概念不同,区分它们很有帮助。
    • 小测试在单个进程中运行,不进行任何阻塞调用或 I/O 操作,速度快、确定性强,间歇性失败的可能性很小。
    • 中级测试在单个节点上运行,执行本地 I/O 操作,如从磁盘读取数据或对本地主机进行网络调用,这会引入更多延迟和不确定性,增加间歇性失败的可能性。
    • 大测试需要多个节点来运行,引入更多的不确定性和更长的延迟。测试规模越大,运行时间越长,越不稳定。因此,应尽可能为给定行为编写最小的测试。
  2. 测试替身
    • 可以使用测试替身来代替真实依赖,以减小测试规模,使其更快且更不易出现间歇性失败。常见的测试替身类型有:
      • 假对象(Fake) :是接口的轻量级实现,行为与真实对象相似,例如内存数据库就是一个假对象。
      • 桩对象(Stub) :是一个无论传入什么参数都返回相同值的函数。
      • 模拟对象(Mock) :对其调用方式有期望,用于测试对象之间的交互。
    • 测试替身的问题在于它们与真实实现的行为细节可能不太相似,相似度越低,使用该替身的测试的有效性就越低。因此,当真实实现快速、确定且依赖较少时,应使用真实实现;否则,需要在替身的保真度和测试规模之间进行权衡。在无法使用真实实现时,优先使用由依赖项的开发者维护的假对象;桩对象和模拟对象是最后的选择,因为它们与实际实现的相似度最低,会使测试变得脆弱。
    • 对于集成测试,一个好的折衷方案是结合使用模拟对象和契约测试。契约测试定义了向外部依赖发送的请求以及期望从其接收的响应,测试使用该契约来模拟外部依赖。例如,REST API 的契约由 HTTP 请求和响应对组成。为确保契约不被破坏,外部依赖的测试套件使用相同的契约来模拟客户端并确保返回预期的响应。
  3. 实际测试考虑
    • 测试需要进行权衡。例如,测试服务的特定用户面向的 API 端点时,该服务与数据存储、其他团队拥有的内部服务和用于计费的第三方 API 进行通信。由于该端点不需要与内部服务通信,可使用模拟对象代替;数据存储有内存实现(假对象),可避免对远程数据存储进行网络调用;不能使用第三方计费 API 进行实际交易,但该 API 有提供测试环境的端点,可在不创建实际交易的情况下使用。这样可以在保持测试范围基本不变的情况下,大幅减小测试规模。
    • 对于一些对安全性要求极高的功能,如在欧洲受 GDPR 法律约束的用户数据清除功能,由于功能静默失败的风险太高,应使用在生产环境中运行的端到端测试,并使用实时服务而非测试替身。
持续交付与部署
  1. 概述
    • 当变更及其新引入的测试合并到存储库后,需要将其发布到生产环境。手动部署变更不频繁,多个变更可能会批量发布,这使得在部署失败时难以确定问题变更,且开发者需要监控部署情况以确保其正常运行或进行回滚,这是对工程时间的浪费。因此,需要自动化整个发布过程,通过持续交付和部署(CD)管道来安全、高效地发布变更。
  2. CD 管道阶段
    • 审查和构建 :代码变更需要经过四个阶段的管道才能发布到生产环境,即审查、构建、预生产部署和生产部署。首先,开发者提交拉取请求(PR)进行审查,此时需要对代码进行编译、静态分析和一系列测试,这些操作应在几分钟内完成。为提高测试速度和减少间歇性失败,此阶段运行的测试应足够小,可在单个进程或节点上运行,如单元测试,较大的测试应在管道的后续阶段运行。PR 需要团队成员审查和批准,审查者需验证变更是否正确、安全,可使用以下检查清单:
      • 变更是否包含必要的单元、集成和端到端测试?
      • 变更是否包含指标、日志和跟踪信息?
      • 变更是否会通过引入不兼容变更或达到服务限制而破坏生产环境?
      • 必要时,变更是否可以安全回滚?
      • 不仅代码变更需要经过审查,云资源模板、静态资产、端到端测试和配置文件等也应进行版本控制,并像代码一样处理。同一服务可以有多个 CD 管道,每个存储库对应一个,这些管道可以并行运行。变更合并到存储库的主分支后,CD 管道进入构建阶段,将存储库内容构建并打包成可部署的发布工件。
    • 预生产 :在此阶段,将工件部署并发布到合成的预生产环境。该环境虽缺乏生产环境的真实性,但可用于验证是否触发硬故障(如因缺少配置设置而在启动时出现空指针异常)以及端到端测试是否成功。由于向预生产环境发布新版本所需的时间比向生产环境发布短得多,因此可以更早地检测到错误。可以有多个预生产环境,从为每个工件从头创建并用于运行简单冒烟测试的环境,到类似于生产环境并接收其一小部分镜像请求的持久环境。例如,AWS 使用多个预生产环境(Alpha、Beta 和 Gamma)。发布到预生产环境的服务应调用其外部依赖的生产端点,以确保环境尽可能稳定;但可以调用同一团队拥有的其他服务的预生产端点。理想情况下,CD 管道应使用与生产环境相同的健康信号来评估工件在预生产环境中的健康状况,预生产环境使用的指标、警报和测试应与生产环境等效,以避免其健康覆盖不足。
    • 生产 :工件成功部署到预生产环境后,CD 管道进入最后阶段,将工件发布到生产环境。首先将其发布到少量生产实例,目的是尽快发现尚未检测到的问题,避免其在生产环境中造成广泛破坏。如果一切顺利且所有健康检查通过,将工件逐步发布到其余实例。在发布过程中,部分实例因部署无法处理流量,其余实例需要承担额外负载,因此需要有足够的容量来维持逐步发布。如果服务在多个区域可用,CD 管道应首先从低流量区域开始,以减少故障发布的影响;将其余区域的发布划分为多个阶段,以进一步降低风险。阶段越多,CD 管道将工件发布到生产环境所需的时间越长。可以在早期阶段成功完成并建立足够信心后提高发布速度。
    • 回滚 :在每个步骤之后,CD 管道需要评估已部署工件的健康状况,否则停止发布并进行回滚。可以使用多种健康信号来做出决策,如端到端测试结果、健康指标(如延迟和错误)、警报和健康端点。不仅要监控正在部署的服务的健康信号,还应监控上游和下游服务的健康状况,以检测部署的间接影响。管道应在步骤之间留出足够的时间(烘焙时间),以确保操作成功,因为有些问题可能在一段时间后才会出现。为加快发布速度,可以在每个步骤成功且信心增强后减少烘焙时间。当健康信号报告性能下降时,CD 管道停止。此时,它可以自动回滚工件,或触发警报通知值班工程师,由工程师决定是否需要回滚。根据工程师的输入,CD 管道可以重试失败的阶段,或完全回滚发布。操作员也可以停止管道,等待带有热修复的新工件进行前滚。由于前滚比回滚风险大得多,因此引入的任何变更都应始终保持向后兼容。为安全地引入不兼容变更,需要将其分解为多个向后兼容的变更。例如,生产者和消费者服务之间的消息传递模式需要进行不兼容更改时,可以将其分解为三个较小的变更:
      • 准备变更 :修改消费者以支持新旧消息传递格式。
      • 激活变更 :修改生产者以使用新格式编写消息。
      • 清理变更 :消费者完全停止支持旧消息传递格式,此变更应在确保激活变更不需要回滚后再发布。可以使用预生产环境中的自动化升级 - 降级测试来验证变更是否可以安全回滚。

以下是 CD 管道流程的 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([提交拉取请求]):::startend --> B(审查和构建):::process
    B --> C(预生产部署):::process
    C --> D{健康检查通过?}:::decision
    D -->|是| E(生产部署 - 少量实例):::process
    D -->|否| F(回滚):::process
    E --> G{健康检查通过?}:::decision
    G -->|是| H(逐步发布到其余实例):::process
    G -->|否| F
    H --> I([发布完成]):::startend
监控
  1. 监控概述
    • 监控主要用于检测影响生产环境中用户的故障,并触发通知(警报)发送给负责缓解问题的人工操作员。另一个重要用途是通过仪表板提供系统健康状况的高级概述。早期,监控主要采用黑盒方法,仅报告服务是否正常运行,对内部情况了解有限。随着时间的推移,它演变为白盒方法,开发者开始在代码中插入应用程序级别的测量代码,以确定特定功能是否按预期工作。黑盒监控仍然用于监控外部依赖项,如第三方 API,并验证用户从外部对服务性能和健康状况的感知。常见的方法是定期运行脚本,向外部 API 端点发送测试请求,监控请求所需时间和是否成功。这些脚本部署在应用程序用户所在的相同区域,访问相同的端点,能够发现应用程序内部不可见的问题,如连接问题,也有助于检测用户不常使用的 API 的问题。黑盒监控擅长检测故障症状,而白盒监控有助于在用户受到影响之前识别已知硬故障模式的根本原因。一般来说,如果无法通过设计避免硬故障模式,就应该为其添加监控。系统运行时间越长,就越能了解其可能的故障方式和需要监控的内容。
  2. 指标
    • 指标是在一段时间内测量的信息的数值表示,以时间序列形式呈现,如服务处理的请求数量。从概念上讲,指标是一个样本列表,每个样本由一个浮点数和一个时间戳表示。现代监控系统允许为指标添加一组键值对标签,增加指标的维度。本质上,每个不同的标签组合都是一个不同的指标,这对于现代服务非常必要,因为每个指标可能关联大量元数据,如数据中心、集群、节点、容器、服务等。高基数指标便于对数据进行切片和分析,避免了为每个标签组合手动创建指标的成本。
    • 服务应发出关于其负载、内部状态以及下游服务依赖项的可用性和性能的指标。结合下游服务发出的指标,操作员可以快速识别问题。这需要开发者进行明确的代码更改,并有意地在代码中插入测量代码。例如,以下是一个虚构的 HTTP 处理程序,返回资源:
def get_resource(id):
    resource = self._cache.get(id)
    # in-process cache
    # Is the id valid?
    # Was there a cache hit?
    # How long has the resource been in the cache?
    if resource is not None:
        return resource
    resource = self._repository.get(id)
    # Did the remote call fail, and if so, why?
    # Did the remote call timeout?
    # How long did the call take?
    self._cache[id] = resource
    # What's the size of the cache?
    return resource
    # How long did it take for the handler to run?
- 记录服务未能处理的请求数量的一种方法是基于事件的方法。每当服务实例未能处理请求时,它会在事件中向本地遥测代理报告失败计数为 1,例如:
{
    "failureCount": 1,
    "serviceRegion": "EastUs2",
    "timestamp": 1614438079
}
- 代理将这些事件批量处理,并定期将它们发送到远程遥测服务,该服务将它们持久化到专门的事件日志数据存储中。但这种方法成本较高,因为后端的负载会随着摄入的事件数量增加而增加,而且在查询时聚合事件也很昂贵。由于指标是时间序列,可以使用数学工具对其进行建模和操作。可以在预定义的时间段(如 1 秒、5 分钟、1 小时等)对时间序列的样本进行预聚合,并使用汇总统计信息(如总和、平均值或百分位数)表示。例如,遥测后端可以在摄入时对指标进行一个或多个时间段的预聚合。在查询时,选择满足查询的最佳时间段的预聚合指标。

以下是监控指标相关的表格:
| 指标类型 | 描述 | 示例 |
| ---- | ---- | ---- |
| 负载指标 | 反映服务的负载情况 | 处理的请求数量、并发连接数 |
| 内部状态指标 | 展示服务的内部状态 | 缓存大小、队列长度 |
| 下游服务依赖指标 | 体现下游服务的可用性和性能 | 响应时间、错误率 |

软件测试、持续交付与监控全解析

监控(续)
  1. 指标聚合与成本优化
    • 基于事件的指标记录方式成本高昂,因为后端负载随事件摄入数量增加,且查询时聚合事件困难。而指标作为时间序列,可利用数学工具进行建模和操作。
    • 具体操作是对时间序列的样本按预定义时间段(如 1 秒、5 分钟、1 小时等)进行预聚合,用总和、平均值或百分位数等汇总统计信息表示。例如,遥测后端在摄入指标时,可按一个或多个时间段进行预聚合。
    • 以记录服务失败请求数为例,若按每小时进行聚合,每个服务区域会有一个 failureCount 指标,每小时有一个样本,如:
"00:00", 561,
"01:00", 42,
"02:00", 61,
...
- 后端可创建不同时间段的多个预聚合。查询时,选择满足查询的最佳时间段的预聚合指标,以此降低查询成本。
监控方法对比
监控方法 适用场景 优点 缺点
黑盒监控 监控外部依赖,如第三方 API;验证用户对服务性能和健康的外部感知 能检测应用内部不可见问题,如连接问题;可发现用户不常使用 API 的问题 难以深入了解服务内部情况
白盒监控 识别已知硬故障模式的根本原因 能在用户受影响前定位问题根源 需要开发者在代码中插入测量代码,实现成本较高
持续交付与部署的关键要点总结
  • 自动化的重要性 :手动部署变更存在诸多弊端,如变更批量发布导致问题定位困难,开发者需耗费大量时间监控部署。而自动化的持续交付和部署(CD)管道能安全、高效地发布变更,让开发者可专注于其他任务。
  • CD 管道各阶段要点
    1. 审查和构建 :拉取请求审查时,编译、静态分析和测试应快速完成。此阶段测试宜小,如单元测试。审查者需依据检查清单验证变更安全性,且不仅代码,云资源模板等也需版本控制并审查。变更合并后进入构建阶段,生成可部署工件。
    2. 预生产 :将工件部署到预生产环境,虽缺乏生产环境真实性,但可提前发现硬故障和端到端测试问题。可设置多个预生产环境,服务调用外部依赖的生产端点保证环境稳定,使用与生产相同的健康信号评估工件。
    3. 生产 :先向少量生产实例发布工件,发现潜在问题。若健康检查通过,逐步发布到其余实例。多区域服务从低流量区域开始,分阶段发布降低风险,可在建立信心后提高发布速度。
    4. 回滚 :各步骤后评估工件健康状况,依据多种健康信号决策是否回滚。不仅监控目标服务,还需关注上下游服务。设置烘焙时间确保操作成功,可根据情况调整。健康信号异常时,可自动回滚或通知工程师决策,引入变更应尽量保持向后兼容,不兼容变更需分解为多个兼容变更。
测试策略选择建议
  • 测试规模与替身选择
    • 优先编写小测试,因其速度快、确定性强。当测试规模需增大时,考虑使用测试替身减小规模。
    • 真实实现快速、确定且依赖少,优先使用真实实现。否则,权衡测试替身的保真度和测试规模。优先选择依赖项开发者维护的假对象,桩对象和模拟对象作为最后选择。
  • 不同功能的测试策略
    • 对于普通 API 端点测试,合理使用模拟对象和假对象,在保持测试范围的同时减小规模。
    • 对于安全性要求极高的功能,如受法律约束的用户数据清除功能,采用在生产环境运行的端到端测试,使用实时服务。
综合流程梳理

下面是一个涵盖测试、持续交付与部署、监控的综合 mermaid 流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([代码开发]):::startend --> B(测试):::process
    B --> C{测试通过?}:::decision
    C -->|是| D(提交拉取请求):::process
    C -->|否| A
    D --> E(审查和构建):::process
    E --> F(预生产部署):::process
    F --> G{预生产健康检查通过?}:::decision
    G -->|是| H(生产部署 - 少量实例):::process
    G -->|否| I(回滚):::process
    H --> J{生产健康检查通过?}:::decision
    J -->|是| K(逐步发布到其余实例):::process
    J -->|否| I
    K --> L([发布完成]):::startend
    M(监控):::process --> N{发现问题?}:::decision
    N -->|是| I
    N -->|否| K

通过上述对软件测试、持续交付与部署以及监控的全面解析,我们了解了各个环节的关键知识和操作要点。在实际的软件开发过程中,合理运用这些方法和策略,能够提高软件的质量和开发效率,确保软件稳定、安全地运行在生产环境中。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值