integration-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegration Testing Skill
集成测试Skill
This skill captures critical learnings from US-001 (birdmate project) where 3 integration bugs appeared in demo despite 272 passing unit tests. Covers CORS configuration, API contract validation, and build artifact hygiene.
本Skill总结了US-001(birdmate项目)中的关键经验:尽管有272个通过的单元测试,但演示中仍出现了3个集成bug。内容涵盖CORS配置、API契约验证及构建产物规范。
CORS Development Configuration
CORS开发配置
Critical Pattern: Development CORS must accommodate dynamic port assignment
typescript
// ❌ WRONG - Single hardcoded origin
const CORS_ORIGIN = 'http://localhost:5173';
// ✅ CORRECT - Support port range
const CORS_ORIGINS = (process.env.CORS_ORIGIN ||
'http://localhost:5173,http://localhost:5174,http://localhost:5175'
).split(',').map(s => s.trim());
app.use(cors({ origin: CORS_ORIGINS, credentials: true }));Why: Vite/dev servers auto-increment ports when default is occupied
Configuration Template:
bash
undefined关键模式:开发环境下的CORS必须支持动态端口分配
typescript
// ❌ 错误 - 单一硬编码源
const CORS_ORIGIN = 'http://localhost:5173';
// ✅ 正确 - 支持端口范围
const CORS_ORIGINS = (process.env.CORS_ORIGIN ||
'http://localhost:5173,http://localhost:5174,http://localhost:5175'
).split(',').map(s => s.trim());
app.use(cors({ origin: CORS_ORIGINS, credentials: true }));原因:当默认端口被占用时,Vite/开发服务器会自动递增端口
配置模板:
bash
undefinedbackend/.env
backend/.env
backend/.env.example (commit this)
backend/.env.example(提交此文件)
undefinedundefinedAPI Contract Validation
API契约验证
Critical: Types at integration boundary must match wire format
关键要点:集成边界的类型必须与传输格式匹配
Anti-Pattern: Over-abstraction
反模式:过度抽象
typescript
// Shared type (ideal design, not actual API)
interface SearchResult {
bird: Bird; // ❌ Nested structure
score: number;
}typescript
// 共享类型(理想设计,非实际API)
interface SearchResult {
bird: Bird; // ❌ 嵌套结构
score: number;
}Correct Pattern: Wire-format types
正确模式:传输格式类型
typescript
// API response types (match actual backend)
interface ApiSearchResult {
id: string; // ✅ Flat structure
commonName: string;
score: number;
}
// Transform at boundary
function transformResults(apiResults: ApiSearchResult[]): AppResult[] {
return apiResults.map(api => ({
bird: { id: api.id, commonName: api.commonName },
score: api.score
}));
}Validation Checklist:
- Test API endpoint with curl, inspect actual JSON
- Create types matching exact response structure
Api* - Transform to app types at integration boundary only
- Never assume shared types match wire format
typescript
// API响应类型(与实际后端匹配)
interface ApiSearchResult {
id: string; // ✅ 扁平结构
commonName: string;
score: number;
}
// 在边界处转换
function transformResults(apiResults: ApiSearchResult[]): AppResult[] {
return apiResults.map(api => ({
bird: { id: api.id, commonName: api.commonName },
score: api.score
}));
}验证检查清单:
- 使用curl测试API端点,检查实际JSON结构
- 创建与响应结构完全匹配的类型
Api* - 仅在集成边界处转换为应用类型
- 切勿假设共享类型与传输格式一致
Build Artifact Hygiene
构建产物规范
Critical: Compiled files must not shadow source files
关键要点:编译文件不得覆盖源文件
TypeScript Project .gitignore
TypeScript项目.gitignore
gitignore
undefinedgitignore
undefinedCompiled outputs
编译输出
dist/
build/
*.js # ⚠️ CRITICAL in src/ directories
*.js.map
*.jsx
*.jsx.map
dist/
build/
*.js # ⚠️ 在src/目录中至关重要
*.js.map
*.jsx
*.jsx.map
Exception: config files (use negation)
例外:配置文件(使用否定规则)
!vite.config.js
!playwright.config.js
!*.config.js
**Pre-demo validation**:
```bash!vite.config.js
!playwright.config.js
!*.config.js
**预演示验证**:
```bashCheck for stale .js in src/
检查src/中的旧.js文件
find src/ -name "*.js" -type f | grep -v node_modules
find src/ -name "*.js" -type f | grep -v node_modules
Should return empty or only intentional .js files
应返回空或仅包含有意保留的.js文件
**Package.json clean scripts**:
```json
{
"scripts": {
"clean": "find src/ -name '*.js' -o -name '*.jsx' | xargs rm -f",
"prebuild": "npm run clean",
"predev": "npm run clean"
}
}
**Package.json清理脚本**:
```json
{
"scripts": {
"clean": "find src/ -name '*.js' -o -name '*.jsx' | xargs rm -f",
"prebuild": "npm run clean",
"predev": "npm run clean"
}
}Integration Testing Workflow
集成测试流程
1. Start Backend
1. 启动后端
bash
cd backend && npm run devVerify CORS origins logged at startup
bash
cd backend && npm run dev验证启动日志中是否包含配置的CORS源
2. Start Frontend
2. 启动前端
bash
cd frontend && npm run devNote actual port assigned (may not be 5173)
bash
cd frontend && npm run dev记录实际分配的端口(可能不是5173)
3. Test CORS Preflight
3. 测试CORS预检
bash
curl -I -X OPTIONS http://localhost:3001/api/v1/search \
-H "Origin: http://localhost:5175" \
-H "Access-Control-Request-Method: POST"bash
curl -I -X OPTIONS http://localhost:3001/api/v1/search \
-H "Origin: http://localhost:5175" \
-H "Access-Control-Request-Method: POST"Should return: Access-Control-Allow-Origin: http://localhost:5175
应返回:Access-Control-Allow-Origin: http://localhost:5175
undefinedundefined4. Test API Contract
4. 测试API契约
bash
curl -X POST http://localhost:3001/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "test"}' | jq '.' > actual-response.jsonbash
curl -X POST http://localhost:3001/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "test"}' | jq '.' > actual-response.jsonVerify response structure matches frontend types
验证响应结构与前端类型匹配
undefinedundefined5. Browser Network Inspection
5. 浏览器网络检查
- Open DevTools → Network tab
- Execute feature in browser
- Verify no CORS errors (200 OK on OPTIONS + POST)
- Verify no type errors in console
- 打开开发者工具 → 网络标签
- 在浏览器中执行功能操作
- 验证无CORS错误(OPTIONS和POST请求均返回200 OK)
- 验证控制台无类型错误
Common Failure Modes
常见故障模式
| Symptom | Root Cause | Fix |
|---|---|---|
| CORS misconfiguration | Add frontend port to backend CORS_ORIGIN |
| Single port in CORS, frontend on different port | Use comma-separated port list |
| Type mismatch (nested vs flat) | Create API-specific types at boundary |
| Old UI showing after code changes | Stale .js files shadowing .tsx | Remove compiled artifacts from src/ |
| Port already in use | Previous dev server still running | |
| 症状 | 根本原因 | 修复方案 |
|---|---|---|
| CORS配置错误 | 将前端端口添加到后端的CORS_ORIGIN中 |
| CORS仅配置了单一端口,前端使用不同端口 | 使用逗号分隔的端口列表 |
| 类型不匹配(嵌套vs扁平) | 在边界处创建API专用类型 |
| 代码更改后仍显示旧UI | 旧.js文件覆盖.tsx文件 | 删除src/中的编译产物 |
| 端口已被占用 | 之前的开发服务器仍在运行 | |
Pre-Demo Validation Checklist
预演示验证检查清单
Before demoing any full-stack feature:
- Backend has
.envwith port range (5173-5175)CORS_ORIGIN - No or
.jsfiles in.jsxorfrontend/src/backend/src/ - test confirms API response structure
curl - Network tab shows successful OPTIONS + POST requests
- Console shows no CORS or type errors
- URL state persistence working (if applicable)
- Both services started fresh (no cached state)
在演示任何全栈功能之前:
- 后端文件中的
.env包含端口范围(5173-5175)CORS_ORIGIN - 或
frontend/src/中无backend/src/或.js文件.jsx - curl测试确认API响应结构正确
- 网络标签显示OPTIONS和POST请求均成功
- 控制台无CORS或类型错误
- URL状态持久化功能正常(如适用)
- 两个服务均为全新启动(无缓存状态)
Integration Test Template
集成测试模板
typescript
// e2e/integration-smoke.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Full Stack Integration', () => {
test.beforeAll(async () => {
// Verify backend is running
const health = await fetch('http://localhost:3001/health');
expect(health.ok).toBeTruthy();
});
test('user can complete primary user flow', async ({ page }) => {
// Monitor console for errors
const consoleErrors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Execute user flow
await page.goto('http://localhost:5173');
await page.getByPlaceholder('Search...').fill('test query');
await page.getByRole('button', { name: 'Search' }).click();
// Verify success
await expect(page.getByTestId('results')).toBeVisible();
// Assert no integration errors
expect(consoleErrors).toHaveLength(0);
expect(consoleErrors.filter(e => e.includes('CORS'))).toHaveLength(0);
expect(consoleErrors.filter(e => e.includes('undefined'))).toHaveLength(0);
});
});typescript
// e2e/integration-smoke.spec.ts
import { test, expect } from '@playwright/test';
test.describe('全栈集成', () => {
test.beforeAll(async () => {
// 验证后端是否运行
const health = await fetch('http://localhost:3001/health');
expect(health.ok).toBeTruthy();
});
test('用户可完成主要用户流程', async ({ page }) => {
// 监控控制台错误
const consoleErrors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// 执行用户流程
await page.goto('http://localhost:5173');
await page.getByPlaceholder('Search...').fill('test query');
await page.getByRole('button', { name: 'Search' }).click();
// 验证成功
await expect(page.getByTestId('results')).toBeVisible();
// 断言无集成错误
expect(consoleErrors).toHaveLength(0);
expect(consoleErrors.filter(e => e.includes('CORS'))).toHaveLength(0);
expect(consoleErrors.filter(e => e.includes('undefined'))).toHaveLength(0);
});
});When to Use This Skill
何时使用本Skill
- Full-stack TypeScript projects with separate frontend/backend
- When backend and frontend run on different ports/origins
- Debugging CORS issues in development
- Verifying API contract alignment between services
- Before demoing features to users
- Setting up new full-stack projects
- 前后端分离的全栈TypeScript项目
- 后端与前端运行在不同端口/源的场景
- 调试开发环境中的CORS问题
- 验证服务间的API契约一致性
- 向用户演示功能之前
- 搭建新的全栈项目时
Related Skills
相关Skill
- tdd-workflow - Unit testing discipline
- playwright-testing - E2E test standards
- fullstack-expertise - Full-stack development patterns
- tdd-workflow - 单元测试规范
- playwright-testing - E2E测试标准
- fullstack-expertise - 全栈开发模式