mcpField
T
test-environments

test-environments

github.com/petrkindlmann测试与质量2221.3k 次安装6 天前更新
代码仓库

关于 test-environments

test-environments 是面向开发与 QA 团队的测试环境设计技能,帮助规划与生产关键部分一致、可隔离且可复现的环境。内容涵盖环境分层、容器化配置、依赖替身和环境一致性检查。

主要功能

  1. 01规划本地、CI、预览与暂存环境
  2. 02通过 Docker Compose 配置测试栈
  3. 03按依赖类型制定外部服务替身策略
  4. 04使用 MSW 在 HTTP 边界模拟 API
  5. 05提供环境一致性检查清单

适用场景

  1. 01减少暂存环境与生产环境差异导致的漏测
  2. 02为每个 PR 隔离预览环境
  3. 03避免 CI 测试调用真实第三方 API
  4. 04搭建可复现的本地与 CI 测试栈

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/petrkindlmann/qa-skills --skill test-environments

安装后在 Claude Code 中输入/test-environments确认已加载。

附带文件references/docker-compose.mdreferences/stubbing.md

详细介绍

在 SQLite 上通过的测试,到了生产环境的 Postgres 上可能会失败;共享的预发布机器会变成排队瓶颈,一次有问题的部署就会阻塞整个团队;未经模拟的 Stripe 调用会导致 CI 随机失败。本技能通过设计环境层级来避免这些问题:在关键处与生产环境保持一致、为每个 PR 隔离环境,并在 HTTP 边界模拟外部依赖。它会提供一个可运行的 `docker compose up` 本地/CI 技术栈、一份一致性检查清单,以及一套按依赖类型划分的模拟策略。

调研问题

先检查 .agents/qa-project-context.md——如果文件存在,就使用其中的信息,并跳过已经回答的问题。然后:

  1. 目前有多少个环境? 本地开发、CI、预发布、预览、生产?在设计所需环境前,先梳理现有环境。
  2. 应用是否容器化? 检查是否存在 Dockerfile、docker-compose.yml 或 compose.yaml。如果是,多阶段构建目标和 Compose 配置都可直接使用;如果不是,这就是首要交付项。
  3. 如何填充测试数据? 手动 SQL、基于迁移、工厂库,还是生产快照?这决定了编写种子脚本是快速改进还是需要重写。
  4. 预发布环境与生产环境有多接近? 数据库引擎、队列、缓存、身份验证提供方、编排方式是否相同?每一处不一致都意味着预发布环境永远无法捕获一类错误。
  5. 外部依赖: 系统会调用多少个第三方 API?它们在非生产环境中是否经过模拟?未经模拟的第三方依赖是 CI 不稳定的首要来源。

核心原则

1. 预发布环境必须在容易出现错误的地方与生产环境保持一致。 如果预发布环境使用 SQLite,而生产环境使用 PostgreSQL,那么预发布测试无法证明生产环境中的行为正确。数据库引擎及其版本、队列系统、缓存层和身份验证提供方都应保持一致——环境相关的错误往往出现在这些地方。

2. 临时环境优于长期运行的环境。 共享的预发布环境会成为瓶颈:一次有问题的部署就会阻塞整个团队。每个 PR 配备独立的预览环境,可以隔离环境并支持并行测试;预发布环境仅用于发布前的最终验证。

3. 使用确定性的种子数据,而非生产数据副本。 生产快照包含个人身份信息、过时引用和无法复现的状态。使用工厂构建种子数据,生成一致、有效且精简的数据集。(关于工厂模式,请参阅 test-data-management。)

4. 在边界处模拟外部依赖,而不是在深层代码中模拟。 第三方 API 不可靠、存在速率限制且成本高昂。使用 MSW 或 WireMock 在 HTTP 边界处模拟它们——不要通过模拟内部服务类来实现,因为这会掩盖自有代码间的集成错误。

5. 将环境配置视为代码。 每项环境差异(URL、标志、凭据、资源限制)都必须纳入版本控制并可供审查。不得进行无法从代码仓库复现的手动设置。


环境策略

环境层级
环境用途数据外部依赖生命周期
本地开发快速迭代填充种子数据的固定数据,精简已模拟(MSW/WireMock)由开发者管理
CI自动化验证每次运行时填充种子数据,临时已模拟或容器化每条流水线创建并销毁
预览PR 级审查和端到端测试由工厂填充种子数据已模拟或沙盒环境创建 PR 时创建,关闭时销毁
预发布生产前验证匿名化且接近生产环境的数据真实集成(沙盒账户)长期运行,定期重置
生产服务真实用户真实数据真实依赖永久运行
本地开发

快速反馈,零共享状态。开发者必须能够在两分钟内于本地运行完整技术栈:

docker compose -f docker-compose.test.yml up -d
npm run db:seed
npm run dev

使用 Docker Compose 管理基础设施依赖(数据库、缓存、队列),但在本机原生运行应用以实现快速重载。外部 API 使用在开发模式下加载的 MSW 处理器进行模拟。

CI 环境

完全容器化,每次流水线运行时全新创建,结束后销毁。下面的代码块是作业的 services: 片段——将其嵌套在 jobs.<id>.services 下,与 runs-on 和 steps 同级;单独使用并不是有效的工作流文件。

# .github/workflows/test.yml — fragment: nest under jobs.test.services
services:
  postgres:
    image: postgres:18-alpine
    env:
      POSTGRES_DB: testdb
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    ports: ['5432:5432']
    options: >-
      --health-cmd="pg_isready -U test"
      --health-interval=5s
      --health-timeout=3s
      --health-retries=5
  redis:
    image: redis:8-alpine
    ports: ['6379:6379']
    options: >-
      --health-cmd="redis-cli ping"
      --health-interval=5s
      --health-timeout=3s
      --health-retries=5
Docker Compose 与 Testcontainers

为测试提供真实基础设施的两种方式。根据生命周期应该由谁管理来选择:

  • Docker Compose — 以声明式方式定义服务栈,在测试套件运行前启动(docker compose up --wait),结束后关闭,通常通过由 trap 保护的脚本完成。最适合本地开发、共享的 CI 服务栈,以及许多测试共用一组服务的 E2E 测试。
  • Testcontainers(Node / JVM / Python / Go)——从测试代码中启动容器,并在每个测试套件或测试结束后自动清理,无需维护 compose 文件或 trap。最适合需要隔离、可通过代码控制的基础设施的集成测试(例如,每个测试类使用一个临时 Postgres)。这是 2026 年“由测试管理的临时基础设施”的默认选择,也是手动编写 compose + trap 脚本的有力替代方案。

当人们和许多测试共用服务栈时,选择 Compose;当每个测试(或套件)都需要独立的临时副本时,选择 Testcontainers。

预览环境(每个 PR 一个)

每个拉取请求都有自己的隔离环境;审查者可以点击链接,测试确切的改动,且不会影响其他 PR。

托管选项(2026 年),根据技术栈选择:

  • Vercel 预览部署——适用于 Next.js / 静态网站 / 无服务器应用;自动为每个 PR 生成 URL。
  • Cloudflare Pages 预览——与 Git 集成,并提供慷慨的免费套餐。
  • Render / Railway 预览环境——支持包含数据库在内的全栈应用。
  • Northflank、Qovery、Bunnyshell、Uffizzi——完整的临时环境平台(基于 Kubernetes),适用于预览需要整个服务栈而不只是前端的情况。

为每个预览环境配套一个数据库分支(Neon、Supabase、PlanetScale 风格):PR 开启时创建分支,关闭时删除。这样每个预览环境都有一个低成本、即时创建的隔离数据库副本,而不是共用暂存数据库。(参见 test-data-management。)

为了让本地开发环境与 CI 保持一致:

  • Devcontainers(.devcontainer/devcontainer.json)——适用于 VS Code、Codespaces、JetBrains。是“每个人都使用相同的 Docker 支持开发环境”的标准方案。
  • Tilt(Tiltfile)——以 Kubernetes 为先的本地开发方案,支持热重载和多服务编排。适用于暂存环境本身也运行在 K8s 上的情况。

为前端创建预览环境,并针对生成的 URL 运行 E2E 测试,只需几行代码:

- name: Run E2E against preview
  env:
    BASE_URL: ${{ steps.deploy.outputs.preview-url }}
  run: npx playwright test --project=chromium

按每个 PR 的命名空间创建自定义 Docker 预览环境,并在 PR 关闭时自动清理:

- name: Deploy preview
  run: |
    NAMESPACE="pr-${{ github.event.number }}"
    docker compose -f docker-compose.preview.yml -p "$NAMESPACE" up -d
    echo "preview-url=https://${NAMESPACE}.preview.example.com" >> "$GITHUB_OUTPUT"

- name: Teardown preview
  if: github.event.action == 'closed'
  run: |
    NAMESPACE="pr-${{ github.event.number }}"
    docker compose -p "$NAMESPACE" down -v
暂存环境

长期运行的环境,基础设施与生产环境相仿。每周或按需重置,以防止配置漂移:

#!/bin/bash
# scripts/reset-staging.sh
set -euo pipefail

echo "Resetting staging database..."
psql "$STAGING_DATABASE_URL" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"

echo "Running migrations..."   # migrations MUST recreate extensions + grants (see caveat below)
npm run db:migrate -- --env staging

echo "Seeding anonymized data..."
npm run db:seed -- --env staging --dataset production-anonymized

echo "Verifying staging health..."
curl -sf https://staging.example.com/health || exit 1
echo "Staging reset complete."

注意: DROP SCHEMA public CASCADE 也会删除该架构的默认权限和已安装的扩展(uuid-ossp、pgcrypto 等)。迁移流水线必须重新创建它们(CREATE EXTENSION IF NOT EXISTS …,重新授予默认权限),否则迁移步骤会失败。不要以为只执行 CREATE SCHEMA public 就能恢复之前的授权——事实并非如此。


用于测试的 Docker Compose

docker-compose.test.yml 示例会启动完整的技术栈(应用、Postgres、Redis、一次性 seed 容器、Mailpit),用于集成测试和 E2E 测试。需要注意的细节:

  • 健康检查是 depends_on 的前置条件。 如果没有 healthcheck 和 condition: service_healthy,depends_on 只会等待容器启动,不会等待服务接受连接——随后测试就会与数据库发生竞争,并因连接错误而失败。
  • Seed 是一次性容器,不是长期运行的服务。 它使用 depends_on: condition: service_completed_successfully,因此只有在 seed 以状态码 0 退出后,应用才会启动。将 seed 建模为长期运行的服务会导致竞争问题,使应用在 seed 过程中启动。
  • 在主机上运行的测试需要发布端口。 示例会将主机端口绑定到回环地址,并确保运行器 URL 与之匹配。并行运行的技术栈需要使用不同的项目名称和主机端口。
  • PostgreSQL 18 使用不同的卷布局。 挂载到 /var/lib/postgresql,作为其带版本号的数据目录;其他主版本请参阅镜像文档。

有关完整的 docker-compose.test.yml、带尽力清理且保留原始退出状态的集成测试运行器、多阶段 Dockerfile(含 production 目标)以及 MinIO 部分,请参阅 references/docker-compose.md。

多阶段 Dockerfile

一个 base 层只安装一次依赖;development、test 和 seed 阶段会复用该层;精简的 production 阶段只运行生产依赖(npm ci --omit=dev),并从 test 阶段复制构建产物。这种划分能让测试依赖和源代码不进入交付的镜像,同时为每个环境提供独立的入口点。在 base 中使用 npm ci --include=dev——这是现代标志;--production=false 是旧式的 --omit/--include 语法。完整 Dockerfile 见 references/docker-compose.md。


外部依赖管理

按依赖类型划分的模拟策略
依赖类型本地/CI 策略预发布环境策略
支付(Stripe)使用 MSW 处理程序返回模拟响应使用 Stripe 测试模式和 sk_test_ 密钥
电子邮件(SendGrid)Mailpit 捕获 SMTP(Web UI 端口为 :8025,SMTP 端口为 :1025)SendGrid 沙盒模式
身份验证(Auth0)使用测试密钥的本地 JWT 签发方Auth0 开发租户
存储(S3)MinIO 容器(兼容 S3)配有生命周期策略的专用测试存储桶
搜索(Elasticsearch)Testcontainers Elasticsearch配有重置脚本的专用测试索引
短信(Twilio)MSW 处理程序Twilio 测试凭据

避免使用:MailHog——该项目已无人维护,最后一次发布于 2020 年。请使用 Mailpit(axllent/mailpit);它使用相同端口(1025 SMTP / 8025 UI),可直接替换。

使用 MSW 模拟 HTTP 请求

使用 MSW 2.x 在 HTTP 边界模拟外部 API:从 msw 导入 http 和 HttpResponse,从 msw/node 导入 setupServer,并通过 beforeAll/afterEach/afterAll 配置生命周期。设置 onUnhandledRequest: "error",让未模拟的外部调用明确导致测试失败,而不是泄漏真实网络请求。Stripe/SendGrid/地理编码处理程序和服务器生命周期详见 references/stubbing.md。

使用 MinIO 替代 S3

在本地/CI 测试中,通过容器运行兼容 S3 的存储,而不是访问真实的 AWS。将 AWS SDK 的 S3Client 指向它,并设置 endpoint、环境变量凭据和 forcePathStyle: true(MinIO 必需)。Compose 服务和客户端配置见 references/docker-compose.md。

使用契约测试验证模拟

模拟可能会偏离实际情况。为每个模拟配套编写契约测试,验证其是否符合真实 API 的结构。详情请参阅 contract-testing。


环境一致性检查清单

设置或审查非生产环境时,请运行此检查。

维度问题风险信号
数据库引擎是否与生产环境使用相同的引擎和版本?测试环境使用 SQLite,生产环境使用 PostgreSQL
数据库架构是否应用了相同的迁移流程?在预发布环境中手动更改架构
数据形态种子数据是否覆盖了所有实体状态?只有“顺利路径”记录,没有边缘情况
基础设施是否使用相同的容器编排方式?CI 中使用 Docker Compose,生产环境使用 Kubernetes
网络内部服务拓扑是否相同?测试环境使用单体架构,生产环境使用微服务
配置环境变量是否有文档记录并纳入版本控制?环境变量未记录,需手动设置
身份验证身份验证提供方和流程是否相同?测试环境绕过身份验证并使用硬编码令牌
功能开关是否使用相同的开关评估引擎?测试环境使用硬编码开关,生产环境使用 LaunchDarkly
TLS/HTTPS证书处理方式是否相同?预发布环境使用 HTTP,生产环境使用 HTTPS
超时/限制速率限制、连接池和超时设置是否相同?测试环境使用无限超时,掩盖性能问题

有关基于工厂的种子数据模式,请参阅 test-data-management。


反模式

将共享预发布环境作为唯一的测试环境。 一位开发者的部署故障会阻碍所有人。 使用每个 PR 独立的临时环境来实现隔离,并仅将预发布环境用于发布前的最终验证。

使用生产数据库副本作为测试数据。 存在个人身份信息风险,状态无法复现,数据集还会过于庞大,拖慢测试。使用工厂构建包含确定性值的最小种子数据。

特定于环境的代码路径。 if (process.env.NODE_ENV === "test") { skipAuth(); } 意味着你没有测试真实的身份验证流程。应通过依赖注入或配置切换实现,而不是使用环境条件判断。

手动设置环境。 如果设置环境需要一份 15 步的维基页面,那么一周内它就会过时。将所有步骤脚本化:docker compose up -d && npm run db:seed 应是唯一需要执行的步骤。

用桩替代内部服务,而非外部服务。 在系统与外部世界交互的 HTTP 边界处设置桩。为内部模块设置桩会掩盖自有服务之间的集成错误。

Docker Compose 中没有健康检查。 如果 depends_on 未配置健康检查,它只会等待容器启动,而不会等待服务就绪——测试会与数据库发生竞态,并因连接错误而失败。

长期运行的预览环境。 合并后仍保留的预览环境会浪费资源并积累过时状态。自动在 PR 关闭时清理环境(if: github.event.action == 'closed')。


验证

针对生成的制品运行以下检查,从最简单的检查开始:

  1. Compose 文件有效 — docker compose -f docker-compose.test.yml config -q 以状态码 0 退出(在拉取镜像之前就能发现 YAML 和架构错误)。
  2. 整个服务栈健康启动 — docker compose -f docker-compose.test.yml up -d --wait --wait-timeout 60 以状态码 0 退出。若失败,请检查输出:健康检查通过之前,启动也可能因镜像拉取、构建或种子数据命令而失败。
  3. 数据库接受连接 — docker compose -f docker-compose.test.yml exec postgres pg_isready -U test -d testdb 报告 accepting connections。还要运行主机上的测试运行器,验证其连接 URL 使用已发布的端口。
  4. Dockerfile 可构建生产目标 — docker build --target production -t app:prod . 成功,并且 docker run --rm app:prod npm ls --omit=dev --depth=0 显示没有开发依赖。
  5. 桩会明确报错 — 使用 onUnhandledRequest: "error" 运行测试套件;任何真实的出站调用都应使测试报错,而不是静默通过。

完成标准

  • 已记录环境清单(开发、CI、预览、预发布、生产),并说明每个层级的特征和访问注意事项。
  • docker compose -f docker-compose.test.yml config -q 和 up -d --wait 均以状态码 0 退出;长时间运行的服务通过其配置的就绪检查,一次性种子任务以状态码 0 退出。
  • 多阶段 Dockerfile 使用 --omit=dev 构建 production 目标(发布的映像中不包含开发依赖)。
  • 种子脚本具有幂等性(运行两次均以状态码 0 退出,且不会出现重复键错误),并已提交到代码仓库。
  • 在 HTTP 边界对外部依赖进行存根处理,并设置 onUnhandledRequest: "error";非生产环境中不使用真实的第三方凭据。
  • 已记录环境间的差异(例如,CI 使用 SQLite,而生产环境使用 PostgreSQL),并落实缓解措施或将其作为问题跟踪。
  • 为 PR 自动创建预览环境,并在 PR 关闭时自动销毁(if: github.event.action == 'closed')。

参考文件(位于 references/ 中)

  • docker-compose.md — 完整的 docker-compose.test.yml(Postgres 18、Redis 8、一次性种子任务、Mailpit)、由 trap 保护的集成测试运行器、多阶段 Dockerfile(base/development/test/seed/production),以及 MinIO 服务和 S3 客户端配置。
  • stubbing.md — 用于 Stripe/SendGrid/地理编码的 MSW 2.x 处理程序,以及带有 onUnhandledRequest: "error" 的 setupServer 生命周期配置。

相关技能

  • service-virtualization — 用于按依赖选择 mock、stub、fake 或真实实现的决策框架,以及 WireMock/MSW 的详细介绍。需要决定存根处理方式时,请参阅该技能;本技能负责将选定的存根接入环境。
  • test-data-management — 工厂模式、合成数据、数据库种子数据,以及用于创建每个 PR 独立数据库副本的数据库分支(Neon/Supabase/PlanetScale)。
  • ci-cd-integration — 流水线配置、GitHub Actions 服务、制品管理、分片和自托管运行器。需要了解周边工作流时,请参阅该技能;本技能定义其运行所依赖的服务。
  • contract-testing — 用于验证存根与真实 API 相符的消费者驱动契约。

评论

支持 Markdown