前端测试深度实践:从单元测试到E2E测试的完整测试解决方案

引言

前端测试是现代Web开发中不可或缺的重要环节,它不仅能够保证代码质量,还能提高开发效率,降低维护成本。随着前端应用复杂度的不断增加,建立完善的测试体系变得越来越重要。

本文将深入探讨前端测试的各个层面,从单元测试到集成测试,再到端到端测试,提供一套完整的前端测试解决方案。我们将涵盖测试策略设计、工具选择、最佳实践以及自动化测试流程等关键内容。

1. 前端测试概述

1.1 测试金字塔理论

前端测试遵循测试金字塔理论,从底层到顶层分为:

  • 单元测试(Unit Tests): 测试独立的函数、组件或模块
  • 集成测试(Integration Tests): 测试组件间的交互
  • 端到端测试(E2E Tests): 测试完整的用户流程

1.2 测试策略管理器

javascript 复制代码
// 前端测试策略管理器
class FrontendTestingManager {
  constructor(config = {}) {
    this.config = {
      unitTestRatio: 0.7,
      integrationTestRatio: 0.2,
      e2eTestRatio: 0.1,
      coverageThreshold: {
        statements: 80,
        branches: 75,
        functions: 80,
        lines: 80
      },
      testEnvironments: ['jsdom', 'node', 'browser'],
      enableParallelTesting: true,
      enableWatchMode: true,
      enableCoverageReport: true,
      enableVisualTesting: true,
      enablePerformanceTesting: true,
      enableAccessibilityTesting: true,
      ...config
    };
    
    this.testSuites = new Map();
    this.testResults = [];
    this.coverageData = null;
    this.testMetrics = {
      totalTests: 0,
      passedTests: 0,
      failedTests: 0,
      skippedTests: 0,
      executionTime: 0,
      coverage: {}
    };
    
    this.init();
  }
  
  // 初始化
  init() {
    this.setupTestEnvironments();
    this.registerTestSuites();
    this.setupTestReporting();
    this.setupCoverageTracking();
  }
  
  // 设置测试环境
  setupTestEnvironments() {
    this.environments = {
      unit: {
        framework: 'jest',
        environment: 'jsdom',
        setupFiles: ['<rootDir>/src/setupTests.js'],
        testMatch: ['**/__tests__/**/*.test.{js,jsx,ts,tsx}'],
        collectCoverageFrom: [
          'src/**/*.{js,jsx,ts,tsx}',
          '!src/**/*.d.ts',
          '!src/index.js',
          '!src/serviceWorker.js'
        ]
      },
      integration: {
        framework: 'jest',
        environment: 'jsdom',
        testMatch: ['**/__tests__/**/*.integration.{js,jsx,ts,tsx}'],
        setupFilesAfterEnv: ['<rootDir>/src/setupIntegrationTests.js']
      },
      e2e: {
        framework: 'playwright',
        browsers: ['chromium', 'firefox', 'webkit'],
        testDir: './e2e',
        testMatch: '**/*.e2e.{js,ts}',
        baseURL: 'http://localhost:3000'
      }
    };
  }
  
  // 注册测试套件
  registerTestSuites() {
    // 单元测试套件
    this.testSuites.set('unit', {
      type: 'unit',
      runner: new UnitTestRunner(this.environments.unit),
      priority: 1,
      parallel: true
    });
    
    // 集成测试套件
    this.testSuites.set('integration', {
      type: 'integration',
      runner: new IntegrationTestRunner(this.environments.integration),
      priority: 2,
      parallel: true
    });
    
    // E2E测试套件
    this.testSuites.set('e2e', {
      type: 'e2e',
      runner: new E2ETestRunner(this.environments.e2e),
      priority: 3,
      parallel: false
    });
    
    // 视觉测试套件
    if (this.config.enableVisualTesting) {
      this.testSuites.set('visual', {
        type: 'visual',
        runner: new VisualTestRunner(),
        priority: 4,
        parallel: true
      });
    }
    
    // 性能测试套件
    if (this.config.enablePerformanceTesting) {
      this.testSuites.set('performance', {
        type: 'performance',
        runner: new PerformanceTestRunner(),
        priority: 5,
        parallel: false
      });
    }
    
    // 可访问性测试套件
    if (this.config.enableAccessibilityTesting) {
      this.testSuites.set('accessibility', {
        type: 'accessibility',
        runner: new AccessibilityTestRunner(),
        priority: 6,
        parallel: true
      });
    }
  }
  
  // 运行所有测试
  async runAllTests(options = {}) {
    const startTime = Date.now();
    
    try {
      console.log('🧪 Starting test execution...');
      
      // 重置测试指标
      this.resetTestMetrics();
      
      // 按优先级排序测试套件
      const sortedSuites = Array.from(this.testSuites.entries())
        .sort(([, a], [, b]) => a.priority - b.priority);
      
      // 运行测试套件
      for (const [name, suite] of sortedSuites) {
        if (options.suites && !options.suites.includes(name)) {
          continue;
        }
        
        console.log(`📋 Running ${name} tests...`);
        
        const suiteResult = await this.runTestSuite(name, suite, options);
        this.processTestResult(name, suiteResult);
      }
      
      // 生成测试报告
      const report = await this.generateTestReport();
      
      // 检查覆盖率阈值
      this.validateCoverageThreshold();
      
      const endTime = Date.now();
      this.testMetrics.executionTime = endTime - startTime;
      
      console.log('✅ Test execution completed');
      console.log('📊 Test Summary:', this.getTestSummary());
      
      return {
        success: this.testMetrics.failedTests === 0,
        metrics: this.testMetrics,
        report: report
      };
    } catch (error) {
      console.error('❌ Test execution failed:', error);
      throw error;
    }
  }
  
  // 运行单个测试套件
  async runTestSuite(name, suite, options) {
    try {
      const result = await suite.runner.run({
        ...options,
        parallel: suite.parallel && this.config.enableParallelTesting
      });
      
      return {
        suite: name,
        type: suite.type,
        success: result.success,
        tests: result.tests || [],
        coverage: result.coverage || null,
        duration: result.duration || 0,
        errors: result.errors || []
      };
    } catch (error) {
      return {
        suite: name,
        type: suite.type,
        success: false,
        tests: [],
        coverage: null,
        duration: 0,
        errors: [error.message]
      };
    }
  }
  
  // 处理测试结果
  processTestResult(suiteName, result) {
    this.testResults.push(result);
    
    // 更新测试指标
    if (result.tests) {
      result.tests.forEach(test => {
        this.testMetrics.totalTests++;
        
        switch (test.status) {
          case 'passed':
            this.testMetrics.passedTests++;
            break;
          case 'failed':
            this.testMetrics.failedTests++;
            break;
          case 'skipped':
            this.testMetrics.skippedTests++;
            break;
        }
      });
    }
    
    // 合并覆盖率数据
    if (result.coverage) {
      this.mergeCoverageData(result.coverage);
    }
    
    // 记录错误
    if (result.errors && result.errors.length > 0) {
      console.error(`❌ Errors in ${suiteName} tests:`, result.errors);
    }
  }
  
  // 合并覆盖率数据
  mergeCoverageData(coverage) {
    if (!this.coverageData) {
      this.coverageData = coverage;
    } else {
      // 简单的覆盖率合并逻辑
      Object.keys(coverage).forEach(file => {
        if (this.coverageData[file]) {
          // 合并文件覆盖率
          this.coverageData[file] = this.mergeCoverageFile(
            this.coverageData[file],
            coverage[file]
          );
        } else {
          this.coverageData[file] = coverage[file];
        }
      });
    }
  }
  
  // 合并单个文件的覆盖率
  mergeCoverageFile(existing, newCoverage) {
    return {
      statements: this.mergeCoverageMetric(existing.statements, newCoverage.statements),
      branches: this.mergeCoverageMetric(existing.branches, newCoverage.branches),
      functions: this.mergeCoverageMetric(existing.functions, newCoverage.functions),
      lines: this.mergeCoverageMetric(existing.lines, newCoverage.lines)
    };
  }
  
  // 合并覆盖率指标
  mergeCoverageMetric(existing, newMetric) {
    return {
      total: Math.max(existing.total, newMetric.total),
      covered: Math.max(existing.covered, newMetric.covered),
      percentage: Math.max(existing.percentage, newMetric.percentage)
    };
  }
  
  // 验证覆盖率阈值
  validateCoverageThreshold() {
    if (!this.coverageData || !this.config.enableCoverageReport) {
      return;
    }
    
    const overallCoverage = this.calculateOverallCoverage();
    const threshold = this.config.coverageThreshold;
    
    const violations = [];
    
    Object.keys(threshold).forEach(metric => {
      if (overallCoverage[metric] < threshold[metric]) {
        violations.push({
          metric,
          actual: overallCoverage[metric],
          expected: threshold[metric]
        });
      }
    });
    
    if (violations.length > 0) {
      console.warn('⚠️ Coverage threshold violations:', violations);
      this.testMetrics.coverageViolations = violations;
    }
    
    this.testMetrics.coverage = overallCoverage;
  }
  
  // 计算整体覆盖率
  calculateOverallCoverage() {
    if (!this.coverageData) {
      return { statements: 0, branches: 0, functions: 0, lines: 0 };
    }
    
    const totals = {
      statements: { total: 0, covered: 0 },
      branches: { total: 0, covered: 0 },
      functions: { total: 0, covered: 0 },
      lines: { total: 0, covered: 0 }
    };
    
    Object.values(this.coverageData).forEach(fileCoverage => {
      Object.keys(totals).forEach(metric => {
        totals[metric].total += fileCoverage[metric].total;
        totals[metric].covered += fileCoverage[metric].covered;
      });
    });
    
    const result = {};
    Object.keys(totals).forEach(metric => {
      const { total, covered } = totals[metric];
      result[metric] = total > 0 ? Math.round((covered / total) * 100) : 0;
    });
    
    return result;
  }
  
  // 生成测试报告
  async generateTestReport() {
    const report = {
      timestamp: new Date().toISOString(),
      summary: this.getTestSummary(),
      suites: this.testResults,
      coverage: this.coverageData ? this.calculateOverallCoverage() : null,
      metrics: this.testMetrics,
      environment: {
        node: process.version,
        platform: process.platform,
        ci: process.env.CI || false
      }
    };
    
    // 保存报告到文件
    if (this.config.enableTestReporting) {
      await this.saveTestReport(report);
    }
    
    return report;
  }
  
  // 保存测试报告
  async saveTestReport(report) {
    try {
      const fs = require('fs').promises;
      const path = require('path');
      
      const reportDir = path.join(process.cwd(), 'test-reports');
      await fs.mkdir(reportDir, { recursive: true });
      
      // JSON报告
      const jsonReportPath = path.join(reportDir, 'test-report.json');
      await fs.writeFile(jsonReportPath, JSON.stringify(report, null, 2));
      
      // HTML报告
      const htmlReport = this.generateHTMLReport(report);
      const htmlReportPath = path.join(reportDir, 'test-report.html');
      await fs.writeFile(htmlReportPath, htmlReport);
      
      console.log('📄 Test reports saved:', { jsonReportPath, htmlReportPath });
    } catch (error) {
      console.error('Failed to save test report:', error);
    }
  }
  
  // 生成HTML报告
  generateHTMLReport(report) {
    return `
<!DOCTYPE html>
<html>
<head>
    <title>Test Report</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .summary { background: #f5f5f5; padding: 15px; border-radius: 5px; }
        .suite { margin: 20px 0; border: 1px solid #ddd; border-radius: 5px; }
        .suite-header { background: #e9e9e9; padding: 10px; font-weight: bold; }
        .test { padding: 10px; border-bottom: 1px solid #eee; }
        .passed { color: green; }
        .failed { color: red; }
        .skipped { color: orange; }
        .coverage { margin: 20px 0; }
        .coverage-bar { width: 200px; height: 20px; background: #ddd; border-radius: 10px; overflow: hidden; }
        .coverage-fill { height: 100%; background: linear-gradient(to right, red, yellow, green); }
    </style>
</head>
<body>
    <h1>Frontend Test Report</h1>
    
    <div class="summary">
        <h2>Summary</h2>
        <p>Total Tests: ${report.summary.total}</p>
        <p>Passed: <span class="passed">${report.summary.passed}</span></p>
        <p>Failed: <span class="failed">${report.summary.failed}</span></p>
        <p>Skipped: <span class="skipped">${report.summary.skipped}</span></p>
        <p>Success Rate: ${report.summary.successRate}%</p>
        <p>Execution Time: ${report.summary.duration}ms</p>
    </div>
    
    ${report.coverage ? `
    <div class="coverage">
        <h2>Coverage</h2>
        <p>Statements: ${report.coverage.statements}%</p>
        <p>Branches: ${report.coverage.branches}%</p>
        <p>Functions: ${report.coverage.functions}%</p>
        <p>Lines: ${report.coverage.lines}%</p>
    </div>
    ` : ''}
    
    <div class="suites">
        <h2>Test Suites</h2>
        ${report.suites.map(suite => `
        <div class="suite">
            <div class="suite-header">${suite.suite} (${suite.type})</div>
            ${suite.tests.map(test => `
            <div class="test ${test.status}">
                ${test.name} - ${test.status}
                ${test.duration ? ` (${test.duration}ms)` : ''}
                ${test.error ? `<br><small>${test.error}</small>` : ''}
            </div>
            `).join('')}
        </div>
        `).join('')}
    </div>
    
    <footer>
        <p>Generated at: ${report.timestamp}</p>
    </footer>
</body>
</html>
    `;
  }
  
  // 设置测试报告
  setupTestReporting() {
    // 配置测试报告选项
    this.reportingConfig = {
      formats: ['json', 'html', 'junit'],
      outputDir: './test-reports',
      includeConsoleOutput: true,
      includeCoverage: this.config.enableCoverageReport
    };
  }
  
  // 设置覆盖率跟踪
  setupCoverageTracking() {
    if (!this.config.enableCoverageReport) return;
    
    // 配置覆盖率收集
    this.coverageConfig = {
      collectCoverageFrom: [
        'src/**/*.{js,jsx,ts,tsx}',
        '!src/**/*.d.ts',
        '!src/**/*.stories.{js,jsx,ts,tsx}',
        '!src/**/*.test.{js,jsx,ts,tsx}'
      ],
      coverageReporters: ['text', 'lcov', 'html', 'json'],
      coverageDirectory: './coverage'
    };
  }
  
  // 重置测试指标
  resetTestMetrics() {
    this.testMetrics = {
      totalTests: 0,
      passedTests: 0,
      failedTests: 0,
      skippedTests: 0,
      executionTime: 0,
      coverage: {},
      coverageViolations: []
    };
    
    this.testResults = [];
    this.coverageData = null;
  }
  
  // 获取测试摘要
  getTestSummary() {
    const { totalTests, passedTests, failedTests, skippedTests, executionTime } = this.testMetrics;
    
    return {
      total: totalTests,
      passed: passedTests,
      failed: failedTests,
      skipped: skippedTests,
      successRate: totalTests > 0 ? Math.round((passedTests / totalTests) * 100) : 0,
      duration: executionTime
    };
  }
  
  // 监听模式
  async startWatchMode() {
    if (!this.config.enableWatchMode) {
      console.log('Watch mode is disabled');
      return;
    }
    
    console.log('👀 Starting watch mode...');
    
    const chokidar = require('chokidar');
    
    // 监听源文件变化
    const watcher = chokidar.watch(['src/**/*.{js,jsx,ts,tsx}', '**/*.test.{js,jsx,ts,tsx}'], {
      ignored: /node_modules/,
      persistent: true
    });
    
    let debounceTimer;
    
    watcher.on('change', (path) => {
      console.log(`📝 File changed: ${path}`);
      
      // 防抖处理
      clearTimeout(debounceTimer);
      debounceTimer = setTimeout(async () => {
        try {
          console.log('🔄 Re-running tests...');
          await this.runAllTests({ watch: true });
        } catch (error) {
          console.error('Watch mode test execution failed:', error);
        }
      }, 1000);
    });
    
    return watcher;
  }
  
  // 获取状态
  getStatus() {
    return {
      config: this.config,
      environments: this.environments,
      testSuites: Array.from(this.testSuites.keys()),
      metrics: this.testMetrics,
      lastResults: this.testResults.slice(-5)
    };
  }
  
  // 清理
  cleanup() {
    this.testSuites.clear();
    this.testResults = [];
    this.coverageData = null;
    this.resetTestMetrics();
  }
}

2.2 React组件测试实践

javascript 复制代码
// React组件测试示例
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { configureStore } from '@reduxjs/toolkit';
import userEvent from '@testing-library/user-event';

// 示例组件:用户登录表单
const LoginForm = ({ onSubmit, loading = false }) => {
  const [formData, setFormData] = React.useState({
    email: '',
    password: ''
  });
  
  const [errors, setErrors] = React.useState({});
  
  const validateForm = () => {
    const newErrors = {};
    
    if (!formData.email) {
      newErrors.email = 'Email is required';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = 'Email is invalid';
    }
    
    if (!formData.password) {
      newErrors.password = 'Password is required';
    } else if (formData.password.length < 6) {
      newErrors.password = 'Password must be at least 6 characters';
    }
    
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (validateForm()) {
      onSubmit(formData);
    }
  };
  
  const handleChange = (field) => (e) => {
    setFormData(prev => ({
      ...prev,
      [field]: e.target.value
    }));
    
    // 清除错误
    if (errors[field]) {
      setErrors(prev => ({
        ...prev,
        [field]: ''
      }));
    }
  };
  
  return (
    <form onSubmit={handleSubmit} data-testid="login-form">
      <div>
        <label htmlFor="email">Email:</label>
        <input
          id="email"
          type="email"
          value={formData.email}
          onChange={handleChange('email')}
          data-testid="email-input"
          aria-invalid={!!errors.email}
          aria-describedby={errors.email ? 'email-error' : undefined}
        />
        {errors.email && (
          <div id="email-error" role="alert" data-testid="email-error">
            {errors.email}
          </div>
        )}
      </div>
      
      <div>
        <label htmlFor="password">Password:</label>
        <input
          id="password"
          type="password"
          value={formData.password}
          onChange={handleChange('password')}
          data-testid="password-input"
          aria-invalid={!!errors.password}
          aria-describedby={errors.password ? 'password-error' : undefined}
        />
        {errors.password && (
          <div id="password-error" role="alert" data-testid="password-error">
            {errors.password}
          </div>
        )}
      </div>
      
      <button
        type="submit"
        disabled={loading}
        data-testid="submit-button"
      >
        {loading ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
};

// 测试套件
describe('LoginForm', () => {
  let mockOnSubmit;
  
  beforeEach(() => {
    mockOnSubmit = jest.fn();
  });
  
  afterEach(() => {
    jest.clearAllMocks();
  });
  
  // 基础渲染测试
  test('renders login form with all fields', () => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    expect(screen.getByTestId('login-form')).toBeInTheDocument();
    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
  });
  
  // 用户交互测试
  test('allows user to enter email and password', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const emailInput = screen.getByTestId('email-input');
    const passwordInput = screen.getByTestId('password-input');
    
    await user.type(emailInput, 'test@example.com');
    await user.type(passwordInput, 'password123');
    
    expect(emailInput).toHaveValue('test@example.com');
    expect(passwordInput).toHaveValue('password123');
  });
  
  // 表单验证测试
  test('shows validation errors for empty fields', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const submitButton = screen.getByTestId('submit-button');
    await user.click(submitButton);
    
    await waitFor(() => {
      expect(screen.getByTestId('email-error')).toHaveTextContent('Email is required');
      expect(screen.getByTestId('password-error')).toHaveTextContent('Password is required');
    });
    
    expect(mockOnSubmit).not.toHaveBeenCalled();
  });
  
  test('shows validation error for invalid email', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const emailInput = screen.getByTestId('email-input');
    const submitButton = screen.getByTestId('submit-button');
    
    await user.type(emailInput, 'invalid-email');
    await user.click(submitButton);
    
    await waitFor(() => {
      expect(screen.getByTestId('email-error')).toHaveTextContent('Email is invalid');
    });
  });
  
  test('shows validation error for short password', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const passwordInput = screen.getByTestId('password-input');
    const submitButton = screen.getByTestId('submit-button');
    
    await user.type(passwordInput, '123');
    await user.click(submitButton);
    
    await waitFor(() => {
      expect(screen.getByTestId('password-error')).toHaveTextContent('Password must be at least 6 characters');
    });
  });
  
  // 成功提交测试
  test('submits form with valid data', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const emailInput = screen.getByTestId('email-input');
    const passwordInput = screen.getByTestId('password-input');
    const submitButton = screen.getByTestId('submit-button');
    
    await user.type(emailInput, 'test@example.com');
    await user.type(passwordInput, 'password123');
    await user.click(submitButton);
    
    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123'
      });
    });
  });
  
  // 加载状态测试
  test('shows loading state when submitting', () => {
    render(<LoginForm onSubmit={mockOnSubmit} loading={true} />);
    
    const submitButton = screen.getByTestId('submit-button');
    
    expect(submitButton).toBeDisabled();
    expect(submitButton).toHaveTextContent('Logging in...');
  });
  
  // 错误清除测试
  test('clears errors when user starts typing', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const emailInput = screen.getByTestId('email-input');
    const submitButton = screen.getByTestId('submit-button');
    
    // 触发验证错误
    await user.click(submitButton);
    
    await waitFor(() => {
      expect(screen.getByTestId('email-error')).toBeInTheDocument();
    });
    
    // 开始输入,错误应该清除
    await user.type(emailInput, 't');
    
    await waitFor(() => {
      expect(screen.queryByTestId('email-error')).not.toBeInTheDocument();
    });
  });
  
  // 可访问性测试
  test('has proper accessibility attributes', () => {
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const emailInput = screen.getByTestId('email-input');
    const passwordInput = screen.getByTestId('password-input');
    
    expect(emailInput).toHaveAttribute('aria-invalid', 'false');
    expect(passwordInput).toHaveAttribute('aria-invalid', 'false');
  });
  
  test('associates error messages with inputs', async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={mockOnSubmit} />);
    
    const submitButton = screen.getByTestId('submit-button');
    await user.click(submitButton);
    
    await waitFor(() => {
      const emailInput = screen.getByTestId('email-input');
      const emailError = screen.getByTestId('email-error');
      
      expect(emailInput).toHaveAttribute('aria-invalid', 'true');
      expect(emailInput).toHaveAttribute('aria-describedby', 'email-error');
      expect(emailError).toHaveAttribute('role', 'alert');
    });
  });
  
  // 快照测试
  test('matches snapshot', () => {
    const { container } = render(<LoginForm onSubmit={mockOnSubmit} />);
    expect(container.firstChild).toMatchSnapshot();
  });
  
  test('matches snapshot with loading state', () => {
    const { container } = render(<LoginForm onSubmit={mockOnSubmit} loading={true} />);
    expect(container.firstChild).toMatchSnapshot();
  });
});

// Redux连接组件测试
describe('LoginForm with Redux', () => {
  let store;
  let mockOnSubmit;
  
  beforeEach(() => {
    mockOnSubmit = jest.fn();
    
    // 创建测试store
    store = configureStore({
      reducer: {
        auth: (state = { loading: false, error: null }, action) => {
          switch (action.type) {
            case 'auth/loginStart':
              return { ...state, loading: true, error: null };
            case 'auth/loginSuccess':
              return { ...state, loading: false, error: null };
            case 'auth/loginFailure':
              return { ...state, loading: false, error: action.payload };
            default:
              return state;
          }
        }
      }
    });
  });
  
  const renderWithProviders = (component) => {
    return render(
      <Provider store={store}>
        <BrowserRouter>
          {component}
        </BrowserRouter>
      </Provider>
    );
  };
  
  test('integrates with Redux store', () => {
    renderWithProviders(<LoginForm onSubmit={mockOnSubmit} />);
    
    // 验证组件正常渲染
    expect(screen.getByTestId('login-form')).toBeInTheDocument();
    
    // 验证初始状态
    const state = store.getState();
    expect(state.auth.loading).toBe(false);
    expect(state.auth.error).toBe(null);
  });
});

3. 集成测试深度实践

3.1 集成测试运行器

javascript 复制代码
// 集成测试运行器
class IntegrationTestRunner {
  constructor(config = {}) {
    this.config = {
      framework: 'jest',
      environment: 'jsdom',
      testMatch: ['**/__tests__/**/*.integration.{js,jsx,ts,tsx}'],
      setupFilesAfterEnv: [],
      testTimeout: 30000,
      maxWorkers: 1,
      ...config
    };
    
    this.testSuites = [];
    this.mockServices = new Map();
    this.testDatabase = null;
    this.testServer = null;
    
    this.init();
  }
  
  // 初始化
  init() {
    this.setupTestEnvironment();
    this.setupMockServices();
    this.setupTestDatabase();
    this.setupTestServer();
  }
  
  // 设置测试环境
  setupTestEnvironment() {
    // 配置测试环境变量
    process.env.NODE_ENV = 'test';
    process.env.API_BASE_URL = 'http://localhost:3001';
    process.env.DATABASE_URL = 'sqlite::memory:';
    
    // 配置全局测试设置
    global.testConfig = {
      apiTimeout: 5000,
      dbTimeout: 3000,
      retryAttempts: 3
    };
  }
  
  // 设置Mock服务
  setupMockServices() {
    this.mockServices.set('api', {
      baseUrl: process.env.API_BASE_URL,
      endpoints: new Map(),
      middleware: [],
      requests: []
    });
    
    this.mockServices.set('auth', {
      users: new Map(),
      sessions: new Map(),
      tokens: new Map()
    });
    
    this.mockServices.set('storage', {
      data: new Map(),
      config: {
        maxSize: 1024 * 1024, // 1MB
        ttl: 3600000 // 1 hour
      }
    });
  }
  
  // 设置测试数据库
  setupTestDatabase() {
    this.testDatabase = {
      connection: null,
      tables: new Map(),
      
      async connect() {
        // 模拟数据库连接
        this.connection = {
          connected: true,
          database: 'test_db',
          tables: this.tables
        };
        
        console.log('📊 Test database connected');
        return this.connection;
      },
      
      async disconnect() {
        if (this.connection) {
          this.connection.connected = false;
          this.connection = null;
        }
        
        console.log('📊 Test database disconnected');
      },
      
      async seed(data = {}) {
        // 填充测试数据
        Object.keys(data).forEach(table => {
          this.tables.set(table, data[table]);
        });
        
        console.log('🌱 Test database seeded');
      },
      
      async clean() {
        // 清理测试数据
        this.tables.clear();
        console.log('🧹 Test database cleaned');
      },
      
      async query(sql, params = []) {
        // 模拟数据库查询
        console.log('🔍 Database query:', sql, params);
        
        // 简单的查询模拟
        if (sql.includes('SELECT')) {
          const tableName = sql.match(/FROM\s+(\w+)/i)?.[1];
          return this.tables.get(tableName) || [];
        }
        
        if (sql.includes('INSERT')) {
          const tableName = sql.match(/INTO\s+(\w+)/i)?.[1];
          const table = this.tables.get(tableName) || [];
          table.push(params);
          this.tables.set(tableName, table);
          return { insertId: table.length };
        }
        
        return { affectedRows: 1 };
      }
    };
  }
  
  // 设置测试服务器
  setupTestServer() {
    this.testServer = {
      port: 3001,
      routes: new Map(),
      middleware: [],
      server: null,
      
      async start() {
        // 模拟服务器启动
        this.server = {
          listening: true,
          port: this.port,
          routes: this.routes
        };
        
        console.log(`🚀 Test server started on port ${this.port}`);
        return this.server;
      },
      
      async stop() {
        if (this.server) {
          this.server.listening = false;
          this.server = null;
        }
        
        console.log('🛑 Test server stopped');
      },
      
      addRoute(method, path, handler) {
        const key = `${method.toUpperCase()} ${path}`;
        this.routes.set(key, handler);
      },
      
      addMiddleware(middleware) {
        this.middleware.push(middleware);
      }
    };
    
    // 添加默认路由
    this.setupDefaultRoutes();
  }
  
  // 设置默认路由
  setupDefaultRoutes() {
    // 健康检查
    this.testServer.addRoute('GET', '/health', () => ({
      status: 'ok',
      timestamp: new Date().toISOString()
    }));
    
    // 用户认证
    this.testServer.addRoute('POST', '/auth/login', (req) => {
      const { email, password } = req.body;
      const authService = this.mockServices.get('auth');
      
      // 简单的认证逻辑
      if (email === 'test@example.com' && password === 'password123') {
        const token = `token_${Date.now()}`;
        const user = { id: 1, email, name: 'Test User' };
        
        authService.tokens.set(token, user);
        
        return {
          success: true,
          token,
          user
        };
      }
      
      return {
        success: false,
        error: 'Invalid credentials'
      };
    });
    
    // 用户信息
    this.testServer.addRoute('GET', '/auth/me', (req) => {
      const token = req.headers.authorization?.replace('Bearer ', '');
      const authService = this.mockServices.get('auth');
      const user = authService.tokens.get(token);
      
      if (user) {
        return { success: true, user };
      }
      
      return { success: false, error: 'Unauthorized' };
    });
    
    // 数据API
    this.testServer.addRoute('GET', '/api/users', async () => {
      const users = await this.testDatabase.query('SELECT * FROM users');
      return { success: true, data: users };
    });
    
    this.testServer.addRoute('POST', '/api/users', async (req) => {
      const result = await this.testDatabase.query(
        'INSERT INTO users (name, email) VALUES (?, ?)',
        [req.body.name, req.body.email]
      );
      
      return {
        success: true,
        data: { id: result.insertId, ...req.body }
      };
    });
  }
  
  // 运行集成测试
  async run(options = {}) {
    const startTime = Date.now();
    
    try {
      console.log('🔗 Running integration tests...');
      
      // 启动测试环境
      await this.startTestEnvironment();
      
      // 运行测试
      const result = await this.runTests(options);
      
      // 清理测试环境
      await this.cleanupTestEnvironment();
      
      const endTime = Date.now();
      result.duration = endTime - startTime;
      
      console.log(`✅ Integration tests completed in ${result.duration}ms`);
      
      return result;
    } catch (error) {
      console.error('❌ Integration test execution failed:', error);
      
      // 确保清理
      await this.cleanupTestEnvironment();
      
      throw error;
    }
  }
  
  // 启动测试环境
  async startTestEnvironment() {
    console.log('🏗️ Setting up integration test environment...');
    
    // 连接测试数据库
    await this.testDatabase.connect();
    
    // 填充测试数据
    await this.seedTestData();
    
    // 启动测试服务器
    await this.testServer.start();
    
    // 设置全局fetch mock
    this.setupFetchMock();
    
    console.log('✅ Integration test environment ready');
  }
  
  // 填充测试数据
  async seedTestData() {
    const testData = {
      users: [
        { id: 1, name: 'John Doe', email: 'john@example.com', role: 'user' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'admin' },
        { id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'user' }
      ],
      posts: [
        { id: 1, title: 'Test Post 1', content: 'Content 1', userId: 1 },
        { id: 2, title: 'Test Post 2', content: 'Content 2', userId: 2 }
      ],
      comments: [
        { id: 1, content: 'Test comment 1', postId: 1, userId: 2 },
        { id: 2, content: 'Test comment 2', postId: 1, userId: 3 }
      ]
    };
    
    await this.testDatabase.seed(testData);
  }
  
  // 设置Fetch Mock
  setupFetchMock() {
    const originalFetch = global.fetch;
    
    global.fetch = jest.fn(async (url, options = {}) => {
      const method = options.method || 'GET';
      const routeKey = `${method.toUpperCase()} ${url.replace(process.env.API_BASE_URL, '')}`;
      const handler = this.testServer.routes.get(routeKey);
      
      if (handler) {
        const req = {
          url,
          method,
          headers: options.headers || {},
          body: options.body ? JSON.parse(options.body) : null
        };
        
        try {
          const response = await handler(req);
          
          return {
            ok: true,
            status: 200,
            json: async () => response,
            text: async () => JSON.stringify(response)
          };
        } catch (error) {
          return {
            ok: false,
            status: 500,
            json: async () => ({ error: error.message }),
            text: async () => JSON.stringify({ error: error.message })
          };
        }
      }
      
      // 回退到原始fetch
      return originalFetch(url, options);
    });
  }
  
  // 运行测试
  async runTests(options = {}) {
    const jest = require('jest');
    
    const jestConfig = {
      testEnvironment: this.config.environment,
      testMatch: this.config.testMatch,
      setupFilesAfterEnv: this.config.setupFilesAfterEnv,
      testTimeout: this.config.testTimeout,
      maxWorkers: this.config.maxWorkers,
      verbose: true,
      runInBand: true // 集成测试通常需要串行运行
    };
    
    return new Promise((resolve, reject) => {
      jest.runCLI(jestConfig, [process.cwd()])
        .then(({ results }) => {
          const tests = [];
          const errors = [];
          
          results.testResults.forEach(testFile => {
            testFile.testResults.forEach(test => {
              tests.push({
                name: test.title,
                file: testFile.testFilePath,
                status: test.status,
                duration: test.duration,
                error: test.failureMessages.length > 0 ? test.failureMessages[0] : null
              });
            });
            
            if (testFile.failureMessage) {
              errors.push(testFile.failureMessage);
            }
          });
          
          resolve({
            success: results.success,
            tests,
            errors,
            numTotalTests: results.numTotalTests,
            numPassedTests: results.numPassedTests,
            numFailedTests: results.numFailedTests
          });
        })
        .catch(reject);
    });
  }
  
  // 清理测试环境
  async cleanupTestEnvironment() {
    console.log('🧹 Cleaning up integration test environment...');
    
    // 停止测试服务器
    await this.testServer.stop();
    
    // 清理测试数据库
    await this.testDatabase.clean();
    await this.testDatabase.disconnect();
    
    // 恢复全局对象
    if (global.fetch && global.fetch.mockRestore) {
      global.fetch.mockRestore();
    }
    
    // 清理Mock服务
    this.mockServices.forEach(service => {
      if (typeof service === 'object' && service !== null) {
        Object.keys(service).forEach(key => {
          if (service[key] instanceof Map) {
            service[key].clear();
          } else if (Array.isArray(service[key])) {
            service[key].length = 0;
          }
        });
      }
    });
    
    console.log('✅ Integration test environment cleaned up');
  }
  
  // 获取状态
  getStatus() {
    return {
      config: this.config,
      database: {
        connected: this.testDatabase.connection?.connected || false,
        tables: this.testDatabase.tables.size
      },
      server: {
        running: this.testServer.server?.listening || false,
        routes: this.testServer.routes.size
      },
      mockServices: Array.from(this.mockServices.keys())
    };
  }
}

3.2 用户管理集成测试示例

javascript 复制代码
// __tests__/integration/user-management.integration.test.js
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { store } from '../../src/store';
import UserManagement from '../../src/components/UserManagement';
import { IntegrationTestRunner } from '../../src/utils/testing';

// 集成测试套件
describe('User Management Integration Tests', () => {
  let testRunner;
  
  beforeAll(async () => {
    // 初始化集成测试环境
    testRunner = new IntegrationTestRunner({
      testTimeout: 30000,
      setupFilesAfterEnv: ['<rootDir>/src/setupTests.js']
    });
    
    await testRunner.startTestEnvironment();
  });
  
  afterAll(async () => {
    // 清理集成测试环境
    await testRunner.cleanupTestEnvironment();
  });
  
  beforeEach(async () => {
    // 每个测试前重置数据
    await testRunner.testDatabase.clean();
    await testRunner.seedTestData();
  });
  
  // 测试组件
  const renderUserManagement = () => {
    return render(
      <Provider store={store}>
        <BrowserRouter>
          <UserManagement />
        </BrowserRouter>
      </Provider>
    );
  };
  
  describe('用户认证流程', () => {
    test('应该能够成功登录并获取用户信息', async () => {
      renderUserManagement();
      
      // 查找登录表单
      const emailInput = screen.getByLabelText(/邮箱/i);
      const passwordInput = screen.getByLabelText(/密码/i);
      const loginButton = screen.getByRole('button', { name: /登录/i });
      
      // 输入登录信息
      fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
      fireEvent.change(passwordInput, { target: { value: 'password123' } });
      
      // 点击登录
      fireEvent.click(loginButton);
      
      // 等待登录成功
      await waitFor(() => {
        expect(screen.getByText(/欢迎, Test User/i)).toBeInTheDocument();
      });
      
      // 验证用户信息显示
      expect(screen.getByText('test@example.com')).toBeInTheDocument();
      
      // 验证API调用
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:3001/auth/login',
        expect.objectContaining({
          method: 'POST',
          headers: expect.objectContaining({
            'Content-Type': 'application/json'
          }),
          body: JSON.stringify({
            email: 'test@example.com',
            password: 'password123'
          })
        })
      );
    });
    
    test('应该处理登录失败的情况', async () => {
      renderUserManagement();
      
      const emailInput = screen.getByLabelText(/邮箱/i);
      const passwordInput = screen.getByLabelText(/密码/i);
      const loginButton = screen.getByRole('button', { name: /登录/i });
      
      // 输入错误的登录信息
      fireEvent.change(emailInput, { target: { value: 'wrong@example.com' } });
      fireEvent.change(passwordInput, { target: { value: 'wrongpassword' } });
      
      fireEvent.click(loginButton);
      
      // 等待错误消息显示
      await waitFor(() => {
        expect(screen.getByText(/登录失败/i)).toBeInTheDocument();
      });
      
      // 验证用户未登录
      expect(screen.queryByText(/欢迎/i)).not.toBeInTheDocument();
    });
    
    test('应该能够获取当前用户信息', async () => {
      // 先模拟已登录状态
      const authService = testRunner.mockServices.get('auth');
      const token = 'test_token_123';
      const user = { id: 1, email: 'test@example.com', name: 'Test User' };
      authService.tokens.set(token, user);
      
      // 设置localStorage中的token
      localStorage.setItem('authToken', token);
      
      renderUserManagement();
      
      // 等待用户信息加载
      await waitFor(() => {
        expect(screen.getByText(/Test User/i)).toBeInTheDocument();
      });
      
      // 验证API调用
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:3001/auth/me',
        expect.objectContaining({
          headers: expect.objectContaining({
            'Authorization': 'Bearer test_token_123'
          })
        })
      );
    });
  });
  
  describe('用户数据管理', () => {
    test('应该能够获取用户列表', async () => {
      renderUserManagement();
      
      // 等待用户列表加载
      await waitFor(() => {
        expect(screen.getByText('John Doe')).toBeInTheDocument();
        expect(screen.getByText('Jane Smith')).toBeInTheDocument();
        expect(screen.getByText('Bob Johnson')).toBeInTheDocument();
      });
      
      // 验证用户信息显示
      expect(screen.getByText('john@example.com')).toBeInTheDocument();
      expect(screen.getByText('jane@example.com')).toBeInTheDocument();
      expect(screen.getByText('bob@example.com')).toBeInTheDocument();
    });
    
    test('应该能够创建新用户', async () => {
      renderUserManagement();
      
      // 查找创建用户表单
      const nameInput = screen.getByLabelText(/姓名/i);
      const emailInput = screen.getByLabelText(/邮箱/i);
      const createButton = screen.getByRole('button', { name: /创建用户/i });
      
      // 输入新用户信息
      fireEvent.change(nameInput, { target: { value: 'New User' } });
      fireEvent.change(emailInput, { target: { value: 'new@example.com' } });
      
      // 点击创建
      fireEvent.click(createButton);
      
      // 等待用户创建成功
      await waitFor(() => {
        expect(screen.getByText('New User')).toBeInTheDocument();
      });
      
      // 验证API调用
      expect(global.fetch).toHaveBeenCalledWith(
        'http://localhost:3001/api/users',
        expect.objectContaining({
          method: 'POST',
          body: JSON.stringify({
            name: 'New User',
            email: 'new@example.com'
          })
        })
      );
      
      // 验证数据库中的数据
      const users = await testRunner.testDatabase.query('SELECT * FROM users');
      expect(users).toContainEqual(
        expect.objectContaining({
          name: 'New User',
          email: 'new@example.com'
        })
      );
    });
    
    test('应该能够编辑用户信息', async () => {
      renderUserManagement();
      
      // 等待用户列表加载
      await waitFor(() => {
        expect(screen.getByText('John Doe')).toBeInTheDocument();
      });
      
      // 点击编辑按钮
      const editButton = screen.getByTestId('edit-user-1');
      fireEvent.click(editButton);
      
      // 修改用户信息
      const nameInput = screen.getByDisplayValue('John Doe');
      fireEvent.change(nameInput, { target: { value: 'John Smith' } });
      
      // 保存修改
      const saveButton = screen.getByRole('button', { name: /保存/i });
      fireEvent.click(saveButton);
      
      // 等待修改成功
      await waitFor(() => {
        expect(screen.getByText('John Smith')).toBeInTheDocument();
      });
      
      // 验证原名称不再显示
      expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
    });
    
    test('应该能够删除用户', async () => {
      renderUserManagement();
      
      // 等待用户列表加载
      await waitFor(() => {
        expect(screen.getByText('Bob Johnson')).toBeInTheDocument();
      });
      
      // 点击删除按钮
      const deleteButton = screen.getByTestId('delete-user-3');
      fireEvent.click(deleteButton);
      
      // 确认删除
      const confirmButton = screen.getByRole('button', { name: /确认删除/i });
      fireEvent.click(confirmButton);
      
      // 等待用户被删除
      await waitFor(() => {
        expect(screen.queryByText('Bob Johnson')).not.toBeInTheDocument();
      });
    });
  });
  
  describe('组件集成', () => {
    test('应该正确处理组件间的数据流', async () => {
      renderUserManagement();
      
      // 测试用户选择
      await waitFor(() => {
        expect(screen.getByText('John Doe')).toBeInTheDocument();
      });
      
      // 选择用户
      const userRow = screen.getByTestId('user-row-1');
      fireEvent.click(userRow);
      
      // 验证用户详情显示
      await waitFor(() => {
        expect(screen.getByTestId('user-details')).toBeInTheDocument();
      });
      
      // 验证用户详情内容
      expect(screen.getByText('用户ID: 1')).toBeInTheDocument();
      expect(screen.getByText('角色: user')).toBeInTheDocument();
    });
    
    test('应该正确处理加载状态', async () => {
      renderUserManagement();
      
      // 验证初始加载状态
      expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
      
      // 等待加载完成
      await waitFor(() => {
        expect(screen.queryByTestId('loading-spinner')).not.toBeInTheDocument();
      });
      
      // 验证内容已加载
      expect(screen.getByText('John Doe')).toBeInTheDocument();
    });
  });
  
  describe('错误处理', () => {
    test('应该处理网络错误', async () => {
      // 模拟网络错误
      global.fetch.mockRejectedValueOnce(new Error('Network Error'));
      
      renderUserManagement();
      
      // 等待错误消息显示
      await waitFor(() => {
        expect(screen.getByText(/网络错误/i)).toBeInTheDocument();
      });
      
      // 验证重试按钮存在
      expect(screen.getByRole('button', { name: /重试/i })).toBeInTheDocument();
    });
    
    test('应该处理服务器错误', async () => {
      // 模拟服务器错误
      global.fetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
        json: async () => ({ error: 'Internal Server Error' })
      });
      
      renderUserManagement();
      
      // 等待错误消息显示
      await waitFor(() => {
        expect(screen.getByText(/服务器错误/i)).toBeInTheDocument();
      });
    });
  });
  
  describe('性能集成测试', () => {
    test('应该在合理时间内加载用户列表', async () => {
      const startTime = Date.now();
      
      renderUserManagement();
      
      await waitFor(() => {
        expect(screen.getByText('John Doe')).toBeInTheDocument();
      });
      
      const endTime = Date.now();
      const loadTime = endTime - startTime;
      
      // 验证加载时间小于2秒
      expect(loadTime).toBeLessThan(2000);
    });
    
    test('应该正确处理大量用户数据', async () => {
      // 生成大量测试数据
      const largeUserData = Array.from({ length: 1000 }, (_, i) => ({
        id: i + 1,
        name: `User ${i + 1}`,
        email: `user${i + 1}@example.com`,
        role: i % 2 === 0 ? 'user' : 'admin'
      }));
      
      await testRunner.testDatabase.seed({ users: largeUserData });
      
      const startTime = Date.now();
      renderUserManagement();
      
      // 等待虚拟滚动加载
      await waitFor(() => {
        expect(screen.getByText('User 1')).toBeInTheDocument();
      }, { timeout: 5000 });
      
      const endTime = Date.now();
      const loadTime = endTime - startTime;
      
      // 即使有大量数据,加载时间也应该合理
      expect(loadTime).toBeLessThan(3000);
    });
  });
});

4. E2E测试深度实践

4.1 E2E测试运行器

javascript 复制代码
// E2E测试运行器
class E2ETestRunner {
  constructor(config = {}) {
    this.config = {
      browser: 'chromium',
      headless: true,
      viewport: { width: 1280, height: 720 },
      baseURL: 'http://localhost:3000',
      timeout: 30000,
      retries: 2,
      video: 'retain-on-failure',
      screenshot: 'only-on-failure',
      trace: 'retain-on-failure',
      ...config
    };
    
    this.browser = null;
    this.context = null;
    this.page = null;
    this.testResults = [];
    this.screenshots = [];
    this.videos = [];
    
    this.init();
  }
  
  // 初始化
  async init() {
    const { chromium, firefox, webkit } = require('playwright');
    
    // 选择浏览器
    const browsers = { chromium, firefox, webkit };
    this.browserType = browsers[this.config.browser] || chromium;
    
    console.log(`🎭 Initializing E2E tests with ${this.config.browser}`);
  }
  
  // 启动浏览器
  async startBrowser() {
    try {
      this.browser = await this.browserType.launch({
        headless: this.config.headless,
        slowMo: this.config.slowMo || 0
      });
      
      this.context = await this.browser.newContext({
        viewport: this.config.viewport,
        baseURL: this.config.baseURL,
        recordVideo: this.config.video ? {
          dir: './test-results/videos/',
          size: this.config.viewport
        } : undefined
      });
      
      // 启用追踪
      if (this.config.trace) {
        await this.context.tracing.start({
          screenshots: true,
          snapshots: true,
          sources: true
        });
      }
      
      this.page = await this.context.newPage();
      
      // 设置默认超时
      this.page.setDefaultTimeout(this.config.timeout);
      
      // 添加控制台日志监听
      this.page.on('console', msg => {
        console.log(`🖥️ Console ${msg.type()}: ${msg.text()}`);
      });
      
      // 添加页面错误监听
      this.page.on('pageerror', error => {
        console.error('🚨 Page error:', error.message);
      });
      
      // 添加请求失败监听
      this.page.on('requestfailed', request => {
        console.error(`🌐 Request failed: ${request.url()} - ${request.failure()?.errorText}`);
      });
      
      console.log('🚀 Browser started successfully');
      
    } catch (error) {
      console.error('❌ Failed to start browser:', error);
      throw error;
    }
  }
  
  // 停止浏览器
  async stopBrowser() {
    try {
      if (this.config.trace && this.context) {
        await this.context.tracing.stop({
          path: `./test-results/traces/trace-${Date.now()}.zip`
        });
      }
      
      if (this.context) {
        await this.context.close();
      }
      
      if (this.browser) {
        await this.browser.close();
      }
      
      console.log('🛑 Browser stopped');
      
    } catch (error) {
      console.error('❌ Error stopping browser:', error);
    }
  }
  
  // 运行E2E测试
  async run(testSuites = []) {
    const startTime = Date.now();
    
    try {
      console.log('🎬 Starting E2E tests...');
      
      await this.startBrowser();
      
      const results = {
        total: 0,
        passed: 0,
        failed: 0,
        skipped: 0,
        tests: [],
        duration: 0
      };
      
      for (const suite of testSuites) {
        console.log(`📋 Running test suite: ${suite.name}`);
        
        const suiteResult = await this.runTestSuite(suite);
        
        results.total += suiteResult.total;
        results.passed += suiteResult.passed;
        results.failed += suiteResult.failed;
        results.skipped += suiteResult.skipped;
        results.tests.push(...suiteResult.tests);
      }
      
      await this.stopBrowser();
      
      const endTime = Date.now();
      results.duration = endTime - startTime;
      
      console.log(`✅ E2E tests completed in ${results.duration}ms`);
      console.log(`📊 Results: ${results.passed} passed, ${results.failed} failed, ${results.skipped} skipped`);
      
      return results;
      
    } catch (error) {
      console.error('❌ E2E test execution failed:', error);
      
      await this.stopBrowser();
      throw error;
    }
  }
  
  // 运行测试套件
  async runTestSuite(suite) {
    const results = {
      name: suite.name,
      total: 0,
      passed: 0,
      failed: 0,
      skipped: 0,
      tests: []
    };
    
    // 运行套件前置操作
    if (suite.beforeAll) {
      await suite.beforeAll(this.page, this.context);
    }
    
    for (const test of suite.tests) {
      const testResult = await this.runTest(test, suite);
      
      results.total++;
      results.tests.push(testResult);
      
      if (testResult.status === 'passed') {
        results.passed++;
      } else if (testResult.status === 'failed') {
        results.failed++;
      } else {
        results.skipped++;
      }
    }
    
    // 运行套件后置操作
    if (suite.afterAll) {
      await suite.afterAll(this.page, this.context);
    }
    
    return results;
  }
  
  // 运行单个测试
  async runTest(test, suite) {
    const startTime = Date.now();
    
    const testResult = {
      name: test.name,
      suite: suite.name,
      status: 'pending',
      duration: 0,
      error: null,
      screenshots: [],
      video: null
    };
    
    try {
      console.log(`  🧪 Running test: ${test.name}`);
      
      // 运行测试前置操作
      if (suite.beforeEach) {
        await suite.beforeEach(this.page, this.context);
      }
      
      // 运行测试
      await test.fn(this.page, this.context, this);
      
      testResult.status = 'passed';
      console.log(`  ✅ Test passed: ${test.name}`);
      
    } catch (error) {
      testResult.status = 'failed';
      testResult.error = error.message;
      
      console.error(`  ❌ Test failed: ${test.name}`);
      console.error(`     Error: ${error.message}`);
      
      // 截图
      if (this.config.screenshot === 'only-on-failure' || this.config.screenshot === 'always') {
        const screenshotPath = `./test-results/screenshots/${suite.name}-${test.name}-${Date.now()}.png`;
        await this.page.screenshot({ path: screenshotPath, fullPage: true });
        testResult.screenshots.push(screenshotPath);
      }
      
    } finally {
      // 运行测试后置操作
      if (suite.afterEach) {
        await suite.afterEach(this.page, this.context);
      }
      
      const endTime = Date.now();
      testResult.duration = endTime - startTime;
    }
    
    return testResult;
  }
  
  // 页面操作助手
  async navigateTo(url) {
    await this.page.goto(url);
    await this.page.waitForLoadState('networkidle');
  }
  
  async waitForElement(selector, options = {}) {
    return await this.page.waitForSelector(selector, {
      timeout: this.config.timeout,
      ...options
    });
  }
  
  async clickElement(selector) {
    await this.page.click(selector);
  }
  
  async fillInput(selector, value) {
    await this.page.fill(selector, value);
  }
  
  async getText(selector) {
    return await this.page.textContent(selector);
  }
  
  async takeScreenshot(name) {
    const screenshotPath = `./test-results/screenshots/${name}-${Date.now()}.png`;
    await this.page.screenshot({ path: screenshotPath, fullPage: true });
    this.screenshots.push(screenshotPath);
    return screenshotPath;
  }
  
  async waitForResponse(urlPattern, action) {
    const responsePromise = this.page.waitForResponse(urlPattern);
    await action();
    return await responsePromise;
  }
  
  async interceptRequest(urlPattern, handler) {
    await this.page.route(urlPattern, handler);
  }
  
  // 性能测试助手
  async measurePageLoad(url) {
    const startTime = Date.now();
    
    await this.page.goto(url);
    await this.page.waitForLoadState('networkidle');
    
    const endTime = Date.now();
    const loadTime = endTime - startTime;
    
    // 获取性能指标
    const metrics = await this.page.evaluate(() => {
      const navigation = performance.getEntriesByType('navigation')[0];
      return {
        domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
        loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
        firstPaint: performance.getEntriesByName('first-paint')[0]?.startTime || 0,
        firstContentfulPaint: performance.getEntriesByName('first-contentful-paint')[0]?.startTime || 0
      };
    });
    
    return {
      totalLoadTime: loadTime,
      ...metrics
    };
  }
  
  // 可访问性测试助手
  async checkAccessibility() {
    const { injectAxe, checkA11y } = require('axe-playwright');
    
    await injectAxe(this.page);
    
    try {
      await checkA11y(this.page, null, {
        detailedReport: true,
        detailedReportOptions: { html: true }
      });
      
      return { passed: true, violations: [] };
      
    } catch (error) {
      return {
        passed: false,
        violations: error.violations || []
      };
    }
  }
  
  // 获取状态
  getStatus() {
    return {
      config: this.config,
      browser: {
        connected: !!this.browser,
        type: this.config.browser
      },
      context: {
        created: !!this.context
      },
      page: {
        created: !!this.page,
        url: this.page?.url() || null
      },
      results: {
        tests: this.testResults.length,
        screenshots: this.screenshots.length,
        videos: this.videos.length
      }
    };
  }
}

4.2 电商应用E2E测试示例

javascript 复制代码
// e2e/ecommerce-flow.spec.js
const { E2ETestRunner } = require('../src/utils/testing');

// E2E测试套件
const ecommerceTestSuite = {
  name: 'E-commerce Application Flow',
  
  beforeAll: async (page, context) => {
    // 设置测试环境
    await page.goto('/login');
    
    // 清理测试数据
    await page.evaluate(() => {
      localStorage.clear();
      sessionStorage.clear();
    });
  },
  
  afterAll: async (page, context) => {
    // 清理测试环境
    await page.evaluate(() => {
      localStorage.clear();
      sessionStorage.clear();
    });
  },
  
  beforeEach: async (page, context) => {
    // 每个测试前重置状态
    await page.goto('/');
  },
  
  afterEach: async (page, context) => {
    // 每个测试后清理
    await page.evaluate(() => {
      // 清理购物车
      if (window.store) {
        window.store.dispatch({ type: 'CLEAR_CART' });
      }
    });
  },
  
  tests: [
    {
      name: '用户注册流程',
      fn: async (page, context, runner) => {
        // 导航到注册页面
        await runner.navigateTo('/register');
        
        // 填写注册表单
        await runner.fillInput('[data-testid="register-name"]', 'Test User');
        await runner.fillInput('[data-testid="register-email"]', 'test@example.com');
        await runner.fillInput('[data-testid="register-password"]', 'password123');
        await runner.fillInput('[data-testid="register-confirm-password"]', 'password123');
        
        // 提交注册
        await runner.clickElement('[data-testid="register-submit"]');
        
        // 等待注册成功
        await runner.waitForElement('[data-testid="registration-success"]');
        
        // 验证跳转到登录页面
        await page.waitForURL('**/login');
        
        // 验证成功消息
        const successMessage = await runner.getText('[data-testid="registration-success"]');
        expect(successMessage).toContain('注册成功');
      }
    },
    
    {
      name: '用户登录流程',
      fn: async (page, context, runner) => {
        // 导航到登录页面
        await runner.navigateTo('/login');
        
        // 填写登录表单
        await runner.fillInput('[data-testid="login-email"]', 'test@example.com');
        await runner.fillInput('[data-testid="login-password"]', 'password123');
        
        // 提交登录
        await runner.clickElement('[data-testid="login-submit"]');
        
        // 等待登录成功
        await runner.waitForElement('[data-testid="user-menu"]');
        
        // 验证跳转到首页
        await page.waitForURL('**/dashboard');
        
        // 验证用户信息显示
        const userMenu = await runner.getText('[data-testid="user-menu"]');
        expect(userMenu).toContain('Test User');
      }
    },
    
    {
      name: '商品浏览和搜索',
      fn: async (page, context, runner) => {
        // 导航到商品页面
        await runner.navigateTo('/products');
        
        // 等待商品列表加载
        await runner.waitForElement('[data-testid="product-list"]');
        
        // 验证商品显示
        const products = await page.$$('[data-testid="product-item"]');
        expect(products.length).toBeGreaterThan(0);
        
        // 搜索商品
        await runner.fillInput('[data-testid="search-input"]', 'iPhone');
        await runner.clickElement('[data-testid="search-button"]');
        
        // 等待搜索结果
        await page.waitForSelector('[data-testid="search-results"]');
        
        // 验证搜索结果
        const searchResults = await page.$$('[data-testid="product-item"]');
        expect(searchResults.length).toBeGreaterThan(0);
        
        // 验证搜索结果包含关键词
        const firstProduct = await runner.getText('[data-testid="product-item"]:first-child [data-testid="product-name"]');
        expect(firstProduct.toLowerCase()).toContain('iphone');
      }
    },
    
    {
      name: '商品详情查看',
      fn: async (page, context, runner) => {
        // 导航到商品页面
        await runner.navigateTo('/products');
        
        // 点击第一个商品
        await runner.clickElement('[data-testid="product-item"]:first-child');
        
        // 等待商品详情页面加载
        await runner.waitForElement('[data-testid="product-details"]');
        
        // 验证商品详情信息
        await runner.waitForElement('[data-testid="product-name"]');
        await runner.waitForElement('[data-testid="product-price"]');
        await runner.waitForElement('[data-testid="product-description"]');
        await runner.waitForElement('[data-testid="product-images"]');
        
        // 验证添加到购物车按钮存在
        await runner.waitForElement('[data-testid="add-to-cart"]');
        
        // 验证商品评价部分
        await runner.waitForElement('[data-testid="product-reviews"]');
      }
    },
    
    {
      name: '购物车操作流程',
      fn: async (page, context, runner) => {
        // 先登录
        await runner.navigateTo('/login');
        await runner.fillInput('[data-testid="login-email"]', 'test@example.com');
        await runner.fillInput('[data-testid="login-password"]', 'password123');
        await runner.clickElement('[data-testid="login-submit"]');
        await runner.waitForElement('[data-testid="user-menu"]');
        
        // 导航到商品页面
        await runner.navigateTo('/products');
        
        // 添加第一个商品到购物车
        await runner.clickElement('[data-testid="product-item"]:first-child [data-testid="add-to-cart"]');
        
        // 等待添加成功提示
        await runner.waitForElement('[data-testid="cart-notification"]');
        
        // 验证购物车图标显示数量
        const cartCount = await runner.getText('[data-testid="cart-count"]');
        expect(cartCount).toBe('1');
        
        // 点击购物车图标
        await runner.clickElement('[data-testid="cart-icon"]');
        
        // 等待购物车页面加载
        await runner.waitForElement('[data-testid="cart-items"]');
        
        // 验证商品在购物车中
        const cartItems = await page.$$('[data-testid="cart-item"]');
        expect(cartItems.length).toBe(1);
        
        // 修改商品数量
        await runner.clickElement('[data-testid="quantity-increase"]');
        
        // 验证数量更新
        const quantity = await runner.getText('[data-testid="item-quantity"]');
        expect(quantity).toBe('2');
        
        // 验证总价更新
        const totalPrice = await runner.getText('[data-testid="cart-total"]');
        expect(totalPrice).toMatch(/\$\d+\.\d{2}/);
      }
    },
    
    {
      name: '结账流程',
      fn: async (page, context, runner) => {
        // 先添加商品到购物车(复用之前的步骤)
        await runner.navigateTo('/login');
        await runner.fillInput('[data-testid="login-email"]', 'test@example.com');
        await runner.fillInput('[data-testid="login-password"]', 'password123');
        await runner.clickElement('[data-testid="login-submit"]');
        await runner.waitForElement('[data-testid="user-menu"]');
        
        await runner.navigateTo('/products');
        await runner.clickElement('[data-testid="product-item"]:first-child [data-testid="add-to-cart"]');
        await runner.waitForElement('[data-testid="cart-notification"]');
        
        // 进入购物车
        await runner.clickElement('[data-testid="cart-icon"]');
        await runner.waitForElement('[data-testid="cart-items"]');
        
        // 点击结账按钮
        await runner.clickElement('[data-testid="checkout-button"]');
        
        // 等待结账页面加载
        await runner.waitForElement('[data-testid="checkout-form"]');
        
        // 填写配送信息
        await runner.fillInput('[data-testid="shipping-name"]', 'Test User');
        await runner.fillInput('[data-testid="shipping-address"]', '123 Test Street');
        await runner.fillInput('[data-testid="shipping-city"]', 'Test City');
        await runner.fillInput('[data-testid="shipping-zip"]', '12345');
        
        // 选择配送方式
        await runner.clickElement('[data-testid="shipping-standard"]');
        
        // 填写支付信息
        await runner.fillInput('[data-testid="card-number"]', '4111111111111111');
        await runner.fillInput('[data-testid="card-expiry"]', '12/25');
        await runner.fillInput('[data-testid="card-cvc"]', '123');
        await runner.fillInput('[data-testid="card-name"]', 'Test User');
        
        // 提交订单
        await runner.clickElement('[data-testid="place-order"]');
        
        // 等待订单确认页面
        await runner.waitForElement('[data-testid="order-confirmation"]');
        
        // 验证订单号
        const orderNumber = await runner.getText('[data-testid="order-number"]');
        expect(orderNumber).toMatch(/^ORD-\d+$/);
        
        // 验证订单详情
        await runner.waitForElement('[data-testid="order-items"]');
        await runner.waitForElement('[data-testid="order-total"]');
        await runner.waitForElement('[data-testid="shipping-info"]');
      }
    },
    
    {
      name: '订单历史查看',
      fn: async (page, context, runner) => {
        // 登录
        await runner.navigateTo('/login');
        await runner.fillInput('[data-testid="login-email"]', 'test@example.com');
        await runner.fillInput('[data-testid="login-password"]', 'password123');
        await runner.clickElement('[data-testid="login-submit"]');
        await runner.waitForElement('[data-testid="user-menu"]');
        
        // 导航到订单历史页面
        await runner.clickElement('[data-testid="user-menu"]');
        await runner.clickElement('[data-testid="order-history"]');
        
        // 等待订单列表加载
        await runner.waitForElement('[data-testid="order-list"]');
        
        // 验证订单显示
        const orders = await page.$$('[data-testid="order-item"]');
        expect(orders.length).toBeGreaterThan(0);
        
        // 点击查看订单详情
        await runner.clickElement('[data-testid="order-item"]:first-child [data-testid="view-order"]');
        
        // 等待订单详情加载
        await runner.waitForElement('[data-testid="order-details"]');
        
        // 验证订单详情信息
        await runner.waitForElement('[data-testid="order-status"]');
        await runner.waitForElement('[data-testid="order-date"]');
        await runner.waitForElement('[data-testid="order-items-detail"]');
        await runner.waitForElement('[data-testid="shipping-address"]');
      }
    },
    
    {
      name: '响应式设计测试',
      fn: async (page, context, runner) => {
        // 测试桌面视图
        await page.setViewportSize({ width: 1280, height: 720 });
        await runner.navigateTo('/');
        
        // 验证桌面导航
        await runner.waitForElement('[data-testid="desktop-nav"]');
        expect(await page.isVisible('[data-testid="mobile-menu-button"]')).toBe(false);
        
        // 测试平板视图
        await page.setViewportSize({ width: 768, height: 1024 });
        await page.reload();
        
        // 验证平板布局
        await runner.waitForElement('[data-testid="tablet-layout"]');
        
        // 测试手机视图
        await page.setViewportSize({ width: 375, height: 667 });
        await page.reload();
        
        // 验证移动端导航
        await runner.waitForElement('[data-testid="mobile-menu-button"]');
        expect(await page.isVisible('[data-testid="desktop-nav"]')).toBe(false);
        
        // 测试移动端菜单
        await runner.clickElement('[data-testid="mobile-menu-button"]');
        await runner.waitForElement('[data-testid="mobile-menu"]');
        
        // 恢复桌面视图
        await page.setViewportSize({ width: 1280, height: 720 });
      }
    },
    
    {
      name: '性能测试',
      fn: async (page, context, runner) => {
        // 测试首页加载性能
        const homeMetrics = await runner.measurePageLoad('/');
        
        // 验证加载时间
        expect(homeMetrics.totalLoadTime).toBeLessThan(3000);
        expect(homeMetrics.firstContentfulPaint).toBeLessThan(1500);
        
        // 测试商品页面加载性能
        const productsMetrics = await runner.measurePageLoad('/products');
        
        // 验证商品页面性能
        expect(productsMetrics.totalLoadTime).toBeLessThan(5000);
        expect(productsMetrics.domContentLoaded).toBeLessThan(2000);
        
        // 测试搜索性能
        const searchStart = Date.now();
        await runner.fillInput('[data-testid="search-input"]', 'test');
        await runner.clickElement('[data-testid="search-button"]');
        await runner.waitForElement('[data-testid="search-results"]');
        const searchTime = Date.now() - searchStart;
        
        expect(searchTime).toBeLessThan(2000);
      }
    },
    
    {
      name: '可访问性测试',
      fn: async (page, context, runner) => {
        // 测试首页可访问性
        await runner.navigateTo('/');
        const homeA11y = await runner.checkAccessibility();
        
        if (!homeA11y.passed) {
          console.warn('首页可访问性问题:', homeA11y.violations);
        }
        
        // 测试登录页面可访问性
        await runner.navigateTo('/login');
        const loginA11y = await runner.checkAccessibility();
        
        if (!loginA11y.passed) {
          console.warn('登录页面可访问性问题:', loginA11y.violations);
        }
        
        // 测试键盘导航
        await page.keyboard.press('Tab');
        const focusedElement = await page.evaluate(() => document.activeElement.tagName);
        expect(['INPUT', 'BUTTON', 'A']).toContain(focusedElement);
        
        // 测试屏幕阅读器支持
        const loginButton = await page.$('[data-testid="login-submit"]');
        const ariaLabel = await loginButton.getAttribute('aria-label');
        expect(ariaLabel).toBeTruthy();
      }
    }
  ]
};

// 运行E2E测试
async function runE2ETests() {
  const runner = new E2ETestRunner({
    browser: 'chromium',
    headless: false, // 开发时可以设置为false观察测试过程
    baseURL: 'http://localhost:3000',
    timeout: 30000,
    video: 'retain-on-failure',
    screenshot: 'only-on-failure'
  });
  
  try {
    const results = await runner.run([ecommerceTestSuite]);
    
    console.log('\n📊 E2E测试结果:');
    console.log(`总计: ${results.total}`);
    console.log(`通过: ${results.passed}`);
    console.log(`失败: ${results.failed}`);
    console.log(`跳过: ${results.skipped}`);
    console.log(`耗时: ${results.duration}ms`);
    
    if (results.failed > 0) {
      console.log('\n❌ 失败的测试:');
      results.tests
        .filter(test => test.status === 'failed')
        .forEach(test => {
          console.log(`  - ${test.name}: ${test.error}`);
        });
    }
    
    return results;
    
  } catch (error) {
    console.error('E2E测试执行失败:', error);
    throw error;
  }
}

// 导出测试套件和运行函数
module.exports = {
  ecommerceTestSuite,
  runE2ETests
};

5. 最佳实践与总结

5.1 测试策略设计原则

测试金字塔实践
javascript 复制代码
// 测试策略管理器
class TestStrategyManager {
  constructor() {
    this.strategies = {
      unit: {
        ratio: 0.7, // 70%的测试应该是单元测试
        focus: ['函数逻辑', '组件行为', '工具函数', '状态管理'],
        tools: ['Jest', 'React Testing Library', 'Enzyme'],
        coverage: { minimum: 80, target: 90 }
      },
      integration: {
        ratio: 0.2, // 20%的测试应该是集成测试
        focus: ['组件集成', 'API集成', '数据流', '用户交互'],
        tools: ['Jest', 'MSW', 'Testing Library'],
        coverage: { minimum: 60, target: 75 }
      },
      e2e: {
        ratio: 0.1, // 10%的测试应该是E2E测试
        focus: ['关键用户流程', '跨浏览器兼容性', '性能', '可访问性'],
        tools: ['Playwright', 'Cypress', 'Puppeteer'],
        coverage: { minimum: 40, target: 60 }
      }
    };
    
    this.qualityGates = {
      coverage: {
        statements: 80,
        branches: 75,
        functions: 80,
        lines: 80
      },
      performance: {
        unitTestSpeed: 1000, // ms per test
        integrationTestSpeed: 5000,
        e2eTestSpeed: 30000
      },
      reliability: {
        flakyTestThreshold: 0.05, // 5%
        testStability: 0.95 // 95%
      }
    };
  }
  
  // 评估测试策略
  evaluateStrategy(testResults) {
    const evaluation = {
      distribution: this.analyzeTestDistribution(testResults),
      coverage: this.analyzeCoverage(testResults),
      performance: this.analyzePerformance(testResults),
      quality: this.analyzeQuality(testResults),
      recommendations: []
    };
    
    // 生成改进建议
    evaluation.recommendations = this.generateRecommendations(evaluation);
    
    return evaluation;
  }
  
  // 分析测试分布
  analyzeTestDistribution(testResults) {
    const total = testResults.unit.count + testResults.integration.count + testResults.e2e.count;
    
    return {
      unit: {
        actual: testResults.unit.count / total,
        expected: this.strategies.unit.ratio,
        status: this.getDistributionStatus('unit', testResults.unit.count / total)
      },
      integration: {
        actual: testResults.integration.count / total,
        expected: this.strategies.integration.ratio,
        status: this.getDistributionStatus('integration', testResults.integration.count / total)
      },
      e2e: {
        actual: testResults.e2e.count / total,
        expected: this.strategies.e2e.ratio,
        status: this.getDistributionStatus('e2e', testResults.e2e.count / total)
      }
    };
  }
  
  // 获取分布状态
  getDistributionStatus(type, actual) {
    const expected = this.strategies[type].ratio;
    const tolerance = 0.1; // 10%容差
    
    if (Math.abs(actual - expected) <= tolerance) {
      return 'optimal';
    } else if (actual > expected) {
      return 'over';
    } else {
      return 'under';
    }
  }
  
  // 生成改进建议
  generateRecommendations(evaluation) {
    const recommendations = [];
    
    // 测试分布建议
    Object.keys(evaluation.distribution).forEach(type => {
      const dist = evaluation.distribution[type];
      if (dist.status === 'under') {
        recommendations.push({
          type: 'distribution',
          priority: 'high',
          message: `增加${type}测试数量,当前比例${(dist.actual * 100).toFixed(1)}%,建议${(dist.expected * 100).toFixed(1)}%`
        });
      } else if (dist.status === 'over') {
        recommendations.push({
          type: 'distribution',
          priority: 'medium',
          message: `${type}测试比例过高,考虑重构为更低层次的测试`
        });
      }
    });
    
    // 覆盖率建议
    Object.keys(evaluation.coverage).forEach(metric => {
      const coverage = evaluation.coverage[metric];
      if (coverage < this.qualityGates.coverage[metric]) {
        recommendations.push({
          type: 'coverage',
          priority: 'high',
          message: `${metric}覆盖率不足,当前${coverage}%,要求${this.qualityGates.coverage[metric]}%`
        });
      }
    });
    
    // 性能建议
    if (evaluation.performance.averageTestTime > this.qualityGates.performance.unitTestSpeed) {
      recommendations.push({
        type: 'performance',
        priority: 'medium',
        message: '测试执行时间过长,考虑优化测试代码或并行执行'
      });
    }
    
    return recommendations;
  }
}

5.2 性能优化策略

测试执行优化
javascript 复制代码
// 测试性能优化器
class TestPerformanceOptimizer {
  constructor() {
    this.optimizations = {
      parallel: true,
      cache: true,
      incremental: true,
      smartSelection: true
    };
    
    this.metrics = {
      executionTime: [],
      memoryUsage: [],
      cacheHitRate: 0,
      parallelEfficiency: 0
    };
  }
  
  // 优化测试执行
  async optimizeExecution(testSuites) {
    const optimizedSuites = [];
    
    for (const suite of testSuites) {
      const optimizedSuite = await this.optimizeSuite(suite);
      optimizedSuites.push(optimizedSuite);
    }
    
    return {
      suites: optimizedSuites,
      config: this.generateOptimizedConfig(),
      recommendations: this.generateOptimizationRecommendations()
    };
  }
  
  // 优化测试套件
  async optimizeSuite(suite) {
    const optimized = { ...suite };
    
    // 智能测试选择
    if (this.optimizations.smartSelection) {
      optimized.tests = await this.selectRelevantTests(suite.tests);
    }
    
    // 测试分组优化
    optimized.groups = this.optimizeTestGroups(optimized.tests);
    
    // 资源预加载
    optimized.preload = this.identifyPreloadResources(optimized.tests);
    
    return optimized;
  }
  
  // 智能测试选择
  async selectRelevantTests(tests) {
    const changedFiles = await this.getChangedFiles();
    const relevantTests = [];
    
    for (const test of tests) {
      const dependencies = await this.getTestDependencies(test);
      const isRelevant = dependencies.some(dep => 
        changedFiles.some(file => dep.includes(file))
      );
      
      if (isRelevant || test.critical) {
        relevantTests.push(test);
      }
    }
    
    return relevantTests.length > 0 ? relevantTests : tests;
  }
  
  // 获取变更文件
  async getChangedFiles() {
    try {
      const { execSync } = require('child_process');
      const output = execSync('git diff --name-only HEAD~1', { encoding: 'utf8' });
      return output.trim().split('\n').filter(Boolean);
    } catch (error) {
      console.warn('无法获取变更文件,运行所有测试');
      return [];
    }
  }
  
  // 获取测试依赖
  async getTestDependencies(test) {
    // 简化的依赖分析
    const dependencies = [];
    
    if (test.file) {
      const fs = require('fs');
      const content = fs.readFileSync(test.file, 'utf8');
      
      // 提取import语句
      const imports = content.match(/import.*from\s+['"]([^'"]+)['"]/g) || [];
      imports.forEach(imp => {
        const match = imp.match(/from\s+['"]([^'"]+)['"]/);;
        if (match) {
          dependencies.push(match[1]);
        }
      });
    }
    
    return dependencies;
  }
  
  // 优化测试分组
  optimizeTestGroups(tests) {
    const groups = {
      fast: [], // 快速测试(<100ms)
      medium: [], // 中等测试(100ms-1s)
      slow: [], // 慢速测试(>1s)
      isolated: [] // 需要隔离的测试
    };
    
    tests.forEach(test => {
      if (test.isolated) {
        groups.isolated.push(test);
      } else if (test.estimatedTime < 100) {
        groups.fast.push(test);
      } else if (test.estimatedTime < 1000) {
        groups.medium.push(test);
      } else {
        groups.slow.push(test);
      }
    });
    
    return groups;
  }
  
  // 生成优化配置
  generateOptimizedConfig() {
    return {
      maxWorkers: this.optimizations.parallel ? '50%' : 1,
      cache: this.optimizations.cache,
      cacheDirectory: '.jest-cache',
      testTimeout: 10000,
      setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
      collectCoverageFrom: [
        'src/**/*.{js,jsx,ts,tsx}',
        '!src/**/*.d.ts',
        '!src/index.js',
        '!src/serviceWorker.js'
      ],
      coverageThreshold: {
        global: {
          branches: 75,
          functions: 80,
          lines: 80,
          statements: 80
        }
      },
      watchPlugins: [
        'jest-watch-typeahead/filename',
        'jest-watch-typeahead/testname'
      ]
    };
  }
  
  // 生成优化建议
  generateOptimizationRecommendations() {
    const recommendations = [];
    
    // 并行执行建议
    if (!this.optimizations.parallel) {
      recommendations.push({
        type: 'parallel',
        impact: 'high',
        message: '启用并行测试执行可以显著提升速度'
      });
    }
    
    // 缓存建议
    if (!this.optimizations.cache) {
      recommendations.push({
        type: 'cache',
        impact: 'medium',
        message: '启用测试缓存可以避免重复执行未变更的测试'
      });
    }
    
    // 增量测试建议
    if (!this.optimizations.incremental) {
      recommendations.push({
        type: 'incremental',
        impact: 'medium',
        message: '启用增量测试可以只运行相关的测试'
      });
    }
    
    return recommendations;
  }
}

5.3 开发体验提升

测试开发工具
javascript 复制代码
// 测试开发助手
class TestDevelopmentHelper {
  constructor() {
    this.templates = new Map();
    this.snippets = new Map();
    this.generators = new Map();
    
    this.initializeTemplates();
    this.initializeSnippets();
    this.initializeGenerators();
  }
  
  // 初始化模板
  initializeTemplates() {
    // React组件测试模板
    this.templates.set('react-component', `
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { {{componentName}} } from './{{componentName}}';

describe('{{componentName}}', () => {
  test('应该正确渲染', () => {
    render(<{{componentName}} />);
    // 添加断言
  });
  
  test('应该处理用户交互', () => {
    render(<{{componentName}} />);
    // 添加交互测试
  });
  
  test('应该处理props变化', () => {
    const { rerender } = render(<{{componentName}} prop="value1" />);
    // 测试props变化
    rerender(<{{componentName}} prop="value2" />);
    // 添加断言
  });
});
`);
    
    // Hook测试模板
    this.templates.set('react-hook', `
import { renderHook, act } from '@testing-library/react';
import { {{hookName}} } from './{{hookName}}';

describe('{{hookName}}', () => {
  test('应该返回初始状态', () => {
    const { result } = renderHook(() => {{hookName}}());
    // 添加断言
  });
  
  test('应该正确更新状态', () => {
    const { result } = renderHook(() => {{hookName}}());
    
    act(() => {
      // 执行状态更新
    });
    
    // 添加断言
  });
});
`);
    
    // 工具函数测试模板
    this.templates.set('utility-function', `
import { {{functionName}} } from './{{functionName}}';

describe('{{functionName}}', () => {
  test('应该处理正常输入', () => {
    const result = {{functionName}}(/* 正常输入 */);
    expect(result).toBe(/* 期望结果 */);
  });
  
  test('应该处理边界情况', () => {
    // 测试边界情况
  });
  
  test('应该处理错误输入', () => {
    expect(() => {
      {{functionName}}(/* 错误输入 */);
    }).toThrow();
  });
});
`);
  }
  
  // 初始化代码片段
  initializeSnippets() {
    this.snippets.set('mock-api', `
// Mock API响应
const mockApiResponse = {
  data: {
    // 模拟数据
  },
  status: 200,
  statusText: 'OK'
};

jest.mock('axios', () => ({
  get: jest.fn(() => Promise.resolve(mockApiResponse)),
  post: jest.fn(() => Promise.resolve(mockApiResponse)),
  put: jest.fn(() => Promise.resolve(mockApiResponse)),
  delete: jest.fn(() => Promise.resolve(mockApiResponse))
}));
`);
    
    this.snippets.set('mock-localStorage', `
// Mock localStorage
const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
  clear: jest.fn()
};

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock
});
`);
    
    this.snippets.set('async-test', `
test('应该处理异步操作', async () => {
  // 等待异步操作完成
  await waitFor(() => {
    expect(screen.getByText('加载完成')).toBeInTheDocument();
  });
  
  // 或者使用findBy查询
  const element = await screen.findByText('异步内容');
  expect(element).toBeInTheDocument();
});
`);
  }
  
  // 初始化生成器
  initializeGenerators() {
    this.generators.set('component-test', this.generateComponentTest.bind(this));
    this.generators.set('hook-test', this.generateHookTest.bind(this));
    this.generators.set('integration-test', this.generateIntegrationTest.bind(this));
  }
  
  // 生成组件测试
  generateComponentTest(componentPath) {
    const fs = require('fs');
    const path = require('path');
    
    // 读取组件文件
    const componentContent = fs.readFileSync(componentPath, 'utf8');
    
    // 提取组件信息
    const componentInfo = this.analyzeComponent(componentContent);
    
    // 生成测试代码
    const testCode = this.generateTestFromTemplate('react-component', {
      componentName: componentInfo.name,
      props: componentInfo.props,
      events: componentInfo.events,
      states: componentInfo.states
    });
    
    // 写入测试文件
    const testPath = componentPath.replace(/\.(jsx?|tsx?)$/, '.test.$1');
    fs.writeFileSync(testPath, testCode);
    
    return testPath;
  }
  
  // 分析组件
  analyzeComponent(content) {
    const info = {
      name: '',
      props: [],
      events: [],
      states: []
    };
    
    // 提取组件名称
    const nameMatch = content.match(/(?:function|const)\s+(\w+)|class\s+(\w+)/);
    if (nameMatch) {
      info.name = nameMatch[1] || nameMatch[2];
    }
    
    // 提取props
    const propsMatch = content.match(/\{([^}]+)\}\s*=\s*props/);
    if (propsMatch) {
      info.props = propsMatch[1].split(',').map(prop => prop.trim());
    }
    
    // 提取事件处理器
    const eventMatches = content.match(/on\w+\s*=/g) || [];
    info.events = eventMatches.map(event => event.replace(/\s*=$/, ''));
    
    // 提取状态
    const stateMatches = content.match(/useState\(([^)]+)\)/g) || [];
    info.states = stateMatches.map((state, index) => `state${index}`);
    
    return info;
  }
  
  // 从模板生成测试
  generateTestFromTemplate(templateName, data) {
    let template = this.templates.get(templateName);
    
    // 替换模板变量
    Object.keys(data).forEach(key => {
      const regex = new RegExp(`\{\{${key}\}\}`, 'g');
      template = template.replace(regex, data[key]);
    });
    
    return template;
  }
  
  // 生成测试报告
  generateTestReport(results) {
    const report = {
      summary: {
        total: results.numTotalTests,
        passed: results.numPassedTests,
        failed: results.numFailedTests,
        skipped: results.numPendingTests,
        coverage: results.coverageMap
      },
      details: results.testResults.map(testFile => ({
        file: testFile.testFilePath,
        tests: testFile.testResults.map(test => ({
          name: test.title,
          status: test.status,
          duration: test.duration,
          error: test.failureMessages[0] || null
        }))
      })),
      recommendations: this.generateTestRecommendations(results)
    };
    
    return report;
  }
  
  // 生成测试建议
  generateTestRecommendations(results) {
    const recommendations = [];
    
    // 覆盖率建议
    if (results.coverageMap) {
      const coverage = results.coverageMap.getCoverageSummary();
      
      if (coverage.statements.pct < 80) {
        recommendations.push({
          type: 'coverage',
          priority: 'high',
          message: `语句覆盖率${coverage.statements.pct}%,建议提升至80%以上`
        });
      }
      
      if (coverage.branches.pct < 75) {
        recommendations.push({
          type: 'coverage',
          priority: 'medium',
          message: `分支覆盖率${coverage.branches.pct}%,建议提升至75%以上`
        });
      }
    }
    
    // 测试质量建议
    const failedTests = results.testResults.filter(test => test.numFailingTests > 0);
    if (failedTests.length > 0) {
      recommendations.push({
        type: 'quality',
        priority: 'high',
        message: `${failedTests.length}个测试文件包含失败的测试,需要修复`
      });
    }
    
    // 性能建议
    const slowTests = results.testResults.filter(test => 
      test.perfStats.end - test.perfStats.start > 5000
    );
    if (slowTests.length > 0) {
      recommendations.push({
        type: 'performance',
        priority: 'medium',
        message: `${slowTests.length}个测试文件执行时间过长,考虑优化`
      });
    }
    
    return recommendations;
  }
}

5.4 技术选型建议

测试工具选择矩阵
测试类型 推荐工具 适用场景 优势 劣势
单元测试 Jest + RTL React应用 生态完善、配置简单 学习曲线
单元测试 Vitest Vite项目 速度快、ESM支持 生态较新
集成测试 Jest + MSW API集成 Mock能力强 配置复杂
E2E测试 Playwright 跨浏览器 功能全面、稳定 资源消耗大
E2E测试 Cypress 开发体验 调试友好、实时预览 浏览器限制
性能测试 Lighthouse CI Web性能 标准化指标 配置复杂
可访问性 axe-core 无障碍测试 规则全面 需要人工验证

5.5 未来发展趋势

AI驱动的测试
javascript 复制代码
// AI测试助手(概念性实现)
class AITestingAssistant {
  constructor() {
    this.model = null; // AI模型接口
    this.testPatterns = new Map();
    this.bugPatterns = new Map();
  }
  
  // AI生成测试用例
  async generateTestCases(componentCode) {
    const analysis = await this.analyzeCode(componentCode);
    
    const testCases = await this.model.generate({
      prompt: `为以下React组件生成测试用例:\n${componentCode}`,
      context: {
        patterns: Array.from(this.testPatterns.values()),
        bestPractices: this.getTestingBestPractices()
      }
    });
    
    return this.validateGeneratedTests(testCases);
  }
  
  // 智能Bug检测
  async detectPotentialBugs(testResults) {
    const patterns = await this.model.analyze({
      data: testResults,
      patterns: Array.from(this.bugPatterns.values())
    });
    
    return patterns.map(pattern => ({
      type: pattern.type,
      confidence: pattern.confidence,
      description: pattern.description,
      suggestion: pattern.suggestion
    }));
  }
  
  // 自动化测试维护
  async maintainTests(codeChanges) {
    const affectedTests = await this.findAffectedTests(codeChanges);
    const updates = [];
    
    for (const test of affectedTests) {
      const update = await this.model.updateTest({
        originalTest: test.content,
        codeChanges: codeChanges,
        context: test.context
      });
      
      updates.push({
        file: test.file,
        changes: update.changes,
        confidence: update.confidence
      });
    }
    
    return updates;
  }
}

5.6 核心价值与收益

测试投资回报分析
javascript 复制代码
// 测试ROI计算器
class TestingROICalculator {
  constructor() {
    this.metrics = {
      bugDetectionCost: 100, // 测试中发现bug的成本
      productionBugCost: 10000, // 生产环境bug的成本
      testMaintenanceCost: 50, // 测试维护成本
      developmentSpeedIncrease: 0.2, // 开发速度提升
      codeQualityImprovement: 0.3 // 代码质量提升
    };
  }
  
  // 计算测试ROI
  calculateROI(testingData) {
    const costs = this.calculateCosts(testingData);
    const benefits = this.calculateBenefits(testingData);
    
    const roi = ((benefits - costs) / costs) * 100;
    
    return {
      roi: roi,
      costs: costs,
      benefits: benefits,
      paybackPeriod: costs / (benefits / 12), // 月
      breakdown: {
        bugPrevention: this.calculateBugPreventionValue(testingData),
        developmentSpeed: this.calculateSpeedValue(testingData),
        codeQuality: this.calculateQualityValue(testingData),
        maintenance: this.calculateMaintenanceValue(testingData)
      }
    };
  }
  
  // 计算测试成本
  calculateCosts(data) {
    return {
      development: data.testDevelopmentHours * data.hourlyRate,
      maintenance: data.testCount * this.metrics.testMaintenanceCost,
      infrastructure: data.ciCdCosts + data.toolingCosts,
      total: 0
    };
  }
  
  // 计算测试收益
  calculateBenefits(data) {
    const bugsPrevented = data.bugsFoundInTesting;
    const productionBugsSaved = bugsPrevented * 0.8; // 80%的bug会到生产环境
    
    return {
      bugPrevention: productionBugsSaved * this.metrics.productionBugCost,
      speedIncrease: data.developmentHours * data.hourlyRate * this.metrics.developmentSpeedIncrease,
      qualityImprovement: data.maintenanceHours * data.hourlyRate * this.metrics.codeQualityImprovement,
      total: 0
    };
  }
}

结语

前端测试是现代Web开发不可或缺的重要环节。通过建立完善的测试体系,我们能够:

  1. 提升代码质量:通过全面的测试覆盖,确保代码的正确性和稳定性
  2. 加速开发流程:自动化测试减少手动验证时间,提升开发效率
  3. 降低维护成本:早期发现问题,避免生产环境的高昂修复成本
  4. 增强团队信心:完善的测试让重构和新功能开发更加安全
  5. 改善用户体验:确保应用在各种场景下都能正常工作

测试不仅仅是质量保证的手段,更是现代软件工程实践的基础。投资于测试体系建设,将为项目的长期成功奠定坚实基础。


相关文章推荐:

2. 单元测试深度实践

2.1 单元测试运行器

javascript 复制代码
// 单元测试运行器
class UnitTestRunner {
  constructor(config = {}) {
    this.config = {
      framework: 'jest',
      environment: 'jsdom',
      setupFiles: [],
      testMatch: ['**/__tests__/**/*.test.{js,jsx,ts,tsx}'],
      collectCoverageFrom: [],
      coverageThreshold: {},
      moduleNameMapping: {},
      transform: {},
      ...config
    };
    
    this.testFiles = [];
    this.testResults = [];
    this.mockRegistry = new Map();
    this.spyRegistry = new Map();
    
    this.init();
  }
  
  // 初始化
  init() {
    this.setupTestFramework();
    this.setupMockSystem();
    this.setupTestUtilities();
  }
  
  // 设置测试框架
  setupTestFramework() {
    // Jest配置
    this.jestConfig = {
      testEnvironment: this.config.environment,
      setupFilesAfterEnv: this.config.setupFiles,
      testMatch: this.config.testMatch,
      collectCoverageFrom: this.config.collectCoverageFrom,
      coverageThreshold: this.config.coverageThreshold,
      moduleNameMapping: this.config.moduleNameMapping,
      transform: {
        '^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
        '^.+\\.css$': 'identity-obj-proxy',
        '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': 'jest-transform-stub',
        ...this.config.transform
      },
      transformIgnorePatterns: [
        '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$',
        '^.+\\.module\\.(css|sass|scss)$'
      ],
      moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
      watchPlugins: [
        'jest-watch-typeahead/filename',
        'jest-watch-typeahead/testname'
      ]
    };
  }
  
  // 设置Mock系统
  setupMockSystem() {
    this.mockUtils = {
      // 创建函数Mock
      createFunctionMock: (implementation) => {
        const mock = jest.fn(implementation);
        this.mockRegistry.set(mock, {
          type: 'function',
          created: Date.now(),
          calls: []
        });
        return mock;
      },
      
      // 创建模块Mock
      createModuleMock: (modulePath, mockImplementation) => {
        const mock = jest.mock(modulePath, () => mockImplementation);
        this.mockRegistry.set(modulePath, {
          type: 'module',
          implementation: mockImplementation,
          created: Date.now()
        });
        return mock;
      },
      
      // 创建对象Mock
      createObjectMock: (object, methods = []) => {
        const mock = {};
        
        if (methods.length === 0) {
          methods = Object.getOwnPropertyNames(object.prototype || object)
            .filter(name => typeof object[name] === 'function');
        }
        
        methods.forEach(method => {
          mock[method] = jest.fn();
        });
        
        this.mockRegistry.set(mock, {
          type: 'object',
          originalObject: object,
          mockedMethods: methods,
          created: Date.now()
        });
        
        return mock;
      },
      
      // 创建API Mock
      createApiMock: (baseUrl, endpoints = {}) => {
        const mock = {
          baseUrl,
          endpoints: {},
          requests: []
        };
        
        Object.keys(endpoints).forEach(endpoint => {
          mock.endpoints[endpoint] = {
            response: endpoints[endpoint],
            calls: 0,
            lastCall: null
          };
        });
        
        // 模拟fetch
        global.fetch = jest.fn((url, options = {}) => {
          const endpoint = url.replace(baseUrl, '');
          const mockEndpoint = mock.endpoints[endpoint];
          
          mock.requests.push({
            url,
            options,
            timestamp: Date.now()
          });
          
          if (mockEndpoint) {
            mockEndpoint.calls++;
            mockEndpoint.lastCall = Date.now();
            
            return Promise.resolve({
              ok: true,
              status: 200,
              json: () => Promise.resolve(mockEndpoint.response),
              text: () => Promise.resolve(JSON.stringify(mockEndpoint.response))
            });
          }
          
          return Promise.reject(new Error(`No mock found for ${endpoint}`));
        });
        
        this.mockRegistry.set('api', mock);
        return mock;
      }
    };
  }
  
  // 设置测试工具
  setupTestUtilities() {
    this.testUtils = {
      // React组件测试工具
      renderComponent: (Component, props = {}, options = {}) => {
        const { render } = require('@testing-library/react');
        const { Provider } = require('react-redux');
        const { BrowserRouter } = require('react-router-dom');
        
        const AllTheProviders = ({ children }) => {
          let wrapped = children;
          
          // Redux Provider
          if (options.store) {
            wrapped = React.createElement(Provider, { store: options.store }, wrapped);
          }
          
          // Router Provider
          if (options.router !== false) {
            wrapped = React.createElement(BrowserRouter, {}, wrapped);
          }
          
          return wrapped;
        };
        
        return render(
          React.createElement(Component, props),
          {
            wrapper: AllTheProviders,
            ...options
          }
        );
      },
      
      // 异步测试工具
      waitForAsync: async (callback, timeout = 5000) => {
        const { waitFor } = require('@testing-library/react');
        return waitFor(callback, { timeout });
      },
      
      // 用户交互模拟
      userInteraction: {
        click: async (element) => {
          const { fireEvent } = require('@testing-library/react');
          fireEvent.click(element);
          await this.testUtils.waitForAsync(() => {});
        },
        
        type: async (element, text) => {
          const { fireEvent } = require('@testing-library/react');
          fireEvent.change(element, { target: { value: text } });
          await this.testUtils.waitForAsync(() => {});
        },
        
        submit: async (form) => {
          const { fireEvent } = require('@testing-library/react');
          fireEvent.submit(form);
          await this.testUtils.waitForAsync(() => {});
        }
      },
      
      // 快照测试
      createSnapshot: (component, props = {}) => {
        const renderer = require('react-test-renderer');
        const tree = renderer.create(
          React.createElement(component, props)
        ).toJSON();
        expect(tree).toMatchSnapshot();
        return tree;
      },
      
      // 性能测试
      measurePerformance: async (callback, iterations = 100) => {
        const times = [];
        
        for (let i = 0; i < iterations; i++) {
          const start = performance.now();
          await callback();
          const end = performance.now();
          times.push(end - start);
        }
        
        return {
          average: times.reduce((a, b) => a + b, 0) / times.length,
          min: Math.min(...times),
          max: Math.max(...times),
          median: times.sort((a, b) => a - b)[Math.floor(times.length / 2)]
        };
      }
    };
  }
  
  // 运行测试
  async run(options = {}) {
    const startTime = Date.now();
    
    try {
      console.log('🧪 Running unit tests...');
      
      // 发现测试文件
      await this.discoverTestFiles();
      
      // 运行Jest
      const jestResult = await this.runJest(options);
      
      // 处理结果
      const result = this.processJestResult(jestResult);
      
      const endTime = Date.now();
      result.duration = endTime - startTime;
      
      console.log(`✅ Unit tests completed in ${result.duration}ms`);
      
      return result;
    } catch (error) {
      console.error('❌ Unit test execution failed:', error);
      throw error;
    }
  }
  
  // 发现测试文件
  async discoverTestFiles() {
    const glob = require('glob');
    const path = require('path');
    
    this.testFiles = [];
    
    for (const pattern of this.config.testMatch) {
      const files = glob.sync(pattern, {
        cwd: process.cwd(),
        absolute: true
      });
      
      this.testFiles.push(...files);
    }
    
    console.log(`📁 Found ${this.testFiles.length} test files`);
    return this.testFiles;
  }
  
  // 运行Jest
  async runJest(options = {}) {
    const jest = require('jest');
    
    const jestOptions = {
      ...this.jestConfig,
      silent: options.silent || false,
      verbose: options.verbose || true,
      collectCoverage: options.coverage !== false,
      runInBand: !options.parallel,
      watchAll: options.watch || false
    };
    
    if (options.testNamePattern) {
      jestOptions.testNamePattern = options.testNamePattern;
    }
    
    if (options.testPathPattern) {
      jestOptions.testPathPattern = options.testPathPattern;
    }
    
    return new Promise((resolve, reject) => {
      jest.runCLI(jestOptions, [process.cwd()])
        .then(({ results }) => resolve(results))
        .catch(reject);
    });
  }
  
  // 处理Jest结果
  processJestResult(jestResult) {
    const tests = [];
    const errors = [];
    
    jestResult.testResults.forEach(testFile => {
      testFile.testResults.forEach(test => {
        tests.push({
          name: test.title,
          file: testFile.testFilePath,
          status: test.status,
          duration: test.duration,
          error: test.failureMessages.length > 0 ? test.failureMessages[0] : null
        });
      });
      
      if (testFile.failureMessage) {
        errors.push(testFile.failureMessage);
      }
    });
    
    return {
      success: jestResult.success,
      tests: tests,
      coverage: jestResult.coverageMap ? this.processCoverageData(jestResult.coverageMap) : null,
      errors: errors,
      numTotalTests: jestResult.numTotalTests,
      numPassedTests: jestResult.numPassedTests,
      numFailedTests: jestResult.numFailedTests,
      numPendingTests: jestResult.numPendingTests
    };
  }
  
  // 处理覆盖率数据
  processCoverageData(coverageMap) {
    const coverage = {};
    
    Object.keys(coverageMap).forEach(filePath => {
      const fileCoverage = coverageMap[filePath];
      
      coverage[filePath] = {
        statements: {
          total: fileCoverage.s ? Object.keys(fileCoverage.s).length : 0,
          covered: fileCoverage.s ? Object.values(fileCoverage.s).filter(Boolean).length : 0,
          percentage: 0
        },
        branches: {
          total: fileCoverage.b ? Object.keys(fileCoverage.b).length : 0,
          covered: fileCoverage.b ? Object.values(fileCoverage.b).filter(branch => branch.some(Boolean)).length : 0,
          percentage: 0
        },
        functions: {
          total: fileCoverage.f ? Object.keys(fileCoverage.f).length : 0,
          covered: fileCoverage.f ? Object.values(fileCoverage.f).filter(Boolean).length : 0,
          percentage: 0
        },
        lines: {
          total: fileCoverage.l ? Object.keys(fileCoverage.l).length : 0,
          covered: fileCoverage.l ? Object.values(fileCoverage.l).filter(Boolean).length : 0,
          percentage: 0
        }
      };
      
      // 计算百分比
      Object.keys(coverage[filePath]).forEach(metric => {
        const { total, covered } = coverage[filePath][metric];
        coverage[filePath][metric].percentage = total > 0 ? Math.round((covered / total) * 100) : 0;
      });
    });
    
    return coverage;
  }
  
  // 清理Mock
  cleanupMocks() {
    // 清理Jest mocks
    jest.clearAllMocks();
    jest.resetAllMocks();
    jest.restoreAllMocks();
    
    // 清理自定义mocks
    this.mockRegistry.clear();
    this.spyRegistry.clear();
    
    // 恢复全局对象
    if (global.fetch && global.fetch.mockRestore) {
      global.fetch.mockRestore();
    }
  }
  
  // 获取Mock统计
  getMockStats() {
    const stats = {
      totalMocks: this.mockRegistry.size,
      mockTypes: {},
      activeMocks: []
    };
    
    this.mockRegistry.forEach((mockInfo, mock) => {
      if (!stats.mockTypes[mockInfo.type]) {
        stats.mockTypes[mockInfo.type] = 0;
      }
      stats.mockTypes[mockInfo.type]++;
      
      stats.activeMocks.push({
        type: mockInfo.type,
        created: mockInfo.created,
        age: Date.now() - mockInfo.created
      });
    });
    
    return stats;
  }
  
  // 获取状态
  getStatus() {
    return {
      config: this.config,
      testFiles: this.testFiles.length,
      mockStats: this.getMockStats(),
      lastResults: this.testResults.slice(-5)
    };
  }
  
  // 清理
  cleanup() {
    this.cleanupMocks();
    this.testFiles = [];
    this.testResults = [];
  }
}
相关推荐
web前端1236 小时前
React 状态管理方案对比分析
前端
南北是北北7 小时前
TextureView中的surfaceTexture的作用
前端·面试
web前端1237 小时前
HTML 和 React Native 元素排列方式对比
前端
w_y_fan7 小时前
Flutter中页面拦截器的实现方法
前端·flutter
快起来搬砖了7 小时前
Uniapp 图片前端上传功能实现与详解
前端·uni-app
南北是北北7 小时前
BufferQueue的环形队列是什么设计的
前端·面试
南北是北北7 小时前
Surface中的BufferQueue
前端·面试
willlzq7 小时前
Swift高级特性深度解析:@dynamicMemberLookup与@dynamicCallable在Builder库中的应用
前端
张旭超7 小时前
vue3 上传插件vue-file-agent-next
前端·vue.js