支付服务的更改可能会通过数千个单元测试而通过,但仍然会在生产中破坏,因为付款服务的API根据idempotency key的解释与服务期望不同。失败可能会在提交通过管道的快速部分后很长时间才被发现,直到每晚的集成作业到达接口。
那就是CI/CD集成测试管道的运营问题。 CI/CD 整合测试. 单元测试证明孤立逻辑行为正确。整合测试提供了证据,证明组件、服务、模式、队列和外部依赖项仍然一致。在现代交付管道中,这些证据应该影响排查决策,而不是成为开发人员习惯忽视的缓慢检查。
CI/CD 已成为主流软件交付模型。 2024 年 mabl 的 DevOps 测试报告 说全球组织的 几乎 90% 的组织 优先考虑 DevOps 转型,而仅有 50% 的测试人员参与定义和维护 CI/CD 流程,而 10% 的组织表示他们根本不部署 CI/CD。实践结果很明显:整合验证现在必须在交付规模上运作。
目录
- 为什么整合测试是 CI/CD 的控制点
- 在单元测试和端到端测试之间的集成测试
- 管理环境和数据以确保测试的可靠性
- Pipeline GitHub 的示例,GitLab CI 和 Jenkins
- CI/CD 集成测试的可靠性策略
- 管控策略和发布促进规则
- 采用清单和长期信任的可观察性
CI/CD中的集成测试是控制点
集成测试是释放风险的具体体现。一个单元测试可以确认一个支付处理器正确格式化了一个幂等性键,但只有集成测试才能证明处理器、HTTP客户端、billing API、持久层和重试行为在一起工作时是否一致。
集成测试阶段是CI/CD管道中的自然控制点。一个提交不应仅因为其单元测试套件是绿色的而被认为是可发布的。它应该通过产生可信的证据来证明它改变的接口在生产环境中仍然可用。

因为频繁的集成没有自动验证,仅仅会将缺陷推进更快。CI/CD改进方法的系统性审查,指出重复出现的优先事项包括减少构建和测试时间、提高结果可见性、支持持续测试、检测故障和提高部署可靠性。同一研究记录中的另一项审查定义了持续集成,围绕着频繁的code集成进行验证,通过自动化构建来包含测试,从而可以快速检测缺陷。
构建控制点
首先根据决策支持的分类来分类集成测试:
- 阻塞测试 保护关键合同并在合并或推广之前同步运行。
- 隔离测试 保持可见性并继续产生证据,但不要阻止交付,直到团队调查不稳定性。
- 异步测试 执行更广泛的工作流程、完整的分阶段组合或昂贵的基础设施,直到提交已通过快速门控。
这比争论每个测试是否应该“在CI中”更有用。正确的问题是,测试是否可靠且价值足够,以影响特定推广决策。
实用规则: 门控稳定的集成证据,而不是集成套件的存在。
其他实现步骤遵循该规则。使用生产环境依赖项在接口可以失败的地方, 并行化独立检查,测量假失败, 并定义明确的隔离和推广条件。团队希望将此工作与更广泛的交付实践联系起来,也可以查看 持续集成的好处.
单元测试和端到端测试之间的集成测试位置
测试金字塔是一种成本模型,而不是rigid的法律。单元测试速度快,因为它们隔离了一个函数、类或模块。这种速度使它们成为即时反馈的理想选择,但mocks可以掩盖系统缝隙中发生的具体失败,例如序列化差异、数据库约束、身份验证配置或队列行为。
端到端测试占据了相反的位置。它们在整个堆栈上演练用户旅程,使它们对于高风险工作流程非常有价值。它们还跨越浏览器、网络、服务和基础设施边界,因此诊断和稳定性变得更加困难。如果每次合并都等待整个端到端estate,开发人员将获得一个慢速信号,通常告诉他们比专注于API级别的集成测试更少。
集成测试占据了中间地带。它们使用真实或接近真实的依赖项来验证服务之间的行为,而不需要完整的UI编排。该层可以覆盖HTTP和gRPC调用、队列发布、数据库迁移、缓存交互和schema兼容性。
三种有用的集成模式
使用 Testcontainers 进行内存组件测试 启动应用组件及其依赖项,例如 PostgreSQL、Redis 或 Kafka。这一模式在涉及持久性、序列化、事务或代理语义的缺陷风险时值得花费设置成本。它为测试提供了一个真实的依赖项,同时保持测试边界狭窄。
使用 Pact 或 schema registry 进行跨服务契约测试 关注消费者和提供者的协议。它们是团队独立发布服务并需要快速反馈以检测契约漂移的强大选择。契约测试不应替代行为集成测试,但它们可以防止不兼容的接口到达共享环境。
API-级别测试 模式
| 运行时 | 环境一致性 | 最佳用途 | 使用 Testcontainers 进行内存组件测试 |
|---|---|---|---|
| targetLanguage | 快速到中等 | 真实选定的依赖项 | 数据库、缓存、代理和应用组件行为 |
| 使用Pact或schema注册表的消费者-提供者契约测试 | 快速 | 高接口一致性 | API和事件兼容性 |
| API测试 | 中等到慢 | 高系统一致性 | 网络路径、身份验证、路由和关键多服务工作流 |
保持同步合并套件的狭窄。一个实际目标是保持集成路径 在每次合并提交后少于 10 分钟然后将繁琐的场景转移到异步执行中。希望对此进行更广泛基础的团队可以查看 什么是自动化测试但是,指导原则仍然简单:在改变发布决策的地方付费实用主义
管理环境和数据以确保可靠的测试
CI/CD 集成测试需要一个环境策略,使依赖边界明确,数据状态可复现 一个图表,展示了管理环境和数据的三个步骤,以确保软件集成测试的可靠性 从你可以可靠运行的依赖开始。Testcontainers 可以为每个作业提供可抛弃的 PostgreSQL、Redis 和 Kafka 实例。关键的好处不是 Docker 本身,而是对版本、配置、启动和停止的控制

使用固定序列而不是让测试 __CAPGO_KEEP_0__ 自由发挥其设置:
可靠的 CI/CD 集成测试
Use a fixed sequence rather than letting test code improvise its setup:
- 启动依赖项。 启动 PostgreSQL 容器,然后等待真正的健康检查,而不是假设正在运行的进程已经准备好接受流量。
- 应用迁移。 运行应用程序使用的相同迁移路径。不要创建手动维护的测试模式,它可能会与生产模式分离。
- 加载确定性的测试数据。 仅为场景加载所需的记录,并为每个任务提供隔离的数据,以便并行运行不会互相影响状态。
- 执行契约断言。 验证请求格式、响应行为、事件模式、状态转换和持久性结果。
- 清除所有内容。 即使测试失败,也销毁容器和临时卷,以便后续任务不继承已损坏的状态。
当它捕捉迁移和事务缺陷时,90 秒的 PostgreSQL 启动可能是一个合理的成本。一个完整的测试集群是一个不同的决策。它消耗更多的基础设施,引入更多的配置漂移,并增加了失败的源头数量。从最窄的忠实环境开始,然后在契约覆盖或生产事件表明较小的边界缺乏有意义的故障模式时,扩大它。
使用虚拟化工具
某些第三方系统无法在每个管道中安全地进行配置。WireMock、Mountebank和Hoverfly可以模拟这些依赖项,但模拟必须被视为一个维护的合同,而不是一个方便的逃避。一个只返回理想响应的模拟不会暴露身份验证过期、速率限制、 Payloads、超时处理或模式演变。
临时预览环境在风险依赖于多个部署服务一起工作时很有用。Docker Compose可以提供一个紧凑的本地和CI组合,而Kubernetes命名空间可以隔离pull请求环境,当部署行为本身需要测试时。无论您使用哪种方法,都要固定依赖项版本并记录每次运行使用的配置。
机密需要与数据一样的纪律。将凭据存储在测试fixture外部并通过平台的受保护机制旋转访问权限。 关于在CI/CD管道中管理机密的Capgo指南 一个短的可视化教程可以帮助团队在环境生命周期上达成一致:
Pipeline Examples for __CAPGO_KEEP_0__ Actions、GitLab CI和Jenkins
Pipeline Examples for GitHub Actions, GitLab CI, and Jenkins
__CAPGO_KEEP_0__ Actions
GitHub指南
A matrix works well when integration tests can be divided by domain or shard. Service containers keep dependencies close to the runner, while JUnit output gives pull requests and downstream systems a stable result format.
name: integration
on:
pull_request:
jobs:
integration:
strategy:
fail-fast: false
matrix:
suite: [billing, orders, notifications]
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: app_test
options: >-
--health-cmd "pg_isready -U postgres -d app_test"
--health-interval 5s
--health-timeout 5s
--health-retries 12
redis:
image: redis:7
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run test:integration, --suite=${{ matrix.suite }}
- if: always()
uses: actions/upload-artifact@v4
with:
name: junit-${{ matrix.suite }}
path: test-results/*.xml
固定版本的动作和依赖版本可以减少环境漂移。 GitHub Actions 通过矩阵和单独的作业主要暴露并行性,所以只在每个分片有隔离的数据和可预测的持续时间时才使用矩阵。
GitLab CI
GitLab CI 可以将准备、测试执行和报告分开。子管道在一个大型仓库拥有多个服务且有不同的集成环境时非常有用。Artifact 可以保留 JUnit 结果,即使测试作业失败。
stages:
- build
- integration
build-image:
stage: build
script:
- docker build --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
integration:
stage: integration
parallel:
matrix:
- SUITE: [billing, orders, notifications]
image: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
services:
- name: postgres:16
alias: postgres
- name: redis:7
alias: redis
script:
- ./scripts/migrate-test-db.sh
- npm run test:integration, --suite="$SUITE" --reporter=junit
artifacts:
when: always
reports:
junit: test-results/*.xml
在服务所有权或部署拓扑使单个 monolithic 文件难以维护时,使用 GitLab 子管道。否则,单个子管道可以通过而整个发布信号仍然模糊。
Jenkins
Jenkins 在团队需要自主的代理或不寻常的网络访问时非常有用。声明式 Jenkinsfile 可以分配 Docker 基于代理并并行运行套件,但团队需要维护插件、控制器、代理和镜像。
pipeline {
agent none
stages {
stage('Build') {
agent { docker 'node:22' }
steps {
sh 'npm ci'
sh 'npm run build'
stash name: 'build', includes: 'dist/**'
}
}
stage('Integration') {
parallel {
stage('Billing') {
agent { docker 'my-org/integration-runner:stable' }
steps {
unstash 'build'
sh './scripts/start-test-dependencies.sh'
sh 'npm run test:integration, --suite=billing'
}
}
stage('Orders') {
agent { docker 'my-org/integration-runner:stable' }
steps {
unstash 'build'
sh './scripts/start-test-dependencies.sh'
sh 'npm run test:integration, --suite=orders'
}
}
}
}
}
post {
always {
junit 'test-results/*.xml'
}
}
}
| 特性 | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| 并行执行 | 矩阵任务和单独任务 | parallel 和矩阵任务 |
声明式 parallel 阶段和分布式代理 |
| 环境控制 | 托管或自托管运行器 | 托管或自托管运行器 | 自管理控制器和代理 |
| 结果可见性 | 工件和检查注释 | JUnit 报告和工件 | JUnit 发布者和构建历史 |
| 版本可复现性 | 固定动作、图像和设置版本 | 固定图像和运行器配置 | 固定代理图像和控制插件 |
| 最佳运营适配 | GitHub-中心化仓库 | GitLab中心化交付 | 需要大量自主托管定制的团队 |
对于已经使用GitHub的团队 自动化构建和发布与GitHub Actions 可以提供一个有用的部署模式。重要的设计选择仍然是所有三个运行器中的一致的:不要让每个昂贵的测试成为同步合并阻塞器。
可靠性战略:如何解决易碎的集成测试
重试对于诊断有用,但 blanket 重试是一种糟糕的可靠性策略。它们可以将真实缺陷转化为绿色构建,隐藏环境不稳定性,并使通过率仪表板看起来比管道实际情况更健康。
问题的规模在发布的工程数据中是可见的。Google 报告大约 16% 的测试 有某些抖动和大约 1.5% 的所有测试运行 返回抖动结果,而另一项研究发现 4.56% 的 Google 测试故障 是由抖动测试引起的,如 AWS 持续集成和交付测试指南 中所述。Google 数据还显示大约84% 的通过到失败 CI 转换 是抖动而不是真实错误,微软项目报告大约 were flaky rather than true bugs, and Microsoft projects reported about 4.6% 的不稳定测试 根据Panto的不稳定测试统计分析报告的一项研究

在改变政策之前,进行测量
跟踪不稳定性如下:
不稳定失败 ÷ 总执行次数 × 100
使用一个 7 到 30 天的时间窗口,并根据套件、测试、运行器镜像、依赖项和环境计算它。一个只在一个运行器上失败的测试是一个不同的修复问题,而一个在每个环境中都失败的测试是一个不同的修复问题。
使用这些分类来进行分类:
- 产品失败: 阻止相关门户并修复 code 或合同。
- 环境故障: 修复健康检查、资源限制、网络或依赖设置。
- 测试故障: 修复断言顺序、共享状态、时间、清理或 fixture 设计。
- 未分类不稳定性: 暂时隔离,但分配负责人和过期日期。
移除常见原因
共享可变状态会导致顺序依赖。为每个作业提供隔离的模式、唯一标识符或事务回滚边界。异步系统需要基于条件的轮询,使用有界超时,而不是任意睡眠。将时钟注入到过期逻辑中,等待容器健康状态而不是进程启动。
分片可以减少墙钟时间,但它并不能解决一个坏的测试。独立运行分片,保留每个分片的日志,并只重新运行失败的测试或分片以进行诊断。不要重新运行整个管道,只因为一个集成检查出现了环境故障。
一个测试只有在满足定义的稳定性政策后才能从隔离中释放,例如连续的绿色执行结果在它将要运行的环境中。具体的阈值应该由团队选择并记录在政策中。重要的是,推进是通过观察到的稳定性而不是删除隔离标签而获得的。
门控策略和部署推进规则
质量门控应该回答一个问题:这个 artifact 有足够可信的证据可以移动到下一个环境吗?它不应该成为组织累积的每个测试的垃圾场。
严重性差距是一个警告。2025年的一项调查被 Testkube的CI/CD测试分析 报告指出 72%的组织 已经在CI/CD中自动化QA 26% 然而,只有
强制质量门控
阻止测试失败时的部署。没有强制措施,发布决策就依赖于记忆、紧迫感或手动清单。
| 使用阶段特定的门控 | 在提交时,阻止快速单元测试和保护更改或关键接口的稳定集成子集。在阶段中,添加更广泛的组合环境检查和部署配置验证。在生产之前,要求已批准的工件、成功的保护环境验证和明确的批准,根据您的风险模型要求。 | 阶段 | 通过率要求允许隔离批准 |
|---|---|---|---|
| 提交或拉取请求 | 所有阻塞检查通过 | 仅测试外部阻塞子集 | 自动合并保护 |
| 阶段推动 | 所有关键集成检查通过 | 仅允许与文档化的拥有者和风险审查 | 团队或服务拥有者 |
| 金丝雀或有限推出 | 推动检查和实时健康信号通过 | 无隔离测试可能覆盖受保护路径 | 应急或发布拥有者 |
| 生产发布 | 所有所需门控都通过审计证据 | 没有阻塞路径隔离 | 在政策要求时显式批准 |
不要混淆“通过率”与原始百分比目标。一个套件可以显示出高的通过率,而在重复失败的确切支付或身份验证路径上。通过行为定义关键路径覆盖,然后要求这些检查在确定性地通过。
使绕过可见
配置 branch 保护,以失败的阻塞检查防止合并。配置环境保护规则,以便推广需要预期的批准和 artifact。 在事件期间,人工干预可能是必要的,但它应该要求明确的原因、指定的批准者、时间戳和跟进票。
隔离不是忽视失败的许可。它是一种控制的方式来保持交付的流动,同时保留证据。如果隔离的测试覆盖了受保护的发布路径,政策应该要么恢复它为阻塞状态,要么在推广前要求风险决策。
采用清单和长期信任的可观察性
团队通常在集成测试中失败的方式有两种。他们要么从一个庞大的套件开始,导致每次合并都会很慢,要么创建一个使用假设非常广泛的快速套件,从而永远不会测试生产暴露的失败。分阶段的采用计划避免了两种陷阱。

第一阶段,确保信号可靠
从您已经有的管道开始:
- 设置第一道门槛: 确定关键服务合同并仅将稳定的检查设置为阻塞项。
- 定义重试行为: 允许针对诊断的重跑,不要静默重试,否则会将失败转换为成功。
- 启用并行化: 根据边界域、依赖关系或分片,将测试套件分隔开,使用隔离的数据进行每个作业。
- 发布测试结果: 存储JUnit报告、日志、容器状态和每次运行的失败分类。
在这个阶段,不要追求最大覆盖率。首先移除最昂贵的噪音来源,然后使用恢复的开发者信任来扩展真实依赖关系覆盖率。
第二阶段,提高环境真实性
添加测试容器以便于重现成本低的依赖项。引入服务所有权分布的契约测试。使用临时环境进行路由、部署配置或跨服务行为的测试,尤其是这些行为无法在紧凑的作业中准确表示时。
生产中断暴露了迁移不匹配时,应添加真实数据库路径。如果API在独立部署的服务之间漂移,应添加消费者-提供者契约。如果失败依赖于Kubernetes配置,应在临时命名空间中运行相关检查,而不是假装模拟证明相同的东西。
阶段三:连接可观察性到故障排除
捕获 测试持续时间百分位数环境配置差异、锁定依赖项版本、重试通过率以及通过OpenTelemetry关联的应用程序日志和跟踪。有用的仪表板应该显示:
- 抖动率烧结: 哪些套件和测试变得越来越不确定。
- 平均时间到绿色: 失败的管道在恢复之前花费的时间。
- 失败模式集群: 是否失败围绕code、环境、依赖项或测试设计进行群集。
- 隔离库存: 哪些测试被隔离,谁拥有它们,以及它们何时需要被审查。
- 推广证据: 每个环境变更之前通过的门控。
这是应用可观察性的运作含义。 仪表板不是一个回顾性的档案。它应该改变今天关于是否阻塞、异步等待或返回隔离的决定。保持一个团队政策文件,包含阻塞子集、隔离规则、环境拥有者、重试次数限制、工件要求以及覆盖过程。每当失败数据发生变化时,进行一次审查,不仅仅是在发生重大事件时强迫对话。
__CAPGO_KEEP_0__ 为 CI/CD 提供了自动化签名的实时更新包上传功能,支持特征 branch、分支和生产工作流。若您的 CapacitorJS 或 Electron 团队需要将集成测试证据连接到受控的移动交付中,请访问
Capgo 来评估 Capgo 和发布工作流。 to evaluate the API and rollout workflow.