Files
novalon-website/docs/testing/test-tiering-best-practices.md
T

10 KiB

分层测试最佳实践

概述

本文档提供分层测试系统的最佳实践,帮助团队构建高效、可靠的测试体系。

核心原则

1. 质量左移

在需求分析和设计阶段就考虑测试策略,而不是在开发完成后才补充测试。

实践:

  • 在需求文档中明确测试要求
  • 在设计评审中讨论可测试性
  • 开发过程中同步编写测试

2. 测试金字塔

遵循测试金字塔原则,保持合理的测试比例:

        /\
       /  \     E2E测试 (10%)
      /____\
     /      \    集成测试 (30%)
    /________\
   /          \  单元测试 (60%)
  /____________\

实践:

  • 单元测试:快速、独立、覆盖核心逻辑
  • 集成测试:验证组件间交互
  • E2E测试:验证关键用户流程

3. 快速反馈

确保测试能够快速提供反馈,帮助开发人员快速定位问题。

实践:

  • 快速层测试在5分钟内完成
  • 标准层测试在30分钟内完成
  • 深度层测试可以接受较长执行时间

测试分层策略

快速层设计

目标: 在5分钟内验证核心功能

包含内容:

  1. 冒烟测试 (Smoke Tests)

    • 验证应用能够正常启动
    • 验证关键页面能够加载
    • 验证核心API能够响应
  2. API测试

    • 验证API端点的正确性
    • 验证数据格式和结构
    • 验证错误处理
  3. 基础功能测试

    • 验证用户登录/登出
    • 验证基本CRUD操作
    • 验证权限控制

最佳实践:

  • 每个测试文件不超过3个测试用例
  • 每个测试用例执行时间不超过10秒
  • 使用mock数据替代真实数据库

示例:

test.describe('用户认证快速测试 @smoke @critical', () => {
  test('应该能够成功登录', async ({ page }) => {
    await page.goto('/login');
    await page.fill('[data-testid="email"]', 'admin@example.com');
    await page.fill('[data-testid="password"]', 'password123');
    await page.click('[data-testid="login-btn"]');
    
    await expect(page).toHaveURL('/dashboard');
  });

  test('应该能够成功登出', async ({ page }) => {
    await page.goto('/dashboard');
    await page.click('[data-testid="logout-btn"]');
    
    await expect(page).toHaveURL('/login');
  });
});

标准层设计

目标: 在30分钟内验证大部分功能

包含内容:

  1. 功能测试 (Functional Tests)

    • 验证完整的用户流程
    • 验证表单验证
    • 验证业务规则
  2. 响应式测试 (Responsive Tests)

    • 验证不同屏幕尺寸下的布局
    • 验证移动端和桌面端的交互
    • 验证触摸和鼠标事件
  3. 管理后台测试 (Admin Tests)

    • 验证内容管理功能
    • 验证用户管理功能
    • 验证系统配置

最佳实践:

  • 每个测试文件包含5-10个测试用例
  • 每个测试用例执行时间不超过30秒
  • 使用Page Object Model模式

示例:

test.describe('新闻管理功能测试 @admin @regression', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/admin/news');
  });

  test('应该能够创建新闻', async ({ page }) => {
    await page.click('[data-testid="create-news-btn"]');
    await page.fill('[data-testid="news-title"]', '测试新闻');
    await page.fill('[data-testid="news-content"]', '新闻内容');
    await page.click('[data-testid="save-btn"]');
    
    await expect(page.locator('[data-testid="success-message"]')).toBeVisible();
  });

  test('应该能够编辑新闻', async ({ page }) => {
    await page.click('[data-testid="edit-news-1"]');
    await page.fill('[data-testid="news-title"]', '更新后的标题');
    await page.click('[data-testid="save-btn"]');
    
    await expect(page.locator('[data-testid="news-title"]')).toHaveValue('更新后的标题');
  });

  test('应该能够删除新闻', async ({ page }) => {
    await page.click('[data-testid="delete-news-1"]');
    await page.click('[data-testid="confirm-btn"]');
    
    await expect(page.locator('[data-testid="news-1"]')).not.toBeVisible();
  });
});

深度层设计

目标: 在发布前进行全面验证

包含内容:

  1. 视觉回归测试 (Visual Regression Tests)

    • 验证UI与设计稿一致
    • 验证样式和布局
    • 验证跨浏览器一致性
  2. 性能测试 (Performance Tests)

    • 验证页面加载时间
    • 验证API响应时间
    • 验证资源加载优化
  3. 完整回归测试 (Full Regression Tests)

    • 验证所有已知功能
    • 验证边界情况
    • 验证错误处理

最佳实践:

  • 使用截图对比工具
  • 使用性能监控工具
  • 在夜间或周末执行

示例:

test.describe('首页视觉回归测试 @visual @regression', () => {
  test('桌面端首页应该与基准一致', async ({ page }) => {
    await page.setViewportSize({ width: 1280, height: 720 });
    await page.goto('/');
    
    await expect(page).toHaveScreenshot('homepage-desktop.png');
  });

  test('移动端首页应该与基准一致', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 });
    await page.goto('/');
    
    await expect(page).toHaveScreenshot('homepage-mobile.png');
  });
});

测试标记策略

标记分类

优先级标记

  • @critical - 关键测试,必须通过
  • @high - 高优先级测试
  • @medium - 中等优先级测试
  • @low - 低优先级测试

类型标记

  • @smoke - 冒烟测试
  • @regression - 回归测试
  • @functional - 功能测试
  • @api - API测试
  • @visual - 视觉测试
  • @performance - 性能测试

平台标记

  • @desktop - 桌面端测试
  • @mobile - 移动端测试
  • @tablet - 平板端测试

功能标记

  • @auth - 认证相关测试
  • @admin - 管理后台测试
  • @content - 内容管理测试
  • @user - 用户功能测试

标记使用规则

  1. 每个测试套件至少有一个标记
  2. 关键测试必须标记为 @critical
  3. 冒烟测试必须标记为 @smoke
  4. 回归测试必须标记为 @regression

性能优化

减少测试执行时间

1. 并行执行

// playwright.config.tiered.ts
{
  fullyParallel: true,
  workers: '75%',
}

2. 减少等待时间

// 不推荐
await page.waitForTimeout(5000);

// 推荐
await page.waitForSelector('[data-testid="result"]', { timeout: 5000 });

3. 使用快速选择器

// 不推荐
await page.click('div > div > button');

// 推荐
await page.click('[data-testid="submit-btn"]');

4. 复用浏览器上下文

test.describe('用户管理测试', () => {
  test.use({ storageState: '.auth/admin.json' });
  
  test('应该能够创建用户', async ({ page }) => {
    // 测试逻辑
  });
});

优化测试数据

1. 使用固定数据

const testUser = {
  email: 'test@example.com',
  password: 'password123',
};

test('应该能够登录', async ({ page }) => {
  await page.fill('[data-testid="email"]', testUser.email);
  await page.fill('[data-testid="password"]', testUser.password);
});

2. 使用测试数据库

test.beforeEach(async () => {
  await db.reset();
  await db.seed(testData);
});

3. 清理测试数据

test.afterEach(async () => {
  await db.cleanup();
});

可维护性

Page Object Model

使用Page Object Model模式提高测试的可维护性:

// pages/LoginPage.ts
export class LoginPage {
  constructor(private page: Page) {}

  async login(email: string, password: string) {
    await this.page.fill('[data-testid="email"]', email);
    await this.page.fill('[data-testid="password"]', password);
    await this.page.click('[data-testid="login-btn"]');
  }

  async expectLoggedIn() {
    await expect(this.page).toHaveURL('/dashboard');
  }
}

// tests/login.spec.ts
test('应该能够登录', async ({ page }) => {
  const loginPage = new LoginPage(page);
  
  await loginPage.login('admin@example.com', 'password123');
  await loginPage.expectLoggedIn();
});

测试数据管理

使用专门的测试数据管理器:

// utils/test-data.ts
export const TestData = {
  users: {
    admin: {
      email: 'admin@example.com',
      password: 'password123',
      role: 'admin',
    },
    user: {
      email: 'user@example.com',
      password: 'password123',
      role: 'user',
    },
  },
  news: {
    valid: {
      title: '测试新闻',
      content: '新闻内容',
    },
    invalid: {
      title: '',
      content: '',
    },
  },
};

配置管理

使用环境变量管理测试配置:

// config/environments.ts
export const getEnvironment = () => {
  const env = process.env.NODE_ENV || 'development';
  
  return {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    timeout: parseInt(process.env.TEST_TIMEOUT || '30000'),
    retries: parseInt(process.env.TEST_RETRIES || '2'),
    headless: process.env.HEADLESS !== 'false',
  };
};

持续改进

定期审查

每月进行一次测试审查:

  1. 检查测试覆盖率
  2. 识别慢速测试
  3. 评估测试有效性
  4. 清理无用测试

性能监控

持续监控测试性能:

  1. 记录测试执行时间
  2. 识别性能趋势
  3. 优化慢速测试
  4. 调整测试分层

反馈收集

收集测试反馈:

  1. 开发人员反馈
  2. 测试失败分析
  3. 用户反馈
  4. 生产问题追踪

常见问题

Q: 如何确定测试应该放在哪一层?

A: 根据测试的执行时间和重要性:

  • 执行时间<30秒且是关键功能 → 快速层
  • 执行时间<60秒 → 标准层
  • 执行时间>60秒或需要完整回归 → 深度层

Q: 测试失败时如何处理?

A: 按照以下优先级处理:

  1. 快速层测试失败 → 立即修复
  2. 标准层测试失败 → 在合并PR前修复
  3. 深度层测试失败 → 在发布前修复

Q: 如何减少测试执行时间?

A: 采用以下策略:

  1. 并行执行测试
  2. 减少不必要的等待
  3. 优化选择器
  4. 拆分大测试
  5. 使用mock数据

Q: 如何提高测试稳定性?

A: 遵循以下原则:

  1. 使用稳定的等待策略
  2. 避免硬编码的等待时间
  3. 使用data-testid选择器
  4. 清理测试数据
  5. 增加重试次数

参考资源