跳过主要内容

CI/CD集成测试:实用管道指南

学习如何设计可靠的CI/CD集成测试管道,使用并行化、门控和可观察性最佳实践。

CI/CD集成测试:实用管道指南

支付服务的更改可能会通过数千个单元测试而通过,但仍然会在生产中破坏,因为付款服务的API根据idempotency key的解释与服务期望的解释不同。失败可能会在提交通过快速管道的快速部分后很长时间才被发现,直到每晚的集成作业到达接口。

那就是CI/CD集成测试的运营问题 CI/CD 整合测试. 单元测试证明孤立逻辑行为正确。整合测试提供了证据,证明组件、服务、模式、队列和外部依赖项仍然一致。在现代交付管道中,这些证据应该影响排查决策,而不是成为开发人员习惯忽视的缓慢检查。

CI/CD 已成为主流软件交付模型。 2024 年 mabl 的 DevOps 测试报告 说全球组织中几乎 90% 的组织 优先考虑 DevOps 转型,而只有 50% 测试人员参与定义和维护 CI/CD 流程,而只有 10% 组织报告他们不部署 CI/CD。

实践结果很明显:整合验证现在必须在交付规模上运作。

为什么集成测试是CI/CD的控制点

集成测试是释放风险的具体体现。一个单元测试可以确认一个支付处理器正确格式化了一个幂等性密钥,但只有集成测试才能证明处理器、HTTP客户端、账单API、持久层和重试行为在一起运作时是否一致。

因此,集成测试阶段是CI/CD管道中的自然控制点。一个提交不应仅因为其单元测试套件是绿色的而被认为是可发布的。它应该通过产生可信的证据来证明它改变的接口在生产环境中仍然可用。

集成测试如何在CI/CD管道中起到关键质量控制点的作用的图表。

因为频繁的集成没有自动验证,仅仅会将缺陷推进得更快。CI/CD改进方法的系统性审查,指出重复出现的优先事项包括减少构建和测试时间、提高结果可见性、支持持续测试、检测故障以及提高部署可靠性。同一研究记录中的另一项审查,定义了持续集成是指频繁的code集成,通过自动化的构建来验证,包括测试,以便快速检测缺陷。

构建控制点

首先根据决策支持的分类来分类集成测试:

  • 阻塞测试 保护关键合同并在合并或推送前同步运行。
  • 隔离测试 保持可见并继续产生证据,但在团队调查不稳定性时不阻止交付。
  • 异步测试 执行更广泛的工作流程、完整的阶段组合或昂贵的基础设施,直到提交已通过快速门控。

这比争论每个测试是否应该“在CI中”更有用。正确的问题是,测试是否可靠且价值足够,以影响特定推送决策。

实用规则: 在稳定的集成证据上门控,而不是在集成套件的存在上门控。

根据这一规则,实现的其余部分遵循。使用生产环境依赖项在接口出现故障时,并行化独立检查,测量假失败,并为隔离和促进定义明确条件。团队可以连接此工作到更广泛的交付实践中,也可以查看 持续集成的好处.

集成测试在单元测试和端到端测试之间的位置

测试金字塔是一个成本模型,而不是一个rigid的法律。单元测试是因为它们隔离了一个函数、类或模块而快。这种速度使它们成为即时反馈的理想选择,但模拟可以掩盖系统缝隙中发生的故障,例如序列化差异、数据库约束、身份验证配置或队列行为。

端到端测试占据了相反的位置。它们在整个堆栈上演练用户旅程,这使它们对于高风险工作流程非常有价值。它们也跨越浏览器、网络、服务和基础设施边界,因此诊断和稳定性变得更加困难。如果每次合并都等待整个端到端estate,开发人员会收到一个慢的信号,通常告诉他们比一个专注的API级别集成测试更少。

集成测试占据了中间地带。它们使用真实或接近真实的依赖项来验证服务之间的行为,而不需要完整的UI编排。该层可以覆盖HTTP和gRPC调用、队列发布、数据库迁移、缓存交互和schema兼容性。

三种有用的集成模式

在 Testcontainers 中进行的组件测试 启动应用组件及其依赖项,例如 PostgreSQL、Redis 或 Kafka。这一模式在涉及持久性、序列化、事务或代理语义的缺陷风险时,值得为其设置成本。它为测试提供了一个真实的依赖项,同时保持测试边界狭窄。

使用 Pact 或 schema registry 的跨服务合同测试 关注消费者和提供者的协议。它们是团队独立发布服务并需要快速反馈以检测协议漂移的强大选择。合同测试不应替代行为集成测试,但它们可以防止不兼容的接口到达共享环境。

API 级别的测试 模式

运行时 环境一致性 最佳用途 在 Testcontainers 中进行的组件测试
targetLanguage 快速到中等 真实选择的依赖项 数据库、缓存、代理和应用组件行为
使用契约或模式注册表的消费者-提供者契约测试 快速 高接口一致性 API 和事件兼容性
API 测试 中等到慢 高系统一致性 网络路径、身份验证、路由和关键多服务工作流

保持同步合并套件的范围尽可能狭窄。一个实际目标是 在 10 分钟以内的合并提交然后将扩展场景转移到异步执行。希望对此进行更广泛基础的团队可以查看 什么是自动化测试但是,指导原则仍然简单:在它改变发布决策时,付费现实。

管理环境和数据以确保有信任的测试

一个运行在错误环境中的测试可以产生错误的自信。一个运行在不稳定的共享环境中的测试可以产生错误的失败。可靠的 CI/CD 集成测试 需要一个环境策略,使依赖边界显式并且数据状态可重现。

一个图表,展示了管理环境和数据的三个步骤,以确保有信任的软件集成测试。

从你可以有信任的依赖开始。Testcontainers 可以为每个作业提供可抛弃的 PostgreSQL、Redis 和 Kafka 实例。关键的好处不是 Docker 本身。它是对版本、配置、启动和清除的控制。

一个可重现的作业序列

使用一个固定的序列,而不是让测试 code 自由地改造其设置:

  1. 启动依赖项。 启动 PostgreSQL 容器,然后等待真正的健康检查,而不是假设正在运行的进程已经准备好接受流量。
  2. 应用迁移。 运行应用程序使用的相同迁移路径。不要创建手动维护的测试模式,它可能会与生产模式分离。
  3. 加载确定性的测试数据。 仅为场景加载所需的记录,并为每个任务提供隔离的数据,以便并行运行不会互相影响状态。
  4. 执行契约断言。 验证请求格式、响应行为、事件模式、状态转换和持久性结果。
  5. 清除所有内容。 销毁容器和临时卷,即使测试失败,也要确保后续任务不继承已损坏的状态。

90 秒的 PostgreSQL 启动可能是一个合理的成本,能够捕捉到迁移和事务缺陷。一个完整的测试环境是一个不同的决策。它消耗更多的基础设施,引入更多的配置漂移,并增加了失败的源头数量。从最窄的忠实环境开始,然后在契约覆盖或生产事件表明较小的边界缺乏一个有意义的故障模式时才扩大它。

使用虚拟化工具

某些第三方系统不能在每个管道中安全地进行配置。WireMock、Mountebank和Hoverfly可以模拟这些依赖项,但模拟必须被视为一个维护的合同,而不是一个方便的逃避。一个只返回理想响应的模拟不会暴露身份验证过期、速率限制、 Payloads、超时处理或模式演变。

临时预览环境在风险依赖于多个部署服务一起工作时很有用。Docker Compose可以提供一个紧凑的本地和CI组合,而Kubernetes命名空间可以在部署行为本身需要测试时隔离pull请求环境。无论您使用哪种方法,都要将依赖项版本固定并记录每次运行时使用的配置。

机密需要与数据一样的纪律。将凭据存储在测试fixture外部并通过平台的受保护机制旋转访问权限。 Capgo guide to managing secrets in CI/CD pipelines 可以帮助团队在环境生命周期上达成共识的短视觉教程:

管道示例

Pipeline Examples for GitHub Actions, GitLab CI, and Jenkins

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

Pin action and dependency versions to reduce environment drift. GitHub Actions exposes parallelism primarily through matrices and separate jobs, so use a matrix only when each shard has isolated data and predictable duration.

GitLab CI

GitLab CI can separate preparation, test execution, and reporting. Child pipelines are useful when a large repository owns multiple services with different integration environments. Artifacts preserve JUnit results even when the test job fails.

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

Use GitLab child pipelines when service ownership or deployment topology makes a single monolithic file difficult to maintain. Keep the parent pipeline responsible for the promotion decision, otherwise an individual child can pass while the overall release signal remains ambiguous.

Jenkins

Jenkins is useful when teams need self-hosted agents or unusual network access. A declarative Jenkinsfile can assign Docker-based agents and run suites in parallel, but the team owns plugin, controller, agent, and image maintenance.

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'
    }
  }
}
Feature GitHub Actions GitLab CI Jenkins
Parallel execution 矩阵作业和单独作业 parallel 和矩阵作业 声明式 parallel 阶段和分布式代理
环境控制 托管或自托管的运行器 托管或自托管的运行器 自我管理的控制器和代理
结果可见性 工件和检查注释 JUnit 报告和工件 JUnit 发布者和构建历史
版本可重现性 固定动作、图像和设置版本 固定图像和运行器配置 固定代理图像和控制插件
最佳运营适配 GitHub-中心化仓库 GitLab中心化交付 需要大量自主化定制的团队

对于已经使用GitHub的团队来说 使用GitHub Actions的自动化构建和发布 可以提供一个有用的部署模式。重要的设计选择仍然是所有三个运行器中的一致的:不要让每个昂贵的测试成为同步合并阻塞器。

可靠性策略:解决易碎的集成测试

重试对于诊断有用,但 blanket 重试是一种糟糕的可靠性策略。它们可以将真实缺陷转化为绿色构建,隐藏环境不稳定性,并使通过率仪表板看起来比管道实际情况更健康。

问题的规模在发布的工程数据中是可见的。Google 报告大约 16% 的测试 有某些波动性,约 1.5% 的所有测试运行 返回波动结果,而另一项研究发现 Google 测试故障 的 4.56% 由波动测试引起的,正如 AWS 持续集成和交付测试指南 中总结的那样。Google 数据还显示约84% 的通过到失败的 CI 转换 是波动而不是真实错误,而 Microsoft 项目报告的约 84% 的通过到失败的 CI 转换 4.6% 的不稳定测试 根据Panto的不稳定测试统计分析报告的一项研究

软件开发管道中管理不稳定集成测试的可靠性策略流程图

在改变政策之前,先进行测量

将不稳定性跟踪为:

不稳定失败 ÷ 总执行次数 × 100

使用一个 7 到 30 天的时间窗口,并根据套件、测试、运行器镜像、依赖项和环境进行计算。一个只在一个运行器上失败的测试是一个不同的修复问题,而一个在每个环境中都失败的测试是一个不同的修复问题。

使用这些分类进行分类:

  • 产品失败: 阻止相关门户并修复 code 或合同。
  • 环境故障: 修复健康检查、资源限制、网络或依赖项设置。
  • 测试故障: 修复断言顺序、共享状态、时间、清理或 fixture 设计。
  • 未分类不稳定性: 暂时隔离,但分配负责人和过期日期。

移除常见原因

共享可变状态会导致顺序依赖。为每个作业提供隔离的模式、唯一标识符或事务回滚边界。异步系统需要基于条件的轮询,使用有界超时,而不是任意睡眠。将时钟注入到过期逻辑中,等待容器健康状态,而不是进程启动。

分片可以减少墙钟时间,但它并不能解决一个坏的测试。独立运行分片,保留每个分片的日志,并只重新运行失败的测试或分片以进行诊断。不要因为一个集成检查出现环境故障就重新运行整个管道。

一个测试只有在满足定义的稳定性政策后才能从隔离中释放,例如连续的绿色执行结果在它将要运行的环境中。具体阈值应该由团队选择并记录在政策中。重要的是,通过观察到的稳定性而不是删除隔离标签来获得推进权。

门控策略和部署推进规则

质量门应该回答一个问题:这个 artifact 有足够的可信证据可以移动到下一个环境吗?它不应该成为组织累积的每个测试的垃圾场。

强制执行差距是一个警告。 2025 年的一项调查被引用为 Testkube 的 CI/CD 测试分析 报告指出 72% 的组织 已经在 CI/CD 中自动化 QA 26% 只有

强制执行质量门控,当测试失败时阻止部署。

不加强的采用会将发布决策留给记忆、紧迫感或手动清单。

使用阶段特定的门控 在提交时,阻止快速单元测试和保护更改或关键接口的稳定集成子集。 在阶段中,添加更广泛的组合环境检查和部署配置验证。 在生产之前,要求批准的工件、成功的保护环境验证和明确的批准,根据您的风险模型要求。 阶段 通过率要求允许隔离批准
__CAPGO_KEEP_0__或__CAPGO_KEEP_1__ __CAPGO_KEEP_0__通过 __CAPGO_KEEP_0__外部测试 __CAPGO_KEEP_0__保护
__CAPGO_KEEP_0__ __CAPGO_KEEP_0__ __CAPGO_KEEP_0__ __CAPGO_KEEP_0__
__CAPGO_KEEP_0__ __CAPGO_KEEP_0__ __CAPGO_KEEP_0__ __CAPGO_KEEP_0__
生产发布 所有所需门控都通过审计证据 没有阻塞路径隔离 根据政策需要,需要明确的批准

不要混淆“通过率”与原始百分比目标。一个套件可以显示出高的通过率,而在重复失败的确切支付或身份验证路径上。通过行为定义关键路径覆盖,然后要求这些检查在确定性地通过。

使绕过措施可见

配置 branch 保护,使阻塞检查失败阻止合并。配置环境保护规则,使发布需要预期的批准和 artifact。人工干预可能在事故期间必要,但它应该需要明确的理由、指定的批准者、时间戳和跟进票。

隔离不是忽视失败的许可。它是一种控制的方式,保持交付流动,同时保留证据。如果隔离的测试覆盖了受保护的发布路径,政策应该要么恢复它为阻塞状态,要么在发布前需要风险决策。

采用清单和长期信任的可观察性

团队通常在集成测试中失败的方式有两种。他们要么从一个巨大的套件开始,导致每次合并都很慢,要么创建一个使用假设非常广泛的快速套件,从而永远不会测试生产暴露的失败。分阶段的采用计划避免了两种陷阱。

实施 CI/CD 集成测试的三阶段清单,涵盖政策门控、先进环境和可观察性

第一阶段,确保信号可靠

从你已经有的管道开始:

  • 设置第一道门槛: 确定关键服务合同并仅阻止稳定的检查。
  • 定义重试行为: 允许针对性重跑以诊断,不要静默重试,否则会将失败转换为成功。
  • 启用并行化: 根据边界域、依赖或分片将测试套件分隔开,使用隔离的数据进行每个作业。
  • 发布测试结果: 存储JUnit报告、日志、容器状态和每次运行的失败分类。

在这个阶段,不要追求最大覆盖率。首先移除最昂贵的噪音来源,然后使用恢复的开发者信任来扩展真正的依赖关系覆盖率。

第二阶段,提高环境真实性

添加测试容器来重现依赖项的成本低。引入服务所有权分布的契约测试。使用临时环境来路由、部署配置或跨服务行为无法在紧凑的作业中准确表示时。

生产中断暴露了迁移不匹配时,应基于证据的选择。若API在独立部署的服务之间漂移,应添加消费者-提供者契约。如果失败依赖于Kubernetes配置,应在临时命名空间中运行相关检查,而不是假装模拟证明相同的事实。

阶段三,连接可观察性到故障排除

捕获 测试持续时间百分位数环境配置差异、锁定依赖项版本、重试通过率以及通过OpenTelemetry相关的应用程序日志和跟踪。有用的仪表板应该显示:

  • 抖动率烧结: 哪些套件和测试变得越来越不确定。
  • 平均到绿色时间: 失败的管道在恢复之前花费的时间。
  • 失败模式集群: 是否失败聚集在code、环境、依赖项或测试设计上。
  • 隔离库存: 隔离的测试、所有者和必须进行的审查时间。
  • 推广证据: 每个环境变更之前通过的门控。

这是应用可观察性的运作含义。 仪表板不是回顾性的档案。它应该改变今天关于是否阻塞、异步等待或返回隔离的决策。保持一个团队政策文件,包含阻塞子集、隔离规则、环境所有权、重试次数限制、工件要求和覆盖过程。每当失败数据发生变化时,进行审查,不仅仅是当重大事件迫使对话时。

__CAPGO_KEEP_0__ 提供了 CI/CD 集成,用于自动化签名的实时更新包上传,支持特征分支、测试环境和生产环境的工作流程。如果您的 CapacitorJS 或 Electron 团队需要将集成测试证据与受控的移动交付联系起来,请访问

Capgo 来评估 Capgo 和发布工作流程。 to evaluate the API and rollout workflow.

实时更新Capacitor应用

当一个web层bug在live状态时,通过Capgo将修复推送给用户,而不是等待几天的app store审批。用户在后台接收更新,而native层的变化仍然在正常的审批路径中。

来自马丁的专业支持

立即开始

最新博客

Capgo为您提供了创建真正专业的移动应用所需的最佳见解。