
Plan-Loop 框架:让 E2E 测试成为开发的一部分

Plan-Loop 框架的核心思想:
通过 E2E 测试纳入更大的 Loop 范围。
即:将端到端测试作为整个开发循环的一部分,而不仅仅是最后的验证步骤,从而实现更全面的质量闭环。
本文介绍 Plan-Loop 框架的流程概览、核心思想和 Loop 规则。
一、流程概览(决策与循环)
循环结构
┌─────────────────────────────────────────────────────────┐
│ Plan-Loop 循环结构 │
├─────────────────────────────────────────────────────────┤
│ 开始 → 进入循环 │
│ ↓ │
│ [Chat] 进入 Plan 模式 │
│ ↓ │
│ 装载需求规格 / 界面原型,并给出指令进行 Plan │
│ ↓ │
│ 判断条件:是否「持续很久 / 夜间」? │
│ ↓ │
│ 是 → (等待或暂停) │
│ 否 → 继续下一步 │
│ ↓ │
│ [模型] 读取 Plan,按照 Loop 规则进行实现 │
│ ↓ │
│ 判断条件:[Chat] 是否符合要求? │
│ ↓ │
│ 是 → 退出 Plan 模式,开始执行 │
│ 否 → 返回循环开始,重新进入 Plan 模式(或继续迭代) │
└─────────────────────────────────────────────────────────┘流程详解
步骤 1:进入循环
启动开发流程,进入 Plan-Loop 循环。
步骤 2:[Chat] 进入 Plan 模式
通过 Chat(对话)进入 Plan 模式。
步骤 3:装载需求规格 / 界面原型
装载需求规格文档或界面原型,并给出指令进行 Plan。
步骤 4:判断条件 — 是否「持续很久 / 夜间」?
是: 等待或暂停(可能需要人工确认或调整优先级)。
否: 继续下一步。
步骤 5:[模型] 读取 Plan,按照 Loop 规则进行实现
模型(AI 或开发人员)读取 Plan,按照 Loop 规则进行实现。
步骤 6:判断条件 — [Chat] 是否符合要求?
是: 退出 Plan 模式,开始执行。
否: 返回循环开始,重新进入 Plan 模式(或继续迭代)。
循环特点
1. 持续迭代
- 循环不是一次性的
- 可以根据反馈进行多次迭代
- 直到满足要求为止
2. 质量闭环
- 测试用例先于实现
- API 测试确保接口正确
- E2E 测试覆盖用户场景
3. 决策机制
- 通过 Chat 进行决策
- 判断是否继续执行
- 确保质量达标
二、核心思想
传统开发流程的问题
传统开发流程:
需求分析 → 设计 → 编码 → 单元测试 → 集成测试 → E2E 测试 → 部署问题:
- 测试滞后 — E2E 测试在最后进行,发现问题太晚
- 反馈延迟 — 从编码到 E2E 测试,周期长,反馈慢
- 质量孤岛 — 测试和开发分离,质量责任不明确
Plan-Loop 框架的核心思想
通过 E2E 测试纳入更大的 Loop 范围。
即:将端到端测试作为整个开发循环的一部分,而不仅仅是最后的验证步骤,从而实现更全面的质量闭环。
核心变化
从「线性流程」到「循环流程」
传统流程(线性):
需求分析 → 设计 → 编码 → 单元测试 → 集成测试 → E2E 测试 → 部署
Plan-Loop 流程(循环):
Plan → 测试用例 → API 测试 → 实现 → 运行测试 → 浏览器调试 → E2E 测试 → (返回 Plan)关键区别:
- 测试先行 — 测试用例先于实现编写
- 持续反馈 — 每个步骤都有测试验证
- 质量闭环 — E2E 测试覆盖整个开发循环
三、Loop 规则(7 个步骤)
规则概览
按顺序执行以下 7 个步骤:
| 步骤 | 内容 | 位置 |
|---|---|---|
| 1 | Plan 等待确认:在 docs/plans 下根据模板创建 Plan,等待人工或 Chat 确认 | docs/plans |
| 2 | 编写测试用例:在 docs/test-cases 下编写 E2E 测试用例(先于实现) | docs/test-cases |
| 3 | 编写 API 测试:根据 Plan 实现 API 测试,运行成功并断言失败(确保测试有效) | tests/api |
| 4 | 编写实现:编写单元测试 + 实现代码,通过编译 | src |
| 5 | 运行 API 测试:运行测试,并修复失败的测试和其他错误 | tests/api |
| 6 | 浏览器调试:使用 Playwright 调试,确认前端视觉效果和交互功能正常 | - |
| 7 | 编写 E2E 测试:编写 Playwright E2E 测试,覆盖本轮需求的测试场景 | tests/e2e |
步骤详解
步骤 1:Plan 等待确认
目标: 在 docs/plans 下根据模板创建 Plan,等待人工或 Chat 确认。
示例 Plan 模板:
# Plan: [功能名称]
## 需求描述
[描述用户需求]
## 技术方案
[描述技术方案]
## 测试用例
[列出测试用例]
## 实现步骤
[列出实现步骤]
## 验收标准
[列出验收标准]确认机制:
- 人工确认:由产品经理或技术负责人确认
- Chat 确认:通过 Chat 对话确认
步骤 2:编写测试用例
目标: 在 docs/test-cases 下编写 E2E 测试用例(先于实现)。
示例测试用例:
# 测试用例:用户登录
## 场景
用户使用正确的邮箱和密码登录系统
## 步骤
1. 访问 /login 页面
2. 输入邮箱 test@x.com
3. 输入密码 123456
4. 点击登录按钮
## 预期结果
- 显示欢迎消息
- 跳转到 /dashboard 页面
- Cookie 中包含认证 token测试先行原则:
- 测试用例先于实现编写
- 测试用例定义需求
- 测试用例作为验收标准
步骤 3:编写 API 测试
目标: 根据 Plan 实现 API 测试,运行成功并断言失败(确保测试有效)。
示例 API 测试:
// tests/api/login.test.ts
import { test, expect } from '@playwright/test';
test('API: 用户登录', async ({ request }) => {
const response = await request.post('/api/login', {
data: {
email: 'test@x.com',
password: '123456'
}
});
// 断言失败(确保测试有效)
expect(response.status()).toBe(401);
// 断言成功(实现后)
// expect(response.status()).toBe(200);
// expect(await response.json()).toHaveProperty('token');
});断言失败原则:
- 先编写失败的断言
- 确保测试能够检测到错误
- 实现后修改为成功的断言
步骤 4:编写实现
目标: 编写单元测试 + 实现代码,通过编译。
示例实现:
// src/auth/login.ts
export async function login(email: string, password: string) {
// 验证邮箱和密码
if (!email || !password) {
throw new Error('邮箱和密码不能为空');
}
// 查询用户
const user = await db.users.findOne({ email });
if (!user) {
throw new Error('用户不存在');
}
// 验证密码
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('密码错误');
}
// 生成 token
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
return { token };
}单元测试:
// src/auth/login.test.ts
import { login } from './login';
test('login: 邮箱和密码不能为空', async () => {
await expect(login('', '')).rejects.toThrow('邮箱和密码不能为空');
});
test('login: 用户不存在', async () => {
await expect(login('test@x.com', '123456')).rejects.toThrow('用户不存在');
});
test('login: 密码错误', async () => {
await expect(login('test@x.com', 'wrong')).rejects.toThrow('密码错误');
});
test('login: 成功', async () => {
const result = await login('test@x.com', '123456');
expect(result).toHaveProperty('token');
});步骤 5:运行 API 测试
目标: 运行测试,并修复失败的测试和其他错误。
运行测试:
npm run test:api修复测试:
- 如果测试失败,分析原因
- 修复代码或测试
- 重新运行测试
验收标准:
- 所有测试通过
- 代码覆盖率达标
- 无性能问题
步骤 6:浏览器调试
目标: 使用 Playwright 调试,确认前端视觉效果和交互功能正常。
Playwright 调试模式:
npx playwright test --debug调试检查项:
- 页面布局是否正常
- 交互功能是否正常
- 样式是否一致
- 动效是否流畅
示例检查:
// tests/e2e/login.spec.ts
test('登录流程', async ({ page }) => {
await page.goto('/login');
// 检查布局
await expect(page.locator('form')).toBeVisible();
// 输入邮箱和密码"
await page.fill('input[name="email"]', 'test@x.com');
await page.fill('input[name="password"]', '123456');
// 点击登录按钮
await page.click('button[type="submit"]');
// 检查跳转
await expect(page).toHaveURL('/dashboard');
// 检查欢迎消息
await expect(page.locator('.welcome')).toBeVisible();
});步骤 7:编写 E2E 测试
目标: 编写 Playwright E2E 测试,覆盖本轮需求的测试场景。
示例 E2E 测试:
// tests/e2e/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('用户登录', () => {
test('成功登录', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@x.com');
await page.fill('input[name="password"]', '123456');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('.welcome')).toBeVisible();
});
test('邮箱为空', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="password"]', '123456');
await page.click('button[type="submit"]');
await expect(page.locator('.error')).toHaveText('邮箱不能为空');
});
test('密码错误', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@x.com');
await page.fill('input[name="password"]', 'wrong');
await page.click('button[type="submit"]');
await expect(page.locator('.error')).toHaveText('密码错误');
});
});E2E 测试覆盖:
- 正常场景
- 边缘场景
- 错误场景
- 性能场景
四、附加说明
ByPass 权限
定义: 在特定情况下允许跳过某些检查或加速流程。
适用场景:
1. 紧急修复
- 生产环境 Bug 需要紧急修复
- 可以跳过部分测试,快速修复
- 事后补齐测试
2. 实验性功能
- 功能处于实验阶段
- 可以跳过部分测试
- 快速验证想法
3. 性能优化
- 优化不改变功能
- 可以跳过 E2E 测试
- 通过性能测试验证
使用原则:
- 谨慎使用,避免滥用
- 事后补齐测试
- 记录 ByPass 原因
核心优势
1. 测试先行
传统方式:
- 先编写代码
- 后编写测试
- 测试作为补充
Plan-Loop 方式:
- 先编写测试用例
- 后编写代码
- 测试作为需求
优势:
- 测试定义需求
- 测试作为验收标准
- 测试驱动开发
2. 持续反馈
传统方式:
- 编码完成后才测试
- 反馈周期长
- 发现问题晚
Plan-Loop 方式:
- 每个步骤都有测试
- 反馈周期短
- 发现问题早
优势:
- 快速发现错误
- 降低修复成本
- 提高代码质量
3. 质量闭环
传统方式:
- 测试和开发分离
- 质量责任不明确
- 测试覆盖不全
Plan-Loop 方式:
- 测试纳入开发循环
- 质量责任明确
- 测试覆盖全面
优势:
- 质量责任明确
- 测试覆盖全面
- 质量持续提升
实施建议
1. 建立模板
Plan 模板:
- 统一的 Plan 格式
- 标准化的验收标准
- 清晰的实现步骤
测试用例模板:
- 统一的测试用例格式
- 标准化的测试步骤
- 清晰的预期结果
2. 自动化工具
自动化测试:
- 自动运行测试
- 自动生成报告
- 自动通知结果
自动化部署:
- 测试通过后自动部署
- 回滚机制
- 灰度发布
3. 团队协作
角色分工:
- 产品经理:确认 Plan
- 开发人员:实现功能
- 测试人员:编写测试用例
协作机制:
- 代码审查
- 测试审查
- Plan 审查
关键洞察
- 通过 E2E 测试纳入更大的 Loop 范围
收尾金句:
- Plan-Loop 框架:将端到端测试作为整个开发循环的一部分
- 核心思想:测试先行、持续反馈、质量闭环
- Loop 规则:Plan → 测试用例 → API 测试 → 实现 → 运行测试 → 浏览器调试 → E2E 测试
- 从「线性流程」到「循环流程」
- 实现更全面的质量闭环
Plan-Loop 框架
让 E2E 测试成为开发的一部分
