Skip to content
On this page

Monorepo 测试策略

测试挑战

在 Monorepo 环境中进行测试面临独特的挑战:

  1. 测试范围确定: 确定需要运行哪些测试
  2. 依赖关系管理: 处理包之间的依赖关系
  3. 测试执行效率: 在大型项目中保持测试效率
  4. 测试隔离: 确保测试的独立性
  5. 测试数据管理: 在多个包间共享测试数据

测试策略设计

1. 分层测试架构

在 Monorepo 中实施分层测试策略:

├── Unit Tests (包内测试)
├── Integration Tests (包间集成)
├── E2E Tests (端到端测试)
└── Contract Tests (契约测试)

2. 影子测试(Affected Testing)

仅运行受代码更改影响的测试:

bash
# Nx 影子测试
nx affected:test --base=main --head=HEAD

# 或使用当前分支对比
nx affected:test

3. 测试并行化

利用并行执行提高测试效率:

json
// package.json
{
  "scripts": {
    "test:parallel": "jest --maxWorkers=50%",
    "test:affected": "nx affected:test --parallel=3"
  }
}

测试配置管理

1. 共享测试配置

创建基础测试配置供所有包使用:

javascript
// jest.config.base.js
module.exports = {
  testEnvironment: 'node',
  collectCoverageFrom: [
    'src/**/*.{js,jsx,ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/index.{js,ts}'
  ],
  moduleNameMapper: {
    '^@/utils/(.*)$': '<rootDir>/../utils/src/$1'
  }
};

2. 包特定配置

每个包可以有特定的测试配置:

javascript
// packages/my-package/jest.config.js
const baseConfig = require('../../jest.config.base');

module.exports = {
  ...baseConfig,
  name: 'my-package',
  displayName: 'My Package',
  testMatch: ['<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}']
};

3. 全局测试配置

在根目录管理全局测试设置:

json
// package.json (root)
{
  "jest": {
    "projects": [
      "<rootDir>/packages/*"
    ],
    "collectCoverage": true,
    "coverageReporters": ["text", "lcov"]
  }
}

依赖测试

1. 包间依赖测试

测试包之间的依赖关系:

typescript
// packages/core/src/__tests__/integration.test.ts
import { someFunction } from '@myorg/utils';
import { CoreClass } from '../core';

describe('Core-Utils integration', () => {
  it('should work with utils package', () => {
    const core = new CoreClass();
    const result = core.useUtility(someFunction);
    expect(result).toBeDefined();
  });
});

2. 契约测试

确保包之间的接口兼容性:

typescript
// packages/api-contracts/src/__tests__/contract.test.ts
import { validateResponse } from '@myorg/validator';
import { apiClient } from '@myorg/api-client';

describe('API Contract', () => {
  it('should return expected response format', async () => {
    const response = await apiClient.getData();
    const isValid = validateResponse(response);
    expect(isValid).toBe(true);
  });
});

3. 版本兼容性测试

测试不同版本间的兼容性:

typescript
// packages/compatibility-tests/src/version-compat.test.ts
describe('Version compatibility', () => {
  it('should maintain backward compatibility', () => {
    // 测试新版本是否兼容旧版本的 API
    expect(new APIv2()).toImplement(APIv1Interface);
  });
});

测试优化策略

1. 测试缓存

实现测试结果缓存:

yaml
# GitHub Actions 示例
- name: Cache Jest
  id: cache-jest
  uses: actions/cache@v3
  with:
    path: .cache/jest
    key: ${{ runner.os }}-jest-${{ hashFiles('**/package-lock.json') }}

2. 测试分片

将大型测试套件分片执行:

bash
# Jest 分片测试
jest --shard=1/4
jest --shard=2/4
jest --shard=3/4
jest --shard=4/4

3. 选择性测试

根据更改类型运行特定测试:

json
// package.json
{
  "scripts": {
    "test:unit": "jest --testPathPattern=unit",
    "test:integration": "jest --testPathPattern=integration",
    "test:changed": "nx affected:test --target=test:unit"
  }
}

CI/CD 中的测试

1. 分阶段测试

在 CI/CD 中实施分阶段测试:

yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
      - run: pnpm install
      - run: pnpm nx affected:test --target=test:unit
  
  integration-tests:
    needs: unit-tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16.x, 18.x]
    steps:
      - uses: actions/checkout@v3
      - uses: pnpm/action-setup@v2
        with:
          version: ${{ matrix.node-version }}
      - run: pnpm install
      - run: pnpm nx affected:test --target=test:integration

2. 测试覆盖率

维护测试覆盖率标准:

json
// package.json
{
  "jest": {
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

3. 性能测试

包含性能测试:

typescript
// packages/core/src/__tests__/performance.test.ts
describe('Performance tests', () => {
  it('should process data within time limit', async () => {
    const start = performance.now();
    await processData(largeDataset);
    const end = performance.now();
    
    expect(end - start).toBeLessThan(1000); // 1秒内完成
  });
});

测试工具集成

1. Nx 测试集成

利用 Nx 的测试功能:

json
// nx.json
{
  "targetDefaults": {
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["default", "^default"],
      "cache": true
    }
  }
}

2. 测试报告

生成统一的测试报告:

json
// package.json
{
  "scripts": {
    "test:report": "jest --coverage --coverageReporters=lcov --testResultsProcessor=jest-junit"
  }
}

3. Mock 管理

在 Monorepo 中统一管理 Mock:

typescript
// packages/test-utils/src/mocks/index.ts
export { createMockUser } from './user.mock';
export { createMockAPI } from './api.mock';
export { createMockStore } from './store.mock';

测试数据管理

1. 共享测试数据

创建共享的测试数据工厂:

typescript
// packages/test-data/src/factories/index.ts
import { UserFactory } from './user.factory';
import { ProductFactory } from './product.factory';

export const TestData = {
  users: UserFactory,
  products: ProductFactory
};

2. 测试数据库

管理集成测试的数据库:

typescript
// packages/test-utils/src/database.ts
export class TestDatabase {
  static async setup() {
    // 设置测试数据库
  }
  
  static async teardown() {
    // 清理测试数据库
  }
}

测试最佳实践

1. 测试命名约定

使用一致的测试命名:

typescript
// packages/ui-components/src/button/__tests__/button.test.ts
describe('Button component', () => {
  describe('when clicked', () => {
    it('should trigger onClick handler', () => {
      // 测试逻辑
    });
  });
  
  describe('when disabled', () => {
    it('should not respond to clicks', () => {
      // 测试逻辑
    });
  });
});

2. 测试组织结构

按功能组织测试:

packages/
└── my-package/
    ├── src/
    │   ├── features/
    │   │   ├── auth/
    │   │   │   ├── login.ts
    │   │   │   └── __tests__/
    │   │   │       └── login.test.ts
    │   │   └── profile/
    │   │       ├── profile.ts
    │   │       └── __tests__/
    │   │           └── profile.test.ts

3. 测试环境

配置不同的测试环境:

typescript
// packages/my-package/src/__tests__/environment.test.ts
describe('Environment-specific tests', () => {
  beforeEach(() => {
    process.env.NODE_ENV = 'test';
  });
  
  afterEach(() => {
    jest.resetModules();
  });
  
  it('should behave differently in test environment', () => {
    // 测试逻辑
  });
});

质量门禁

1. 测试通过标准

设置测试通过的质量门禁:

yaml
# 在 CI 中设置质量门禁
- name: Check coverage
  run: |
    # 检查覆盖率是否满足要求
    if [ $COVERAGE -lt 80 ]; then
      echo "Coverage too low: $COVERAGE%"
      exit 1
    fi

2. 性能基准

设置性能基准测试:

typescript
// packages/performance-tests/src/benchmarks.test.ts
const PERFORMANCE_THRESHOLD = 1000; // 1秒

describe('Performance benchmarks', () => {
  it('should not exceed performance threshold', async () => {
    const startTime = Date.now();
    await runBenchmark();
    const duration = Date.now() - startTime;
    
    expect(duration).toBeLessThan(PERFORMANCE_THRESHOLD);
  });
});

总结

Monorepo 中的测试策略需要考虑包之间的依赖关系、测试执行效率和维护成本。关键要点包括:

  1. 分层测试: 实施单元、集成和端到端测试的分层策略
  2. 影子测试: 仅运行受更改影响的测试以提高效率
  3. 共享配置: 统一管理测试配置和工具
  4. 依赖测试: 确保包间依赖的正确性
  5. 性能优化: 通过缓存、并行化和分片优化测试执行
  6. 质量门禁: 设置自动化质量检查标准

通过实施这些测试策略,可以确保 Monorepo 项目的质量和稳定性。