name: test-specialist description: 该技能应用于编写测试用例、修复缺陷、分析代码潜在问题或提升JavaScript/TypeScript应用程序的测试覆盖率。适用于单元测试、集成测试、端到端测试、调试运行时错误、逻辑缺陷、性能问题、安全漏洞以及系统性代码分析。
测试专家
概述
为JavaScript/TypeScript应用程序应用系统化的测试方法和调试技术。本技能提供全面的测试策略、缺陷分析框架以及用于识别覆盖率差距和未测试代码的自动化工具。
核心能力
1. 编写测试用例
编写涵盖单元、集成和端到端场景的全面测试。
单元测试方法
使用AAA模式(Arrange-Act-Assert)构建测试:
describe('ExpenseCalculator', () => {
describe('calculateTotal', () => {
test('正确汇总支出金额', () => {
// 准备
const expenses = [
{ amount: 100, category: 'food' },
{ amount: 50, category: 'transport' },
{ amount: 25, category: 'entertainment' }
];
// 执行
const total = calculateTotal(expenses);
// 断言
expect(total).toBe(175);
});
test('处理空支出列表', () => {
expect(calculateTotal([])).toBe(0);
});
test('处理负金额', () => {
const expenses = [
{ amount: 100, category: 'food' },
{ amount: -50, category: 'refund' }
];
expect(calculateTotal(expenses)).toBe(50);
});
});
});
关键原则:
- 每个测试只测试一个行为
- 覆盖正常路径、边界情况和错误条件
- 使用描述性测试名称解释场景
- 保持测试独立和隔离
集成测试方法
测试组件如何协同工作,包括数据库、API和服务交互:
describe('ExpenseAPI 集成测试', () => {
beforeAll(async () => {
await database.connect(TEST_DB_URL);
});
afterAll(async () => {
await database.disconnect();
});
beforeEach(async () => {
await database.clear();
await seedTestData();
});
test('POST /expenses 创建支出并更新总额', async () => {
const response = await request(app)
.post('/api/expenses')
.send({
amount: 50,
category: 'food',
description: '午餐'
})
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(Number),
amount: 50,
category: 'food'
});
// 验证数据库状态
const total = await getTotalExpenses();
expect(total).toBe(50);
});
});
端到端测试方法
使用Playwright或Cypress等工具测试完整的用户工作流:
test('用户可以从头到尾跟踪支出', async ({ page }) => {
// 导航到应用
await page.goto('/');
// 添加新支出
await page.click('[data-testid="add-expense-btn"]');
await page.fill('[data-testid="amount"]', '50.00');
await page.selectOption('[data-testid="category"]', 'food');
await page.fill('[data-testid="description"]', '午餐');
await page.click('[data-testid="submit"]');
// 验证支出出现在列表中
await expect(page.locator('[data-testid="expense-item"]')).toContainText('午餐');
await expect(page.locator('[data-testid="total"]')).toContainText('$50.00');
});
2. 系统性缺陷分析
应用结构化调试方法识别和修复问题。
五步分析流程
-
复现:可靠地复现缺陷
- 记录触发缺陷的确切步骤
- 识别所需环境/状态
- 记录预期与实际行为
-
隔离:缩小问题范围
- 通过代码路径进行二分查找
- 创建最小复现案例
- 移除无关依赖
-
根本原因分析:确定根本原因
- 跟踪执行流程
- 检查假设和前置条件
- 审查最近更改(git blame)
-
修复实现:实施解决方案
- 先编写失败测试(TDD)
- 实施修复
- 验证测试通过
-
验证:确保完整性
- 运行完整测试套件
- 测试边界情况
- 验证无回归
常见缺陷模式
竞态条件:
// 测试并发操作
test('正确处理并发更新', async () => {
const promises = Array.from({ length: 100 }, () =>
incrementExpenseCount()
);
await Promise.all(promises);
expect(getExpenseCount()).toBe(100);
});
空值/未定义错误:
// 测试空值安全性
test.each([null, undefined, '', 0, false])
('处理无效输入:%p', (input) => {
expect(() => processExpense(input)).toThrow('无效支出');
});
差一错误:
// 显式测试边界
describe('分页', () => {
test('处理空列表', () => {
expect(paginate([], 1, 10)).toEqual([]);
});
test('处理单个项目', () => {
expect(paginate([item], 1, 10)).toEqual([item]);
});
test('处理部分项目的最后一页', () => {
const items = Array.from({ length: 25 }, (_, i) => i);
expect(paginate(items, 3, 10)).toHaveLength(5);
});
});
3. 识别潜在问题
主动识别问题,防止其成为缺陷。
安全漏洞
测试常见安全问题:
describe('安全性', () => {
test('防止SQL注入', async () => {
const malicious = "'; DROP TABLE expenses; --";
await expect(
searchExpenses(malicious)
).resolves.not.toThrow();
});
test('清理描述中的XSS', () => {
const xss = '<script>alert("xss")</script>';
const expense = createExpense({ description: xss });
expect(expense.description).not.toContain('<script>');
});
test('支出操作需要身份验证', async () => {
await request(app)
.post('/api/expenses')
.send({ amount: 50 })
.expect(401);
});
});
性能问题
测试性能问题:
test('高效处理大型支出列表', () => {
const largeList = Array.from({ length: 10000 }, (_, i) => ({
amount: i,
category: 'test'
}));
const start = performance.now();
const total = calculateTotal(largeList);
const duration = performance.now() - start;
expect(duration).toBeLessThan(100); // 应在<100ms内完成
expect(total).toBe(49995000);
});
逻辑错误
使用参数化测试捕获边界情况:
test.each([
// [输入, 预期, 描述]
[[10, 20, 30], 60, '正常正值'],
[[0, 0, 0], 0, '全零值'],
[[-10, 20, -5], 5, '正负混合值'],
[[0.1, 0.2], 0.3, '小数精度'],
[[Number.MAX_SAFE_INTEGER], Number.MAX_SAFE_INTEGER, '大数字'],
])('calculateTotal(%p) = %p (%s)', (amounts, expected, description) => {
const expenses = amounts.map(amount => ({ amount, category: 'test' }));
expect(calculateTotal(expenses)).toBeCloseTo(expected);
});
4. 测试覆盖率分析
使用自动化工具识别测试覆盖率差距。
查找未测试代码
运行提供的脚本识别没有测试的源文件:
python3 scripts/find_untested_code.py src
脚本将:
- 扫描源目录中的所有代码文件
- 识别哪些文件缺少对应的测试文件
- 按类型(组件、服务、工具等)分类未测试文件
- 优先处理最需要测试的文件
解释:
- API/服务:高优先级 - 测试业务逻辑和数据操作
- 模型:高优先级 - 测试数据验证和转换
- 钩子:中优先级 - 测试有状态行为
- 组件:中优先级 - 测试复杂UI逻辑
- 工具:低优先级 - 根据需要测试复杂函数
分析覆盖率报告
生成覆盖率后运行覆盖率分析脚本:
# 生成覆盖率(使用Jest示例)
npm test -- --coverage
# 分析覆盖率差距
python3 scripts/analyze_coverage.py coverage/coverage-final.json
脚本识别:
- 低于覆盖率阈值(默认80%)的文件
- 语句、分支和函数覆盖率百分比
- 需要改进的优先文件
覆盖率目标:
- 关键路径:90%+ 覆盖率
- 业务逻辑:85%+ 覆盖率
- UI组件:75%+ 覆盖率
- 工具:70%+ 覆盖率
5. 测试维护与质量
确保测试保持价值且易于维护。
测试代码质量原则
DRY(不要重复自己):
// 提取通用设置
function createTestExpense(overrides = {}) {
return {
amount: 50,
category: 'food',
description: '测试支出',
date: new Date('2024-01-01'),
...overrides
};
}
test('按类别过滤', () => {
const expenses = [
createTestExpense({ category: 'food' }),
createTestExpense({ category: 'transport' }),
];
// ...
});
清晰的测试数据:
// 差:魔法数字
expect(calculateDiscount(100, 0.15)).toBe(85);
// 好:命名常量
const ORIGINAL_PRICE = 100;
const DISCOUNT_RATE = 0.15;
const EXPECTED_PRICE = 85;
expect(calculateDiscount(ORIGINAL_PRICE, DISCOUNT_RATE)).toBe(EXPECTED_PRICE);
避免测试相互依赖:
// 差:测试依赖执行顺序
let sharedState;
test('测试1', () => {
sharedState = { value: 1 };
});
test('测试2', () => {
expect(sharedState.value).toBe(1); // 依赖测试1
});
// 好:独立测试
test('测试1', () => {
const state = { value: 1 };
expect(state.value).toBe(1);
});
test('测试2', () => {
const state = { value: 1 };
expect(state.value).toBe(1);
});
工作流决策树
遵循此决策树确定测试方法:
-
添加新功能?
- 是 → 先写测试(TDD)
- 编写失败测试
- 实现功能
- 验证测试通过
- 重构
- 否 → 转到步骤2
- 是 → 先写测试(TDD)
-
修复缺陷?
- 是 → 应用缺陷分析流程
- 复现缺陷
- 编写展示缺陷的失败测试
- 修复实现
- 验证测试通过
- 否 → 转到步骤3
- 是 → 应用缺陷分析流程
-
改进测试覆盖率?
- 是 → 使用覆盖率工具
- 运行
find_untested_code.py识别差距 - 在覆盖率报告上运行
analyze_coverage.py - 优先处理关键路径
- 为未测试代码编写测试
- 运行
- 否 → 转到步骤4
- 是 → 使用覆盖率工具
-
分析代码质量?
- 是 → 系统性审查
- 检查安全漏洞
- 测试边界情况和错误处理
- 验证性能特征
- 审查错误处理
- 是 → 系统性审查
测试框架和工具
推荐技术栈
单元/集成测试:
- Jest或Vitest作为测试运行器
- Testing Library用于React组件
- Supertest用于API测试
- MSW(Mock Service Worker)用于API模拟
端到端测试:
- Playwright或Cypress
- 页面对象模型模式
覆盖率:
- Istanbul(内置在Jest/Vitest中)
- JSON格式的覆盖率报告
运行测试
# 运行所有测试
npm test
# 运行覆盖率测试
npm test -- --coverage
# 运行特定测试文件
npm test -- ExpenseCalculator.test.ts
# 运行监视模式
npm test -- --watch
# 运行端到端测试
npm run test:e2e
参考文档
有关详细模式和技术的参考:
references/testing_patterns.md- 全面的测试模式、最佳实践和代码示例references/bug_analysis.md- 深入的缺陷分析框架、常见缺陷模式和调试技术
这些参考包含大量示例和高级技术。在以下情况下加载它们:
- 处理复杂测试场景
- 需要特定模式实现
- 调试异常问题
- 寻求特定情况的最佳实践
脚本
analyze_coverage.py
分析Jest/Istanbul覆盖率报告以识别差距:
python3 scripts/analyze_coverage.py [coverage-file]
如果未指定,自动查找常见的覆盖率文件位置。
输出:
- 低于覆盖率阈值的文件
- 语句、分支和函数覆盖率百分比
- 需要改进的优先文件
find_untested_code.py
查找没有对应测试文件的源文件:
python3 scripts/find_untested_code.py [src-dir] [--pattern test|spec]
输出:
- 源文件和测试文件总数
- 测试文件覆盖率百分比
- 按类型(API、服务、组件等)分类的未测试文件
- 优先级建议
最佳实践总结
- 先写测试(TDD)添加新功能时
- 测试行为,而非实现 - 测试应在重构后仍然有效
- 保持测试独立 - 测试间无共享状态
- 使用描述性名称 - 测试名称应解释场景
- 覆盖边界情况 - 空值、空集、边界值、错误条件
- 模拟外部依赖 - 测试应快速可靠
- 保持高覆盖率 - 关键代码80%+
- 立即修复失败测试 - 绝不提交损坏的测试
- 重构测试 - 应用与生产代码相同的质量标准
- 使用工具 - 自动化覆盖率分析和差距识别