test-environments
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese<objective>
Staging on SQLite passes tests that break on prod Postgres; a shared staging box becomes a
queue where one broken deploy blocks the whole team; an unmocked Stripe call flakes CI at
random. This skill prevents those by designing environment tiers that mirror production where
it matters, isolate per-PR, and stub external dependencies at the HTTP boundary. It delivers a
working `docker compose up` local/CI stack, a parity checklist, and a stubbing strategy keyed
to dependency type.
</objective>
<objective>
如果Staging环境使用SQLite,而生产环境使用PostgreSQL,那么Staging环境中通过的测试可能在生产环境中失败;共享的Staging服务器会成为瓶颈,一次失败的部署会阻塞整个团队;未桩化的Stripe调用会导致CI随机失败。本技能通过设计与生产环境关键特性一致的环境分层、按PR隔离环境、在HTTP边界层桩化外部依赖来避免这些问题。它提供可运行的`docker compose up`本地/CI栈、一致性检查表,以及按依赖类型划分的桩化策略。
</objective>
Discovery Questions
探索问题
Check first — if it exists, use it and skip anything already
answered there. Then:
.agents/qa-project-context.md- How many environments exist today? Local dev, CI, staging, preview, production? Map what you have before designing what you need.
- Is the app containerized? Check for ,
Dockerfile, ordocker-compose.yml. If yes, multi-stage targets and compose come for free; if not, that is the first deliverable.compose.yaml - How is test data seeded? Manual SQL, migration-based, factory libraries, or production snapshots? This decides whether seed scripts are a quick win or a rewrite.
- How close is staging to production? Same DB engine, queue, cache, auth provider, orchestration? Each mismatch is a class of bugs staging can never catch.
- External dependencies: How many third-party APIs does the system call, and are they stubbed in non-prod? Unstubbed third parties are the top source of CI flake.
首先查看——如果该文件存在,请使用其中的信息,并跳过已回答的问题。然后:
.agents/qa-project-context.md- 当前存在多少种环境? 本地开发、CI、Staging、预览、生产环境?在设计所需环境之前,先梳理现有环境的情况。
- 应用是否已容器化? 检查是否存在、
Dockerfile或docker-compose.yml。如果已容器化,则可直接使用多阶段构建目标和Compose;如果没有,这将是首要交付成果。compose.yaml - 测试数据如何初始化? 手动SQL脚本、基于迁移的方式、工厂库还是生产环境快照?这将决定种子脚本是快速优化还是需要重写。
- Staging环境与生产环境的接近程度如何? 是否使用相同的数据库引擎、队列、缓存、认证提供商、编排工具?每一处不匹配都会导致Staging环境无法发现某类Bug。
- 外部依赖: 系统调用多少个第三方API?在非生产环境中这些API是否已被桩化?未桩化的第三方API是CI不稳定的主要原因。
Core Principles
核心原则
1. Staging must mirror production where bugs hide. If staging uses SQLite and production
uses PostgreSQL, staging tests prove nothing about prod behavior. Match the database engine
and version, the queue system, the cache layer, and the auth provider — those are where
environment-specific bugs live.
2. Ephemeral environments beat long-lived ones. A shared staging environment becomes a
bottleneck where one broken deploy blocks the entire team. Per-PR preview environments give
isolation and parallel testing; keep staging only for final pre-release validation.
3. Deterministic seed data, not production copies. Production snapshots carry PII, stale
references, and non-reproducible state. Build seed data from factories that generate
consistent, valid, minimal datasets. (For factory patterns, see .)
test-data-management4. Stub external dependencies at the boundary, not deep inside. Third-party APIs are
unreliable, rate-limited, and expensive. Stub them at the HTTP boundary with MSW or WireMock —
never by mocking internal service classes, which hides integration bugs between your own code.
5. Environment config is code. Every environment difference (URLs, flags, credentials,
resource limits) must be version-controlled and reviewable. No manual setup that cannot be
reproduced from the repo.
1. Staging环境必须在Bug高发区域与生产环境保持一致。 如果Staging使用SQLite而生产环境使用PostgreSQL,那么Staging的测试结果无法反映生产环境的行为。匹配数据库引擎及版本、队列系统、缓存层和认证提供商——这些都是环境特定Bug的高发区。
2. 临时环境优于长期运行环境。 共享的Staging环境会成为瓶颈,一次失败的部署会阻塞整个团队。按PR划分的预览环境提供隔离性和并行测试能力;仅保留Staging环境用于最终的预发布验证。
3. 使用确定性种子数据,而非生产环境副本。 生产环境快照包含PII(个人可识别信息)、陈旧引用和不可复现的状态。基于工厂模式构建种子数据,生成一致、有效、最小化的数据集。(关于工厂模式,请参考。)
test-data-management4. 在边界层桩化外部依赖,而非在内部深层桩化。 第三方API不可靠、有调用限制且成本高昂。使用MSW或WireMock在HTTP边界层桩化它们——绝不要通过Mock内部服务类的方式,这会隐藏自有代码之间的集成Bug。
5. 环境配置即代码。 所有环境差异(URL、功能开关、凭证、资源限制)都必须纳入版本控制并可被评审。不允许存在无法从代码仓库复现的手动配置。
Environment Strategy
环境策略
Environment Tiers
环境分层
| Environment | Purpose | Data | External Deps | Lifecycle |
|---|---|---|---|---|
| Local dev | Fast inner loop | Seeded fixtures, minimal | Stubbed (MSW/WireMock) | Developer-managed |
| CI | Automated validation | Seeded per-run, ephemeral | Stubbed or containerized | Created/destroyed per pipeline |
| Preview | PR-level review & E2E | Seeded from factories | Stubbed or sandbox | Created on PR, destroyed on close |
| Staging | Pre-production validation | Anonymized production-like | Real integrations (sandbox accounts) | Long-lived, regularly reset |
| Production | Live users | Real | Real | Permanent |
| 环境 | 用途 | 数据 | 外部依赖 | 生命周期 |
|---|---|---|---|---|
| 本地开发 | 快速内循环验证 | 初始化的测试数据,最小化规模 | 桩化(MSW/WireMock) | 开发者自主管理 |
| CI | 自动化验证 | 每次运行初始化,临时数据 | 桩化或容器化 | 随流水线创建/销毁 |
| 预览 | PR级评审与端到端测试 | 基于工厂模式生成的初始化数据 | 桩化或沙箱环境 | PR创建时生成,PR关闭时销毁 |
| Staging | 预生产验证 | 匿名化的生产级数据 | 真实集成(沙箱账号) | 长期运行,定期重置 |
| 生产 | 面向真实用户 | 真实数据 | 真实依赖 | 永久运行 |
Local Development
本地开发
Fast feedback, zero shared state. Developers must be able to run the full stack locally in
under two minutes:
bash
docker compose -f docker-compose.test.yml up -d
npm run db:seed
npm run devUse Docker Compose for infrastructure deps (database, cache, queue) but run the application
natively for fast reload. External APIs are stubbed with MSW handlers loaded in dev mode.
快速反馈,无共享状态。开发者必须能够在两分钟内启动完整的本地栈:
bash
docker compose -f docker-compose.test.yml up -d
npm run db:seed
npm run dev使用Docker Compose管理基础设施依赖(数据库、缓存、队列),但本地运行应用以实现快速重载。外部API通过开发模式下加载的MSW处理器进行桩化。
CI Environment
CI环境
Fully containerized, created fresh per pipeline run, destroyed after. The block below is the
fragment of a job — nest it under alongside
and ; on its own it is not a valid workflow file.
services:jobs.<id>.servicesruns-onstepsyaml
undefined完全容器化,每次流水线运行时全新创建,运行结束后销毁。以下是作业的**片段**——将其嵌套在下,与和同级;单独使用时并非有效的工作流文件。
services:jobs.<id>.servicesruns-onstepsyaml
undefined.github/workflows/test.yml — fragment: nest under jobs.test.services
.github/workflows/test.yml — 片段:嵌套在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
undefinedservices:
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
undefinedDocker Compose vs Testcontainers
Docker Compose vs Testcontainers
Two ways to give tests real infrastructure. Pick by where the lifecycle should live:
- Docker Compose — declarative stack you bring up before the suite () and tear down after, usually via a
docker compose up --wait-guarded script. Best for local dev, a shared CI stack, and E2E where many tests share one set of services.trap - Testcontainers (Node / JVM / Python / Go) — containers spun up from test code and
auto-torn-down per suite or per test, with no compose file or to maintain. Best for integration tests that need isolated, programmatic infra (a throwaway Postgres per test class). The 2026 default for "ephemeral infra owned by the test," and a strong alternative to hand-rolled compose + trap scripts.
trap
Reach for Compose when humans and many tests share the stack; reach for Testcontainers when
each test (or suite) wants its own disposable copy.
为测试提供真实基础设施的两种方式。根据生命周期管理需求选择:
- Docker Compose — 声明式栈,在测试套件运行前启动(),运行后销毁,通常通过
docker compose up --wait守护脚本实现。最适合本地开发、共享CI栈,以及多个测试共享同一服务集的端到端测试。traps - Testcontainers(Node / JVM / Python / Go) — 从测试代码中启动容器,测试套件或单个测试结束后自动销毁,无需维护compose文件或脚本。最适合需要隔离、可编程基础设施的集成测试(例如每个测试类使用独立的Postgres实例)。这是2026年「测试专属临时基础设施」的默认方案,也是手动编写compose+traps脚本的优质替代方案。
traps
当人员和多个测试共享基础设施时选择Compose;当每个测试(或套件)需要独立的一次性副本时选择Testcontainers。
Preview Environments (Per-PR)
预览环境(按PR划分)
Each pull request gets its own isolated environment; reviewers click a link and test the exact
changes without interfering with other PRs.
Hosting options (2026), pick by stack:
- Vercel preview deployments — Next.js / static / serverless; per-PR URL automatically.
- Cloudflare Pages preview — git-integrated, generous free tier.
- Render / Railway preview environments — full-stack including databases.
- Northflank, Qovery, Bunnyshell, Uffizzi — full ephemeral-environment platforms (Kubernetes-backed) when previews need the whole stack, not just a frontend.
For each preview, pair the env lifecycle with a database branch (Neon, Supabase,
PlanetScale-style): create a branch on PR open, drop it on close. That gives every preview a
cheap, instant, isolated DB copy instead of a shared staging DB. (See .)
test-data-managementFor local-dev parity with CI:
- Devcontainers () — VS Code, Codespaces, JetBrains. The standard for "everyone gets the same Docker-backed dev env."
.devcontainer/devcontainer.json - Tilt () — Kubernetes-first local dev with hot reload and multi-service orchestration. Pick when staging itself is K8s.
Tiltfile
A frontend preview with E2E against the generated URL is a few lines:
yaml
- name: Run E2E against preview
env:
BASE_URL: ${{ steps.deploy.outputs.preview-url }}
run: npx playwright test --project=chromiumA custom Docker preview keyed to a per-PR namespace, auto-torn-down on close:
yaml
- 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每个Pull Request都拥有独立的隔离环境;评审人员点击链接即可测试确切的变更,不会干扰其他PR。
2026年的托管选项,根据技术栈选择:
- Vercel预览部署 — 适用于Next.js / 静态站点 / Serverless;自动生成按PR划分的URL。
- Cloudflare Pages预览 — 与Git集成,免费额度充足。
- Render / Railway预览环境 — 包含数据库的全栈方案。
- Northflank、Qovery、Bunnyshell、Uffizzi — 全栈临时环境平台(基于Kubernetes),适用于需要完整栈而非仅前端的预览场景。
为每个预览环境搭配数据库分支(Neon、Supabase、PlanetScale风格):PR打开时创建分支,PR关闭时删除分支。这样每个预览环境都拥有廉价、即时、隔离的数据库副本,而非共享Staging数据库。(参考。)
test-data-management为实现本地开发与CI环境的一致性:
- Devcontainers() — 适用于VS Code、Codespaces、JetBrains。这是「所有人使用相同Docker驱动开发环境」的标准方案。
.devcontainer/devcontainer.json - Tilt() — 面向Kubernetes的本地开发方案,支持热重载和多服务编排。当Staging环境本身基于K8s时选择此方案。
Tiltfile
针对生成的URL运行端到端测试的前端预览配置只需几行代码:
yaml
- name: Run E2E against preview
env:
BASE_URL: ${{ steps.deploy.outputs.preview-url }}
run: npx playwright test --project=chromium按PR命名空间划分、PR关闭时自动销毁的自定义Docker预览配置:
yaml
- 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 -vStaging
Staging
Long-lived environment that mirrors production infrastructure. Reset weekly or on-demand to
prevent drift:
bash
#!/bin/bash与生产基础设施一致的长期运行环境。每周或按需重置以避免环境漂移:
bash
#!/bin/bashscripts/reset-staging.sh
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."
**Caveat:** `DROP SCHEMA public CASCADE` also drops the schema's default privileges and any
installed extensions (`uuid-ossp`, `pgcrypto`, …). Your migration pipeline must recreate them
(`CREATE EXTENSION IF NOT EXISTS …`, re-grant defaults) or the migrate step fails. Don't assume
a bare `CREATE SCHEMA public` restores the prior grants — it does not.
---set -euo pipefail
echo "Resetting staging database..."
psql "$STAGING_DATABASE_URL" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
echo "Running migrations..." # 迁移必须重新创建扩展和权限(见下方注意事项)
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 for Testing
Docker Compose测试方案
A production-quality spins up the full stack (app, Postgres, Redis,
a one-shot seed container, Mailpit) for integration and E2E tests. Two details that matter:
docker-compose.test.yml- Health checks gate . Without a
depends_on+healthcheck,condition: service_healthyonly waits for the container to start, not for the service to accept connections — tests then race the database and fail with connection errors.depends_on - Seed is a one-shot container, not a long-running service. It uses , so the app starts only after seeding exits 0. Teams that model seed as a long-running service get a race where the app boots mid-seed.
depends_on: condition: service_completed_successfully
See for the full , the
-guarded integration test runner, the multi-stage Dockerfile (with the
target), and the MinIO block.
references/docker-compose.mddocker-compose.test.ymltrapproduction生产级别的会启动完整栈(应用、Postgres、Redis、一次性初始化容器、Mailpit)以支持集成测试和端到端测试。以下两个细节至关重要:
docker-compose.test.yml- 健康检查控制。 如果没有
depends_on+healthcheck,condition: service_healthy仅等待容器启动,而非服务就绪并可接受连接——此时测试会与数据库竞争资源,因连接错误而失败。depends_on - 初始化是一次性容器,而非长期运行服务。 使用,确保应用仅在初始化容器*成功退出(exit 0)*后启动。将初始化建模为长期运行服务的团队会遇到竞争问题:应用在初始化过程中启动。
depends_on: condition: service_completed_successfully
完整的、守护的集成测试运行器、多阶段Dockerfile(含目标)及MinIO配置,请参考。
docker-compose.test.ymltrapsproductionreferences/docker-compose.mdMulti-Stage Dockerfile
多阶段Dockerfile
One layer installs deps once; , , and stages reuse it; and a
slim stage runs prod deps only () with build artifacts copied
from the stage. The split keeps test dependencies and source out of the shipped image
while giving each environment its own entrypoint. Use in — the
modern flag; is legacy / syntax. Full Dockerfile in
.
basedevelopmenttestseedproductionnpm ci --omit=devtestnpm ci --include=devbase--production=false--omit--includereferences/docker-compose.md一个层一次性安装依赖;、和阶段复用该层;精简的阶段仅运行生产依赖(),并从阶段复制构建产物。这种拆分可将测试依赖和源码排除在发布镜像之外,同时为每个环境提供独立的入口点。在层使用——这是现代语法;是旧版/语法。完整Dockerfile请参考。
basedevelopmenttestseedproductionnpm ci --omit=devtestbasenpm ci --include=dev--production=false--omit--includereferences/docker-compose.mdExternal Dependency Management
外部依赖管理
Stubbing Strategy by Dependency Type
按依赖类型划分的桩化策略
| Dependency Type | Local/CI Strategy | Staging Strategy |
|---|---|---|
| Payment (Stripe) | MSW handler returning mock responses | Stripe test mode with |
| Email (SendGrid) | Mailpit capturing SMTP (web UI on :8025, SMTP on :1025) | SendGrid sandbox mode |
| Auth (Auth0) | Local JWT issuer with test keys | Auth0 dev tenant |
| Storage (S3) | MinIO container (S3-compatible) | Dedicated test bucket with lifecycle policy |
| Search (Elasticsearch) | Testcontainers Elasticsearch | Dedicated test index with reset script |
| SMS (Twilio) | MSW handler | Twilio test credentials |
Avoid: MailHog — unmaintained, last release 2020. Use Mailpit (); it is a
drop-in on the same ports (1025 SMTP / 8025 UI).
axllent/mailpit| 依赖类型 | 本地/CI策略 | Staging策略 |
|---|---|---|
| 支付(Stripe) | MSW处理器返回模拟响应 | 使用 |
| 邮件(SendGrid) | Mailpit捕获SMTP(Web UI端口:8025,SMTP端口:1025) | SendGrid沙箱模式 |
| 认证(Auth0) | 本地JWT签发器+测试密钥 | Auth0开发租户 |
| 存储(S3) | MinIO容器(兼容S3) | 带生命周期策略的专用测试存储桶 |
| 搜索(Elasticsearch) | Testcontainers Elasticsearch | 带重置脚本的专用测试索引 |
| 短信(Twilio) | MSW处理器 | Twilio测试凭证 |
避免使用MailHog——已停止维护,最后一次发布是2020年。请使用Mailpit();它是MailHog的无缝替代,使用相同端口(1025 SMTP / 8025 UI)。
axllent/mailpitMSW for HTTP Stubs
MSW用于HTTP桩化
Stub external APIs at the HTTP boundary with MSW 2.x: + from ,
from , lifecycle wired through //. Set
so an unmocked external call fails the test loudly instead of
leaking a real network request. See for the Stripe/SendGrid/geocoding
handlers and the server lifecycle.
httpHttpResponsemswsetupServermsw/nodebeforeAllafterEachafterAllonUnhandledRequest: "error"references/stubbing.md使用MSW 2.x在HTTP边界层桩化外部API:从导入 + ,从导入,通过//管理生命周期。设置,这样未桩化的外部调用会直接导致测试失败,而非静默发起真实网络请求。Stripe/SendGrid/地理编码处理器及服务器生命周期配置,请参考。
mswhttpHttpResponsemsw/nodesetupServerbeforeAllafterEachafterAllonUnhandledRequest: "error"references/stubbing.mdMinIO as an S3 Substitute
MinIO作为S3替代方案
Run S3-compatible storage in a container instead of hitting real AWS in local/CI tests. Point
the AWS SDK at it with , env-var credentials, and
(required for MinIO). Compose service + client config in .
S3ClientendpointforcePathStyle: truereferences/docker-compose.md在容器中运行兼容S3的存储服务,避免在本地/CI测试中调用真实AWS服务。通过、环境变量凭证和(MinIO必需)配置AWS SDK的。Compose服务及客户端配置请参考。
endpointforcePathStyle: trueS3Clientreferences/docker-compose.mdContract Testing as Stub Validation
契约测试作为桩化验证手段
Stubs drift from reality. Pair every stub with a contract test that verifies the stub matches
the real API shape. For details, see .
contract-testing桩化实现会与真实API产生偏差。为每个桩化实现搭配契约测试,验证桩化响应与真实API结构一致。详情请参考。
contract-testingEnvironment Parity Checklist
环境一致性检查表
Run this when setting up or auditing a non-production environment.
| Dimension | Question | Red Flag |
|---|---|---|
| Database engine | Same engine and version as production? | SQLite in test, PostgreSQL in prod |
| Database schema | Same migration pipeline applied? | Manual schema changes in staging |
| Data shape | Seed data covers all entity states? | Only "happy path" records, no edge cases |
| Infrastructure | Same container orchestration? | Docker Compose in CI, Kubernetes in prod |
| Network | Same internal service topology? | Monolith in test, microservices in prod |
| Config | Env vars documented and version-controlled? | Undocumented env vars, manual setup |
| Auth | Same auth provider/flow? | Bypassed auth in test with hardcoded tokens |
| Feature flags | Same flag evaluation engine? | Hardcoded flags in test, LaunchDarkly in prod |
| TLS/HTTPS | Same certificate handling? | HTTP in staging, HTTPS in prod |
| Timeouts/Limits | Same rate limits, pools, timeouts? | Infinite timeouts in test hide perf issues |
For factory-based seed data patterns, see .
test-data-management在搭建或审计非生产环境时使用此检查表。
| 维度 | 问题 | 风险信号 |
|---|---|---|
| 数据库引擎 | 是否与生产环境使用相同引擎及版本? | 测试环境用SQLite,生产环境用PostgreSQL |
| 数据库 schema | 是否应用了相同的迁移流水线? | Staging环境存在手动schema变更 |
| 数据结构 | 种子数据是否覆盖所有实体状态? | 仅包含「正常路径」记录,无边缘情况 |
| 基础设施 | 是否使用相同的容器编排工具? | CI环境用Docker Compose,生产环境用Kubernetes |
| 网络 | 是否拥有相同的内部服务拓扑? | 测试环境是单体应用,生产环境是微服务 |
| 配置 | 环境变量是否已文档化并纳入版本控制? | 存在未文档化的环境变量、手动配置 |
| 认证 | 是否使用相同的认证提供商/流程? | 测试环境通过硬编码令牌绕过认证 |
| 功能开关 | 是否使用相同的开关评估引擎? | 测试环境用硬编码开关,生产环境用LaunchDarkly |
| TLS/HTTPS | 是否使用相同的证书处理方式? | Staging环境用HTTP,生产环境用HTTPS |
| 超时/限制 | 是否使用相同的速率限制、连接池、超时设置? | 测试环境无超时限制,隐藏性能问题 |
基于工厂模式的种子数据模式,请参考。
test-data-managementAnti-Patterns
反模式
Shared staging as the only test environment. One developer's broken deploy blocks everyone.
Use ephemeral per-PR environments for isolation and keep staging for final pre-release
validation only.
Production database copies for test data. PII risk, non-reproducible state, massive
datasets that slow tests. Build minimal seed data from factories with deterministic values.
Environment-specific code paths.
means you are not testing the real auth flow. Swap implementations via dependency injection or
config, not environment conditionals.
if (process.env.NODE_ENV === "test") { skipAuth(); }Manual environment setup. If setup needs a 15-step wiki page, it will be wrong within a
week. Script everything: should be the only steps.
docker compose up -d && npm run db:seedStubbing internal services instead of external ones. Stub at the HTTP boundary where your
system talks to the outside world. Stubbing internal modules hides integration bugs between
your own services.
No health checks in Docker Compose. without a healthcheck waits only for the
container to start, not for the service to be ready — tests race the database and fail with
connection errors.
depends_onLong-lived preview environments. Previews that persist after merge waste resources and
accumulate stale state. Automate teardown on PR close ().
if: github.event.action == 'closed'仅使用共享Staging作为测试环境。 开发者的一次失败部署会阻塞所有人。使用按PR划分的临时环境实现隔离,仅保留Staging用于最终预发布验证。
使用生产数据库副本作为测试数据。 存在PII泄露风险、不可复现状态,且数据集过大导致测试缓慢。基于工厂模式构建最小化的确定性种子数据。
环境特定代码路径。 意味着未测试真实认证流程。通过依赖注入或配置切换实现,而非环境条件判断。
if (process.env.NODE_ENV === "test") { skipAuth(); }手动环境配置。 如果配置需要15步的wiki文档,一周内就会出现错误。将所有操作脚本化:应是唯一需要执行的步骤。
docker compose up -d && npm run db:seed桩化内部服务而非外部依赖。 在系统与外部交互的HTTP边界层进行桩化。桩化内部模块会隐藏自有服务之间的集成Bug。
Docker Compose中未配置健康检查。 无健康检查的仅等待容器启动,而非服务就绪——测试会与数据库竞争资源,因连接错误而失败。
depends_on长期运行的预览环境。 合并后仍保留的预览环境会浪费资源并积累陈旧状态。在PR关闭时自动销毁()。
if: github.event.action == 'closed'Verification
验证步骤
Run these against the artifacts you produce, smallest check first:
- Compose file is valid — exits 0 (catches YAML and schema errors before you ever pull an image).
docker compose -f docker-compose.test.yml config -q - Stack comes up healthy — exits 0; a non-zero exit means a healthcheck never went green.
docker compose -f docker-compose.test.yml up -d --wait --wait-timeout 60 - Database accepts connections — reports
docker compose exec postgres pg_isready -U test -d testdb.accepting connections - Dockerfile builds the production target — succeeds, and
docker build --target production -t app:prod .shows no dev deps.docker run --rm app:prod npm ls --omit=dev --depth=0 - Stubs fail loud — run the suite with ; any real outbound call should error the test, not pass silently.
onUnhandledRequest: "error"
针对交付成果执行以下验证,从最小检查项开始:
- Compose文件有效 — 返回0(在拉取镜像前捕获YAML和schema错误)。
docker compose -f docker-compose.test.yml config -q - 栈健康启动 — 返回0;非0返回值表示健康检查从未通过。
docker compose -f docker-compose.test.yml up -d --wait --wait-timeout 60 - 数据库可接受连接 — 返回
docker compose exec postgres pg_isready -U test -d testdb。accepting connections - Dockerfile可构建生产目标 — 执行成功,且
docker build --target production -t app:prod .显示无开发依赖。docker run --rm app:prod npm ls --omit=dev --depth=0 - 桩化失败时触发错误 — 使用运行测试套件;任何真实外部调用都应导致测试失败,而非静默通过。
onUnhandledRequest: "error"
Done When
交付标准
- Environment inventory documented (dev, CI, preview, staging, production) with characteristics and access notes per tier.
- exits 0 and
docker compose -f docker-compose.test.yml config -qbrings every service to a passing healthcheck (exit 0).docker compose up -d --wait - Multi-stage Dockerfile builds the target with
production(no dev dependencies in the shipped image).--omit=dev - Seed scripts are idempotent (running twice exits 0, no duplicate-key errors) and checked into the repository.
- External dependencies are stubbed at the HTTP boundary with ; no real third-party credentials in non-prod.
onUnhandledRequest: "error" - Environment parity gaps documented (e.g. SQLite in CI vs PostgreSQL in prod) with mitigations in place or tracked as issues.
- Preview environments auto-created for PRs and auto-torn-down on close ().
if: github.event.action == 'closed'
- 已记录环境清单(开发、CI、预览、Staging、生产),包含各层级的特性及访问说明。
- 返回0,且
docker compose -f docker-compose.test.yml config -q使所有服务通过健康检查(返回0)。docker compose up -d --wait - 多阶段Dockerfile可构建目标,且使用
production(发布镜像中无开发依赖)。--omit=dev - 种子脚本具有幂等性(运行两次返回0,无重复键错误),并已提交至代码仓库。
- 外部依赖已在HTTP边界层桩化,且设置;非生产环境中无真实第三方凭证。
onUnhandledRequest: "error" - 已记录环境一致性差距(例如CI用SQLite vs 生产用PostgreSQL),并已采取缓解措施或跟踪为问题。
- PR打开时自动创建预览环境,PR关闭时自动销毁()。
if: github.event.action == 'closed'
Reference Files (in references/
)
references/参考文件(位于references/
)
references/- docker-compose.md — full (Postgres 18, Redis 8, one-shot seed, Mailpit), the
docker-compose.test.yml-guarded integration test runner, the multi-stage Dockerfile (base/development/test/seed/production), and the MinIO service + S3 client config.trap - stubbing.md — MSW 2.x handlers for Stripe/SendGrid/geocoding and the lifecycle with
setupServer.onUnhandledRequest: "error"
- docker-compose.md — 完整的(Postgres 18、Redis 8、一次性初始化容器、Mailpit)、
docker-compose.test.yml守护的集成测试运行器、多阶段Dockerfile(base/development/test/seed/production)、MinIO服务及S3客户端配置。traps - stubbing.md — Stripe/SendGrid/地理编码的MSW 2.x处理器,以及设置的
onUnhandledRequest: "error"生命周期配置。setupServer
Related Skills
相关技能
- service-virtualization — Decision framework for choosing mock vs stub vs fake vs real per dependency, and WireMock/MSW depth. Go there to decide the stubbing approach; this skill wires the chosen stub into the environment.
- test-data-management — Factory patterns, synthetic data, database seeding, and DB branching (Neon/Supabase/PlanetScale) for per-PR DB copies.
- ci-cd-integration — Pipeline config, GitHub Actions services, artifact management, sharding, and self-hosted runners. Go there for the surrounding workflow; this skill defines the services it runs against.
- contract-testing — Consumer-driven contracts that verify your stubs match real APIs.
- service-virtualization — 针对单个依赖选择Mock/Stub/Fake/真实实现的决策框架,以及WireMock/MSW的深度配置。如需确定桩化方案,请参考该技能;本技能负责将选定的桩化方案接入环境。
- test-data-management — 工厂模式、合成数据、数据库初始化、数据库分支(Neon/Supabase/PlanetScale)用于按PR划分数据库副本。
- ci-cd-integration — 流水线配置、GitHub Actions服务、制品管理、分片、自托管运行器。如需配置周边工作流,请参考该技能;本技能定义工作流运行的服务环境。
- contract-testing — 消费者驱动契约,验证桩化实现与真实API一致。