Loading...
Loading...
Compare original and translation side by side
╔═══════════════════════╗
║ E2E Tests ║ Playwright browser · user journeys
╠═══════════════════════╣
║ API Tests ║ Playwright HTTP · HTTP contracts
╠═══════════════════════╣
║Integration ║ Vitest + Prisma · every branch with real DB
╠═══════════════════════╣
║ Unit Tests ║ Vitest · isolated logic, no I/O
╚═══════════════════════╝ ╔═══════════════════════╗
║ E2E Tests ║ Playwright browser · user journeys
╠═══════════════════════╣
║ API Tests ║ Playwright HTTP · HTTP contracts
╠═══════════════════════╣
║Integration ║ Vitest + Prisma · every branch with real DB
╠═══════════════════════╣
║ Unit Tests ║ Vitest · isolated logic, no I/O
╚═══════════════════════╝| Concern | Unit | Integration | API | E2E |
|---|---|---|---|---|
| Zod field exhaustive validation | Owner | 1–2 wiring checks | 1 per endpoint | No |
| Service method branches | Owner (mocked deps) | Owner (real DB) | No | No |
| Pure utility functions | Owner | No | No | No |
| Mock call argument verification | Owner | No | No | No |
| HTTP contract (status codes, body) | No | No | Owner | No |
| Write verification (POST→GET, DELETE→404) | No | No | Owner | No |
| Authorization enforcement (401, 403) | No | Owner | Owner | No |
| External service calls (mocked) | Owner (vi.mock) | Owner (Wiremock) | No | No |
| DB persistence verification | No | Owner | No | No |
| Complete user journey through UI | No | No | No | Owner |
| Navigation and routing | No | No | No | Owner |
| 测试点 | 单元测试 | 集成测试 | API测试 | E2E测试 |
|---|---|---|---|---|
| Zod字段全量校验 | 责任层 | 1-2项连通性检查 | 每个接口1项检查 | 无 |
| 服务方法分支 | 责任层(依赖mock) | 责任层(真实数据库) | 无 | 无 |
| 纯工具函数 | 责任层 | 无 | 无 | 无 |
| Mock调用参数校验 | 责任层 | 无 | 无 | 无 |
| HTTP契约(状态码、响应体) | 无 | 无 | 责任层 | 无 |
| 写入操作校验(POST→GET、DELETE→404) | 无 | 无 | 责任层 | 无 |
| 鉴权规则校验(401、403) | 无 | 责任层 | 责任层 | 无 |
| 外部服务调用(mock) | 责任层(vi.mock) | 责任层(Wiremock) | 无 | 无 |
| 数据库持久化校验 | 无 | 责任层 | 无 | 无 |
| 完整UI用户流程 | 无 | 无 | 无 | 责任层 |
| 导航与路由 | 无 | 无 | 无 | 责任层 |
<domain>.dto.ts<domain>.service.tsapp/api/<path>/route.ts<domain>.utils.tsprisma/schema.prismasrc/lib/server/errors.tsdata-testid<domain>.dto.ts<domain>.service.tsapp/api/<path>/route.ts<domain>.utils.tsprisma/schema.prismasrc/lib/server/errors.tsdata-testidDoes the code have Zod schemas or pure utility functions?
YES → Unit tests required (always the baseline layer)
Does the code have service methods with branches (if/else, switch, throw)?
YES → Unit tests (mocked) + Integration tests (real DB) required
Why: unit tests prove logic; integration tests prove wiring with real DB.
Together they are not redundant — they cover different failure modes.
Does the code expose HTTP endpoints?
YES → API tests required (one spec file per endpoint)
Does the code have browser UI with data-testid attributes?
YES → E2E tests required (one spec per feature area)代码是否包含Zod模式或纯工具函数?
是 → 必须编写单元测试(始终是基础层)
代码是否包含带分支逻辑的服务方法(if/else、switch、throw)?
是 → 必须编写单元测试(依赖mock)+ 集成测试(真实数据库)
原因:单元测试验证逻辑正确性;集成测试验证与真实数据库的连通性。
两者并不冗余——覆盖的故障模式不同。
代码是否暴露HTTP接口?
是 → 必须编写API测试(每个接口对应一个测试用例文件)
代码是否包含带`data-testid`属性的浏览器UI?
是 → 必须编写E2E测试(每个功能域对应一个测试用例文件)references/unit-testing.mdschema.helper.ts<domain>.unit-factory.tsdescribeit()vitest run src/modules/<domain>/test/unit/references/unit-testing.mdschema.helper.ts<domain>.unit-factory.tsdescribeit()vitest run src/modules/<domain>/test/unit/references/integration-testing.mddescribeDatabaseHelperAuthHelperyarn test:integrationreferences/integration-testing.mddescribeDatabaseHelperAuthHelperyarn test:integrationreferences/api-testing.mdnpx playwright test --project=apireferences/api-testing.mdnpx playwright test --project=apireferences/e2e-testing.mddata-testiddata-testidcounter + timestamp + randomnpx playwright test --project=chromiumreferences/e2e-testing.mddata-testiddata-testidnpx playwright test --project=chromiumlet counter = 0;
function uniqueSuffix(): string {
counter++;
return `${counter}-${Date.now()}-${Math.floor(Math.random() * 100000)}`;
}
// Usage: `test-user-${uniqueSuffix()}@test.com`let counter = 0;
function uniqueSuffix(): string {
counter++;
return `${counter}-${Date.now()}-${Math.floor(Math.random() * 100000)}`;
}
// 用法:`test-user-${uniqueSuffix()}@test.com`// Arrange — set up state
const cookie = await authenticateUser(request);
const dto = generateCreateNoteDto();
// Act — execute the behavior
const response = await executePostNoteRequest(request, dto, cookie);
// Assert — verify the result
expect201Created(response);
expect(response.data.title).toBe(dto.title);// 安排 — 设置状态
const cookie = await authenticateUser(request);
const dto = generateCreateNoteDto();
// 执行 — 运行目标行为
const response = await executePostNoteRequest(request, dto, cookie);
// 断言 — 验证结果
expect201Created(response);
expect(response.data.title).toBe(dto.title);// ✓ Correct — runs even when test throws
test.afterEach(async ({ request }) => {
for (const cookie of cookiesToCleanup) {
await cleanupNotes(request, cookie);
await cleanupUser(request, cookie);
}
cookiesToCleanup.length = 0;
});
// ✗ Wrong — skipped when test throws before reaching cleanup
test("...", async ({ request }) => {
const cookie = await authenticateUser(request);
// ...
await cleanupUser(request, cookie); // This line may never run
});// ✓ 正确 — 即使测试抛出异常也会运行
test.afterEach(async ({ request }) => {
for (const cookie of cookiesToCleanup) {
await cleanupNotes(request, cookie);
await cleanupUser(request, cookie);
}
cookiesToCleanup.length = 0;
});
// ✗ 错误 — 当测试在到达清理代码前抛出异常时会跳过清理
test("...", async ({ request }) => {
const cookie = await authenticateUser(request);
// ...
await cleanupUser(request, cookie); // 这行可能永远不会运行
});beforeAllbeforeAll// ✗ Forbidden at all layers
await new Promise(resolve => setTimeout(resolve, 1000));
await page.waitForTimeout(5000);
// ✓ Use explicit conditions instead
// API/Integration: poll with 400ms interval, 15s timeout
// E2E: locator.waitFor(), page.waitForURL(), expect(locator).toBeVisible()// ✗ 所有层都禁止使用
await new Promise(resolve => setTimeout(resolve, 1000));
await page.waitForTimeout(5000);
// ✓ 改用显式条件
// API/集成测试:间隔400ms轮询,超时时间15s
// E2E测试:locator.waitFor(), page.waitForURL(), expect(locator).toBeVisible().toMatchSnapshot().toMatchInlineSnapshot().toMatchSnapshot().toMatchInlineSnapshot()baseURLbaseURL| File | Contains | Key templates |
|---|---|---|
| 8-step workflow, Zod test patterns, mocking rules | |
| 5-step workflow, parallel-safe cleanup, validation wiring | |
| 8-step workflow, test-owned interfaces, write verification | |
| 9-step workflow, POM rules, waiting strategy | |
| 文件 | 包含内容 | 核心模板 |
|---|---|---|
| 8步工作流、Zod测试模式、mock规则 | |
| 5步工作流、并行安全清理、校验连通性 | |
| 8步工作流、测试专属接口、写入校验 | |
| 9步工作流、POM规则、等待策略 | |
titletitle| Layer | What to test | Test count |
|---|---|---|
| Unit | missing, undefined, null, empty string, min boundary (1 char ✓), max boundary (255 chars ✓), 256 chars ✗, wrong type (number) | 8+ tests |
| Integration | one representative: missing title → 400 VALIDATION_ERROR (proves Zod is wired) | 1 test |
| API | one representative: missing title → 400 (verifies HTTP contract) | 1 test |
| E2E | nothing — E2E tests do not test field validation | 0 tests |
| 测试层 | 测试内容 | 测试数量 |
|---|---|---|
| 单元测试 | 缺失、undefined、null、空字符串、最小边界(1个字符 ✓)、最大边界(255个字符 ✓)、256个字符 ✗、类型错误(数字) | 8+ 个测试 |
| 集成测试 | 一个代表性场景:缺失title → 返回400 VALIDATION_ERROR(证明Zod已连通) | 1个测试 |
| API测试 | 一个代表性场景:缺失title → 返回400(验证HTTP契约) | 1个测试 |
| E2E测试 | 无 —— E2E测试不测试字段校验 | 0个测试 |
notFoundnotFound| Layer | What to test |
|---|---|
| Unit | mock |
| Integration | request with a real non-existent ID in DB → verify 404 response with correct |
| API | |
| E2E | nothing — E2E tests don't test error branches |
| 测试层 | 测试内容 |
|---|---|
| 单元测试 | mock |
| 集成测试 | 用数据库中真实不存在的ID发起请求 → 验证返回404响应,携带正确的 |
| API测试 | |
| E2E测试 | 无 —— E2E测试不测试错误分支 |
| Layer | What to test |
|---|---|
| Unit | mock |
| Integration | user B requests note owned by user A's organization → verify 403 FORBIDDEN response and DB unchanged |
| API | user B requests note owned by user A → verify 403 response |
| E2E | verify user B cannot see user A's notes in the UI (data isolation test) |
| 测试层 | 测试内容 |
|---|---|
| 单元测试 | mock |
| 集成测试 | 用户B请求属于用户A组织的笔记 → 验证返回403 FORBIDDEN响应,数据库未变更 |
| API测试 | 用户B请求属于用户A的笔记 → 验证返回403响应 |
| E2E测试 | 验证用户B在UI中看不到用户A的笔记(数据隔离测试) |
| Layer | Files created | Run command |
|---|---|---|
| Unit | | |
| Integration | | |
| API | | |
| E2E | | |
| 测试层 | 创建的文件 | 运行命令 |
|---|---|---|
| 单元测试 | | |
| 集成测试 | | |
| API测试 | | |
| E2E测试 | | |