testing-strategies

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing Strategies

测试策略

When to use this skill

何时使用该技能

  • 신규 프로젝트: 테스트 전략 수립
  • 품질 문제: 버그 빈번히 발생
  • 리팩토링 전: 안전망 구축
  • CI/CD 구축: 자동화된 테스트
  • 新项目:制定测试策略
  • 质量问题:频繁出现漏洞
  • 重构前:搭建安全保障体系
  • 搭建CI/CD:自动化测试

Instructions

操作指南

Step 1: Test Pyramid 이해

步骤1:理解测试金字塔

       /\
      /E2E\          ← 적음 (느림, 비용 높음)
     /______\
    /        \
   /Integration\    ← 중간
  /____________\
 /              \
/   Unit Tests   \  ← 많음 (빠름, 비용 낮음)
/________________\
비율 가이드:
  • Unit: 70%
  • Integration: 20%
  • E2E: 10%
       /\
      /E2E\          ← 少量(速度慢、成本高)
     /______\
    /        \
   /Integration\    ← 中等数量
  /____________\
 /              \
/   Unit Tests   \  ← 大量(速度快、成本低)
/________________\
比例指南:
  • 单元测试:70%
  • 集成测试:20%
  • E2E测试:10%

Step 2: Unit Testing 전략

步骤2:单元测试策略

Given-When-Then 패턴:
typescript
describe('calculateDiscount', () => {
  it('should apply 10% discount for orders over $100', () => {
    // Given: 주어진 상황
    const order = { total: 150, customerId: '123' };

    // When: 행동을 실행
    const discount = calculateDiscount(order);

    // Then: 결과 검증
    expect(discount).toBe(15);
  });

  it('should not apply discount for orders under $100', () => {
    const order = { total: 50, customerId: '123' };
    const discount = calculateDiscount(order);
    expect(discount).toBe(0);
  });

  it('should throw error for invalid order', () => {
    const order = { total: -10, customerId: '123' };
    expect(() => calculateDiscount(order)).toThrow('Invalid order');
  });
});
Mocking 전략:
typescript
// 외부 의존성 모킹
jest.mock('../services/emailService');
import { sendEmail } from '../services/emailService';

describe('UserService', () => {
  it('should send welcome email on registration', async () => {
    // Arrange
    const mockSendEmail = sendEmail as jest.MockedFunction<typeof sendEmail>;
    mockSendEmail.mockResolvedValueOnce(true);

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

    // Assert
    expect(mockSendEmail).toHaveBeenCalledWith({
      to: 'test@example.com',
      subject: 'Welcome!',
      body: expect.any(String)
    });
  });
});
Given-When-Then模式:
typescript
describe('calculateDiscount', () => {
  it('should apply 10% discount for orders over $100', () => {
    // Given: 给定场景
    const order = { total: 150, customerId: '123' };

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

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

  it('should not apply discount for orders under $100', () => {
    const order = { total: 50, customerId: '123' };
    const discount = calculateDiscount(order);
    expect(discount).toBe(0);
  });

  it('should throw error for invalid order', () => {
    const order = { total: -10, customerId: '123' };
    expect(() => calculateDiscount(order)).toThrow('Invalid order');
  });
});
Mocking策略:
typescript
// 模拟外部依赖
jest.mock('../services/emailService');
import { sendEmail } from '../services/emailService';

describe('UserService', () => {
  it('should send welcome email on registration', async () => {
    // Arrange
    const mockSendEmail = sendEmail as jest.MockedFunction<typeof sendEmail>;
    mockSendEmail.mockResolvedValueOnce(true);

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

    // Assert
    expect(mockSendEmail).toHaveBeenCalledWith({
      to: 'test@example.com',
      subject: 'Welcome!',
      body: expect.any(String)
    });
  });
});

Step 3: Integration Testing

步骤3:集成测试

API 엔드포인트 테스트:
typescript
describe('POST /api/users', () => {
  beforeEach(async () => {
    await db.user.deleteMany();  // Clean DB
  });

  it('should create user with valid data', 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'
    });

    // DB에 실제로 저장되었는지 확인
    const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
    expect(user).toBeTruthy();
  });

  it('should reject duplicate email', 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);
  });
});
API端点测试:
typescript
describe('POST /api/users', () => {
  beforeEach(async () => {
    await db.user.deleteMany();  // 清理数据库
  });

  it('should create user with valid data', 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('should reject duplicate email', 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);
  });
});

Step 4: E2E Testing (Playwright)

步骤4:E2E测试(Playwright)

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

test.describe('User Registration Flow', () => {
  test('should complete full registration process', async ({ page }) => {
    // 1. 홈페이지 방문
    await page.goto('http://localhost:3000');

    // 2. 회원가입 버튼 클릭
    await page.click('text=Sign Up');

    // 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=Welcome')).toBeVisible();

    // 6. 대시보드로 리다이렉트 확인
    await expect(page).toHaveURL('http://localhost:3000/dashboard');

    // 7. 사용자 정보 표시 확인
    await expect(page.locator('text=testuser')).toBeVisible();
  });

  test('should show error for invalid email', 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=Invalid email')).toBeVisible();
  });
});
typescript
import { test, expect } from '@playwright/test';

test.describe('User Registration Flow', () => {
  test('should complete full registration process', async ({ page }) => {
    // 1. 访问首页
    await page.goto('http://localhost:3000');

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

    // 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=Welcome')).toBeVisible();

    // 6. 验证重定向到仪表盘
    await expect(page).toHaveURL('http://localhost:3000/dashboard');

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

  test('should show error for invalid email', 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=Invalid email')).toBeVisible();
  });
});

Step 5: TDD (Test-Driven Development)

步骤5:测试驱动开发(TDD)

Red-Green-Refactor Cycle:
typescript
// 1. RED: 실패하는 테스트 작성
describe('isPalindrome', () => {
  it('should return true for palindrome', () => {
    expect(isPalindrome('racecar')).toBe(true);
  });
});

// 2. GREEN: 테스트 통과하는 최소 코드
function isPalindrome(str: string): boolean {
  return str === str.split('').reverse().join('');
}

// 3. REFACTOR: 코드 개선
function isPalindrome(str: string): boolean {
  const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
  return cleaned === cleaned.split('').reverse().join('');
}

// 4. 추가 테스트 케이스
it('should ignore case and spaces', () => {
  expect(isPalindrome('A man a plan a canal Panama')).toBe(true);
});

it('should return false for non-palindrome', () => {
  expect(isPalindrome('hello')).toBe(false);
});
红-绿-重构循环:
typescript
// 1. RED:编写失败的测试
describe('isPalindrome', () => {
  it('should return true for palindrome', () => {
    expect(isPalindrome('racecar')).toBe(true);
  });
});

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

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

// 4. 添加更多测试用例
it('should ignore case and spaces', () => {
  expect(isPalindrome('A man a plan a canal Panama')).toBe(true);
});

it('should return false for non-palindrome', () => {
  expect(isPalindrome('hello')).toBe(false);
});

Output format

输出格式

테스트 전략 문서

测试策略文档

markdown
undefined
markdown
undefined

Testing Strategy

Testing Strategy

Coverage Goals

覆盖目标

  • Unit Tests: 80%
  • Integration Tests: 60%
  • E2E Tests: Critical user flows
  • 单元测试:80%
  • 集成测试:60%
  • E2E测试:核心用户流程

Test Execution

测试执行

  • Unit: Every commit (local + CI)
  • Integration: Every PR
  • E2E: Before deployment
  • 单元测试:每次提交(本地 + CI)
  • 集成测试:每次PR
  • E2E测试:部署前

Tools

工具

  • Unit: Jest
  • Integration: Supertest
  • E2E: Playwright
  • Coverage: Istanbul/nyc
  • 单元测试:Jest
  • 集成测试:Supertest
  • E2E测试:Playwright
  • 覆盖率:Istanbul/nyc

CI/CD Integration

CI/CD集成

  • GitHub Actions: Run all tests on PR
  • Fail build if coverage < 80%
  • E2E tests on staging environment
undefined
  • GitHub Actions:在PR上运行所有测试
  • 覆盖率低于80%则构建失败
  • 在预发布环境运行E2E测试
undefined

Constraints

约束条件

필수 규칙 (MUST)

必须遵守的规则(MUST)

  1. 테스트 격리: 각 테스트는 독립적
  2. Fast Feedback: Unit tests는 빠르게 (<1분)
  3. Deterministic: 같은 입력 → 같은 결과
  1. 测试隔离:每个测试必须独立
  2. 快速反馈:单元测试需快速完成(<1分钟)
  3. 确定性:相同输入必须得到相同结果

금지 사항 (MUST NOT)

禁止事项(MUST NOT)

  1. 테스트 의존성: 테스트 A가 테스트 B에 의존 금지
  2. 프로덕션 DB: 테스트에서 실제 DB 사용 금지
  3. Sleep/Timeout: 시간 기반 테스트 지양
  1. 测试依赖:禁止测试A依赖测试B的执行结果
  2. 生产数据库:禁止在测试中使用生产数据库
  3. 睡眠/超时:避免使用基于时间的测试

Best practices

最佳实践

  1. AAA 패턴: Arrange-Act-Assert
  2. 테스트 이름: "should ... when ..."
  3. Edge Cases: 경계값, null, 빈 값 테스트
  4. Happy Path + Sad Path: 성공/실패 시나리오 모두
  1. AAA模式:Arrange-Act-Assert(准备-执行-验证)
  2. 测试命名:采用“should ... when ...”格式
  3. 边界用例:测试边界值、null、空值等场景
  4. 正常+异常路径:覆盖成功和失败场景

References

参考资料

Metadata

元数据

버전

版本

  • 현재 버전: 1.0.0
  • 최종 업데이트: 2025-01-01
  • 호환 플랫폼: Claude, ChatGPT, Gemini
  • 当前版本:1.0.0
  • 最后更新:2025-01-01
  • 兼容平台:Claude, ChatGPT, Gemini

관련 스킬

相关技能

  • backend-testing
  • code-review
  • backend-testing
  • code-review

태그

标签

#testing
#test-strategy
#TDD
#unit-test
#integration-test
#E2E
#code-quality
#testing
#test-strategy
#TDD
#unit-test
#integration-test
#E2E
#code-quality

Examples

示例

Example 1: Basic usage

示例1:基础用法

<!-- Add example content here -->
<!-- 在此添加示例内容 -->

Example 2: Advanced usage

示例2:高级用法

<!-- Add advanced example content here -->
<!-- 在此添加高级示例内容 -->