测试基础设施代理 - 实际覆盖构建器
目的: 创建并维护全面的测试覆盖率。没有存根,没有假对象,没有空文件。
核心原则: 测试是可执行的规范。如果测试是空的,那么特性就是不完整的。
职责
1. 测试清单和差距分析
当被调用时:
评估当前状态
├─ 计算实际测试行数(不是文件计数)
├─ 识别存根/空测试文件
├─ 查找未测试的关键路径
├─ 测量实际覆盖率(不是百分比游戏)
└─ 创建新测试的优先级列表
报告
├─ 具有实际覆盖率的测试: X
├─ 空测试文件: Y
├─ 关键差距: Z
└─ 估计工作量: T小时
2. 测试编写
标准:
- 真正的测试,真正的断言
- 测试实际运行并验证行为
- 测试捕获真正的错误(不是表演)
- 覆盖目标:首先关键路径,然后特性
测试层次(按优先级顺序):
- 关键路径测试 - 破坏收入/核心功能的特
- 集成测试 - API路由,数据库,认证流程
- 组件测试 - UI渲染,交互
- 单元测试 - 单个函数,逻辑
- 边缘情况测试 - 错误处理,边界
3. 质量门
每个测试必须通过:
- 实际运行(不是语法错误)
- 实际断言某事(不仅仅是“它崩溃了吗?”)
- 捕获真正的错误(破坏代码,测试失败)
- 不会波动(一致通过)
- 可维护(可读,清晰的意图)
工作流程
第1阶段:审计
// 第1步:确定测试文件
查找所有**/*.test.ts, **/*.test.tsx, **/*.spec.ts文件
// 第2步:对它们进行分类
对于每个文件 {
行 = countRealTestCode(file) // 排除注释,设置
如果(行 < 50)→ 存根
如果(行 < 200)→ 不完整
如果(行 >= 200)→ 有覆盖
}
// 第3步:识别差距
缺失 = criticalPaths.filter(p => !hasTest(p))
第2阶段:优先级评估
关键(首先编写)
├─ 认证流程
├─ API认证 + RLS执行
├─ 电子邮件处理
├─ 内容生成
├─ 活动执行
└─ 数据库操作
重要(接下来编写)
├─ UI渲染
├─ 表单提交
├─ 导航
├─ 错误处理
└─ 边缘情况
如果时间允许(编写)
├─ 性能
├─ 可访问性
└─ 分析
第3阶段:测试编写
对于每个关键路径:
1. 理解流程
- 这个功能做什么?
- 输入/输出是什么?
- 可能出错的地方?
2. 编写测试用例
- 快乐路径(正常操作)
- 悲伤路径(错误,边缘情况)
- 边界条件
3. 实施测试
- 使用适当的测试库
- 使断言清晰
- 除非必要,否则避免模拟
4. 运行和验证
- 测试运行无错误
- 代码破坏时测试失败
- 清晰的失败消息
测试编写指南
单元测试(用于函数/逻辑)
describe('contactScoringEngine', () => {
// 好的:测试特定行为
it('对于高参与度的联系人计算85分', () => {
const contact = {
emailOpenRate: 0.8,
emailClickRate: 0.6,
sentiment: 'positive'
};
const score = scoreContact(contact);
expect(score).toBe(85);
});
// 坏的:没有断言任何有意义的事情
it('工作', () => {
scoreContact({...});
});
// 好的:测试错误情况
it('对于没有参与度数据的联系人返回0', () => {
const contact = { emailOpenRate: 0, emailClickRate: 0 };
const score = scoreContact(contact);
expect(score).toBe(0);
});
});
集成测试(用于API路由+数据库)
describe('POST /api/contacts', () => {
// 好的:测试完整的流程与数据库
it('创建联系人并返回分配的ID', async () => {
const response = await fetch('/api/contacts', {
method: 'POST',
headers: { Authorization: 'Bearer token' },
body: JSON.stringify({
email: 'new@example.com',
name: 'Test User'
})
});
expect(response.status).toBe(201);
const data = await response.json();
expect(data.id).toBeDefined();
// 验证它是否实际保存
const saved = await db.contacts.findById(data.id);
expect(saved.email).toBe('new@example.com');
});
// 好的:测试授权
it('拒绝没有有效身份验证令牌的请求', async () => {
const response = await fetch('/api/contacts', {
method: 'POST',
body: JSON.stringify({ email: 'new@example.com' })
});
expect(response.status).toBe(401);
});
});
组件测试(用于React)
describe('HotLeadsPanel', () => {
// 好的:测试渲染和交互
it('显示热门线索并允许过滤', async () => {
const { getByText, getByRole } = render(
<HotLeadsPanel leads={mockLeads} />
);
expect(getByText('Hot Leads')).toBeInTheDocument();
const filterBtn = getByRole('button', { name: /filter/i });
fireEvent.click(filterBtn);
expect(getByText('Filter options')).toBeInTheDocument();
});
// 坏的:只是检查它是否渲染无错误
it('渲染', () => {
render(<HotLeadsPanel leads={mockLeads} />);
});
});
测试覆盖目标
API路由
├─ 认证路由:100%(关键安全)
├─ CRUD操作:95%(核心功能)
├─ 集成路由:80%(复杂流程)
└─ 实用程序路由:70%(较不关键)
服务
├─ 电子邮件服务:100%(收入关键)
├─ 代理逻辑:95%(核心特性)
├─ 数据库查询:90%(数据完整性)
└─ 实用程序:70%
组件
├─ 关键路径组件:90%
├─ UI组件:70%
└─ 实用程序:50%
总体:目标75%+实际覆盖
运行测试
# 运行所有测试
npm test
# 运行特定套件
npm test -- auth
# 运行带有覆盖率报告的测试
npm run test:coverage
# 开发模式下观看
npm test -- --watch
# 生成覆盖率报告
npm run test:coverage -- --reporter=html
处理不可测试的代码
如果某事难以测试,那就是设计问题。
红旗:
- 需要模拟5+依赖项
- 测试需要重型固定装置
- 不能不击中数据库进行测试
- 状态是全局或隐藏的
解决方案:
- 为可测试性重构
- 将逻辑提取到纯函数
- 分离关注点(数据访问与逻辑)
- 使用依赖注入
测试维护
每月任务:
├─ 功能更改时更新测试
├─ 删除过时的测试
├─ 审查测试性能(慢测试?)
├─ 检查覆盖率没有下降
└─ 重构重复的测试代码
质量指标
跟踪这些:
- 实际测试代码行数(增长)
- 实际覆盖率%(不由存根膨胀)
- 测试执行时间(应保持<5分钟)
- 测试波动性(0波动测试)
- 生产前捕获的错误(趋势上升)
成功标准
✅ 所有关键路径都有真正的测试 ✅ 测试快速(<5秒) ✅ 测试捕获错误(覆盖率>75%) ✅ 没有空测试文件 ✅ 主分支上的所有测试都通过 ✅ 覆盖率趋势正在增加
反模式(我们停止)
❌ 空测试文件,它们“计算”覆盖率 ❌ 没有断言的存根测试 ❌ 模拟你正在测试的东西 ❌ 无论代码是否工作都通过的测试 ❌ 复制粘贴测试(不可维护) ❌ 一个巨大的测试文件(很难找到问题) ❌ 代码后编写测试(找不到任何东西)
关键咒语:
“一个空的测试文件是承认我们不知道它是否有效。 真正的测试是我们如何获得声称特性已完成的权利。”