🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx @anthropic-ai/skills install supercent-io/skills-template/testing-strategies

💡 提示:需要 Node.js 和 NPM

测试策略

何时使用此技能

  • 新项目:定义测试策略
  • 质量问题:频繁出现错误
  • 重构前:构建安全网
  • CI/CD 设置:自动化测试

指示

步骤 1:理解测试金字塔

       /\
      /E2E\          ← 少量(慢,成本高)
     /______\
    /        \
   /  集成测试 \       ← 中等
  /____________\
 /              \
/    单元测试     \    ← 大量(快,成本低)
/________________\

比例指南

  • 单元测试:70%
  • 集成测试:20%
  • 端到端测试:10%

步骤 2:单元测试策略

给定-当-那么模式

describe('calculateDiscount', () => {
  it('对于超过 100 美元的订单应应用 10% 折扣', () => {
    // 给定:准备
    const order = { total: 150, customerId: '123' };

    // 当:执行操作
    const discount = calculateDiscount(order);

    // 那么:验证结果
    expect(discount).toBe(15);
  });

  it('对于低于 100 美元的订单不应应用折扣', () => {
    const order = { total: 50, customerId: '123' };
    const discount = calculateDiscount(order);
    expect(discount).toBe(0);
  });

  it('对于无效订单应抛出错误', () => {
    const order = { total: -10, customerId: '123' };
    expect(() => calculateDiscount(order)).toThrow('无效订单');
  });
});

模拟策略

// 模拟外部依赖
jest.mock('../services/emailService');
import { sendEmail } from '../services/emailService';

describe('UserService', () => {
  it('注册时应发送欢迎邮件', async () => {
    // 准备
    const mockSendEmail = sendEmail as jest.MockedFunction<typeof sendEmail>;
    mockSendEmail.mockResolvedValueOnce(true);

    // 执行
    await userService.register({ email: 'test@example.com', password: 'pass' });

    // 断言
    expect(mockSendEmail).toHaveBeenCalledWith({
      to: 'test@example.com',
      subject: '欢迎!',
      body: expect.any(String)
    });
  });
});

步骤 3:集成测试

API 端点测试

describe('POST /api/users', () => {
  beforeEach(async () => {
    await db.user.deleteMany();  // 清理数据库
  });

  it('应使用有效数据创建用户', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        email: 'test@example.com',
        username: 'testuser',
        password: 'Password123!'
      });

    expect(response.status).toBe(201);
    expect(response.body.user).toMatchObject({
      email: 'test@example.com',
      username: 'testuser'
    });

    // 验证它是否实际保存到数据库
    const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
    expect(user).toBeTruthy();
  });

  it('应拒绝重复的邮箱', async () => {
    // 创建第一个用户
    await request(app)
      .post('/api/users')
      .send({ email: 'test@example.com', username: 'user1', password: 'Pass123!' });

    // 尝试重复
    const response = await request(app)
      .post('/api/users')
      .send({ email: 'test@example.com', username: 'user2', password: 'Pass123!' });

    expect(response.status).toBe(409);
  });
});

步骤 4:端到端测试(Playwright)

import { test, expect } from '@playwright/test';

test.describe('用户注册流程', () => {
  test('应完成整个注册过程', async ({ page }) => {
    // 1. 访问首页
    await page.goto('http://localhost:3000');

    // 2. 点击注册按钮
    await page.click('text=注册');

    // 3. 填写表单
    await page.fill('input[name="email"]', 'test@example.com');
    await page.fill('input[name="username"]', 'testuser');
    await page.fill('input[name="password"]', 'Password123!');

    // 4. 提交
    await page.click('button[type="submit"]');

    // 5. 确认成功消息
    await expect(page.locator('text=欢迎')).toBeVisible();

    // 6. 确认重定向到仪表板
    await expect(page).toHaveURL('http://localhost:3000/dashboard');

    // 7. 确认用户信息已显示
    await expect(page.locator('text=testuser')).toBeVisible();
  });

  test('对于无效邮箱应显示错误', async ({ page }) => {
    await page.goto('http://localhost:3000/signup');
    await page.fill('input[name="email"]', 'invalid-email');
    await page.fill('input[name="password"]', 'Password123!');
    await page.click('button[type="submit"]');

    await expect(page.locator('text=无效邮箱')).toBeVisible();
  });
});

步骤 5:测试驱动开发

红-绿-重构循环

// 1. 红:编写一个失败的测试
describe('isPalindrome', () => {
  it('对于回文应返回 true', () => {
    expect(isPalindrome('racecar')).toBe(true);
  });
});

// 2. 绿:编写最少代码通过测试
function isPalindrome(str: string): boolean {
  return str === str.split('').reverse().join('');
}

// 3. 重构:改进代码
function isPalindrome(str: string): boolean {
  const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
  return cleaned === cleaned.split('').reverse().join('');
}

// 4. 添加更多测试用例
it('应忽略大小写和空格', () => {
  expect(isPalindrome('A man a plan a canal Panama')).toBe(true);
});

it('对于非回文应返回 false', () => {
  expect(isPalindrome('hello')).toBe(false);
});

输出格式

测试策略文档

## 测试策略

### 覆盖率目标
- 单元测试:80%
- 集成测试:60%
- 端到端测试:关键用户流程

### 测试执行
- 单元测试:每次提交(本地 + CI)
- 集成测试:每次拉取请求
- 端到端测试:部署前

### 工具
- 单元测试:Jest
- 集成测试:Supertest
- 端到端测试:Playwright
- 覆盖率:Istanbul/nyc

### CI/CD 集成
- GitHub Actions:在拉取请求上运行所有测试
- 如果覆盖率 < 80%,则构建失败
- 在预发布环境上运行端到端测试

约束条件

必需规则(必须遵守)

  1. 测试隔离:每个测试都是独立的
  2. 快速反馈:单元测试应该很快(<1 分钟)
  3. 确定性:相同输入 → 相同结果

禁止事项(不得违反)

  1. 测试依赖:不要让测试 A 依赖于测试 B
  2. 生产数据库:不要在测试中使用真实数据库
  3. 睡眠/超时:避免基于时间的测试

最佳实践

  1. AAA 模式:准备-执行-断言
  2. 测试命名:“应该…当…”
  3. 边缘情况:边界值、null、空值
  4. 快乐路径 + 悲伤路径:包括成功和失败场景

参考资料

元数据

版本

  • 当前版本:1.0.0
  • 最后更新:2025-01-01
  • 兼容平台:Claude, ChatGPT, Gemini

相关技能

标签

#测试 #测试策略 #TDD #单元测试 #集成测试 #端到端测试 #代码质量

示例

示例 1:基本用法

示例 2:高级用法

📄 原始文档

完整文档(英文):

https://skills.sh/supercent-io/skills-template/testing-strategies

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。