Skip to Content
🎲 欢迎使用 RandBox - 功能强大的 JavaScript 随机数据生成库! 了解详情
博客从零开始打造 RandBox:一个强大的 JavaScript 随机数据生成库(4/5)- 测试体系与质量保证

从零开始打造 RandBox:一个强大的 JavaScript 随机数据生成库(4/5)- 测试体系与质量保证

前言

在前三篇文章中,我们深入探讨了 RandBox 的规划、架构和功能实现。今天,我将分享如何为 RandBox 构建完善的测试体系,确保库的质量和可靠性。对于一个随机数据生成库而言,测试面临着独特的挑战:如何测试”随机”的结果?如何保证跨平台的一致性?这些问题都需要巧妙的解决方案。

测试策略设计

测试金字塔原则

RandBox 的测试体系遵循经典的测试金字塔原则:

/\ / \ / E2E \ (少量端到端测试) /______\ / \ / 集成测试 \ (中等数量) /____________\ / \ / 单元测试 \ (大量基础测试) /________________\

核心测试挑战

1. 随机性测试的悖论

随机数据生成的结果本质上是不可预测的,但测试需要可预测的断言。

解决方案:种子控制

// 使用固定种子确保测试结果可重现 const randBox = new RandBox('test-seed-123'); assert(randBox.integer(1, 10) === 7); // 在相同种子下总是返回7

2. 统计性质验证

需要验证生成数据的统计特性是否符合预期。

解决方案:大数定律

// 生成大量数据验证分布特性 test('integer distribution should be uniform', () => { const results = {}; const iterations = 10000; for (let i = 0; i < iterations; i++) { const value = randBox.integer(1, 10); results[value] = (results[value] || 0) + 1; } // 验证每个数字出现频率接近期望值 Object.values(results).forEach(count => { const expected = iterations / 10; const tolerance = expected * 0.1; // 10% 容差 assert(Math.abs(count - expected) < tolerance); }); });

单元测试实现

1. 基础功能测试框架

选择 AVA 作为测试框架,原因如下:

  • 简洁的 API 设计
  • 内置并行执行
  • 强大的断言库
  • 优秀的错误报告
// test/basics.test.js import test from 'ava'; import RandBox from '../src/index.js'; test.beforeEach(t => { // 每个测试使用相同的种子确保可重现性 t.context.randBox = new RandBox('test-seed'); }); test('integer() should return number within range', t => { const { randBox } = t.context; for (let i = 0; i < 100; i++) { const result = randBox.integer(1, 10); t.true(Number.isInteger(result)); t.true(result >= 1 && result <= 10); } }); test('integer() with exclude option', t => { const { randBox } = t.context; const exclude = [5, 7, 9]; for (let i = 0; i < 100; i++) { const result = randBox.integer({ min: 1, max: 10, exclude }); t.false(exclude.includes(result)); } });

2. 参数验证测试

test('integer() parameter validation', t => { const { randBox } = t.context; // 测试无效参数 t.throws(() => randBox.integer('invalid'), { instanceOf: TypeError, message: /Expected number/ }); // 测试范围错误 t.throws(() => randBox.integer(10, 5), { instanceOf: RangeError, message: /Min cannot be greater than max/ }); // 测试边界值 t.notThrows(() => randBox.integer(0, 0)); // 应该总是返回0 t.is(randBox.integer(0, 0), 0); });

3. 复杂功能测试

// 测试姓名生成 test('name() should generate valid names', t => { const { randBox } = t.context; // 中文姓名测试 const chineseName = randBox.name({ nationality: 'zh' }); t.true(typeof chineseName === 'string'); t.true(chineseName.length >= 2 && chineseName.length <= 4); // 英文姓名测试 const englishName = randBox.name({ nationality: 'en' }); t.true(englishName.includes(' ')); // 英文名包含空格 // 带前缀的姓名 const nameWithPrefix = randBox.name({ nationality: 'en', prefix: true, gender: 'male' }); t.true(nameWithPrefix.startsWith('Mr.') || nameWithPrefix.startsWith('Dr.')); }); // 测试邮箱生成 test('email() should generate valid email addresses', t => { const { randBox } = t.context; const email = randBox.email(); // 基本格式验证 t.regex(email, /^[^\s@]+@[^\s@]+\.[^\s@]+$/); // 自定义域名测试 const customEmail = randBox.email({ domain: 'example.com' }); t.true(customEmail.endsWith('@example.com')); });

性能测试与基准测试

1. 性能测试框架搭建

// test/performance.test.js import { performance } from 'perf_hooks'; import test from 'ava'; import RandBox from '../src/index.js'; function benchmark(name, fn, iterations = 10000) { const start = performance.now(); for (let i = 0; i < iterations; i++) { fn(); } const end = performance.now(); const duration = end - start; const opsPerSec = (iterations / duration) * 1000; console.log(`${name}: ${duration.toFixed(2)}ms (${opsPerSec.toFixed(0)} ops/sec)`); return { duration, opsPerSec }; } test('performance benchmarks', t => { const randBox = new RandBox(); // 基础功能性能测试 const integerPerf = benchmark('integer()', () => randBox.integer()); const stringPerf = benchmark('string()', () => randBox.string()); const namePerf = benchmark('name()', () => randBox.name()); // 性能要求:每秒至少处理10000次操作 t.true(integerPerf.opsPerSec > 10000); t.true(stringPerf.opsPerSec > 5000); t.true(namePerf.opsPerSec > 1000); });

2. 内存使用测试

test('memory usage should be reasonable', t => { const initialMemory = process.memoryUsage().heapUsed; // 创建多个实例 const instances = []; for (let i = 0; i < 1000; i++) { instances.push(new RandBox()); } // 生成大量数据 instances.forEach(randBox => { for (let j = 0; j < 100; j++) { randBox.name(); randBox.email(); randBox.address(); } }); const finalMemory = process.memoryUsage().heapUsed; const memoryIncrease = finalMemory - initialMemory; // 内存增长应该在合理范围内(小于50MB) t.true(memoryIncrease < 50 * 1024 * 1024); });

统计特性验证

1. 随机分布测试

// test/statistics.test.js import test from 'ava'; import RandBox from '../src/index.js'; function chiSquareTest(observed, expected, significance = 0.05) { let chiSquare = 0; for (let i = 0; i < observed.length; i++) { const diff = observed[i] - expected[i]; chiSquare += (diff * diff) / expected[i]; } // 简化的卡方检验(实际应用中需要查表) const degreesOfFreedom = observed.length - 1; const critical = degreesOfFreedom * 2; // 简化阈值 return chiSquare < critical; } test('integer distribution should pass chi-square test', t => { const randBox = new RandBox(); const buckets = new Array(10).fill(0); const iterations = 10000; for (let i = 0; i < iterations; i++) { const value = randBox.integer(0, 9); buckets[value]++; } const expected = new Array(10).fill(iterations / 10); const isUniform = chiSquareTest(buckets, expected); t.true(isUniform, 'Distribution should be approximately uniform'); }); test('floating point distribution', t => { const randBox = new RandBox(); const samples = []; for (let i = 0; i < 1000; i++) { samples.push(randBox.floating(0, 1)); } // 验证范围 samples.forEach(sample => { t.true(sample >= 0 && sample <= 1); }); // 验证均值接近0.5 const mean = samples.reduce((sum, val) => sum + val, 0) / samples.length; t.true(Math.abs(mean - 0.5) < 0.05); });

2. 正态分布测试

test('normal distribution should have correct properties', t => { const randBox = new RandBox(); const samples = []; const mean = 100; const stdDev = 15; for (let i = 0; i < 1000; i++) { samples.push(randBox.normal(mean, stdDev)); } // 计算样本均值和标准差 const sampleMean = samples.reduce((sum, val) => sum + val, 0) / samples.length; const variance = samples.reduce((sum, val) => sum + Math.pow(val - sampleMean, 2), 0) / samples.length; const sampleStdDev = Math.sqrt(variance); // 验证均值和标准差在合理范围内 t.true(Math.abs(sampleMean - mean) < 2); t.true(Math.abs(sampleStdDev - stdDev) < 2); });

兼容性测试

1. 多环境测试

// test/compatibility.test.js test('should work in different JavaScript environments', t => { // Node.js 环境测试 if (typeof window === 'undefined') { t.true(true, 'Running in Node.js environment'); } // 测试 ES6 模块导入 t.truthy(RandBox); t.true(typeof RandBox === 'function'); // 测试基本功能 const randBox = new RandBox(); t.true(typeof randBox.random === 'function'); }); // 浏览器环境测试(使用 Puppeteer) test('browser compatibility', async t => { const puppeteer = require('puppeteer'); const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.addScriptTag({ path: './dist/index.umd.js' }); const result = await page.evaluate(() => { const randBox = new window.RandBox(); return { integer: randBox.integer(1, 10), string: randBox.string(), name: randBox.name() }; }); t.true(typeof result.integer === 'number'); t.true(typeof result.string === 'string'); t.true(typeof result.name === 'string'); await browser.close(); });

2. TypeScript 类型测试

// test/types.test.ts import test from 'ava'; import RandBox from '../src/index'; test('TypeScript type definitions', t => { const randBox = new RandBox(); // 编译时类型检查 const num: number = randBox.integer(); const str: string = randBox.string(); const bool: boolean = randBox.bool(); // 可选参数类型检查 const customInt: number = randBox.integer({ min: 1, max: 10, exclude: [5] }); const customStr: string = randBox.string({ length: 20, alpha: true }); t.true(typeof num === 'number'); t.true(typeof str === 'string'); t.true(typeof bool === 'boolean'); });

持续集成配置

1. GitHub Actions 工作流

# .github/workflows/test.yml name: Test Suite on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node-version: [16.x, 18.x, 20.x] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} cache: 'npm' - name: Install dependencies run: npm ci - name: Run linting run: npm run lint - name: Run type checking run: npm run typecheck - name: Run tests run: npm test - name: Run coverage run: npm run coverage - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 browser-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '18' - name: Install dependencies run: npm ci - name: Build for browser run: npm run build - name: Run browser tests run: npm run test:browser

2. 测试覆盖率配置

// nyc.config.js module.exports = { include: ['src/**/*.ts'], exclude: [ 'src/**/*.test.ts', 'src/**/*.spec.ts', 'src/types.ts' ], reporter: ['text', 'html', 'lcov'], 'report-dir': './coverage', 'temp-dir': './coverage/.nyc_output', checkCoverage: true, lines: 90, statements: 90, functions: 90, branches: 85 };

质量保证流程

1. 代码质量检查

// .eslintrc.js module.exports = { extends: [ '@typescript-eslint/recommended', 'plugin:jest/recommended' ], rules: { // 自定义规则 'prefer-const': 'error', 'no-var': 'error', '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/no-explicit-any': 'error' } };

2. 自动化发布流程

{ "scripts": { "preversion": "npm run test && npm run build", "version": "npm run docs:build", "postversion": "git push && git push --tags", "prepublishOnly": "npm run test && npm run build", "release": "semantic-release" } }

3. 变更日志自动生成

// release.config.js module.exports = { branches: ['main'], plugins: [ '@semantic-release/commit-analyzer', '@semantic-release/release-notes-generator', '@semantic-release/changelog', '@semantic-release/npm', '@semantic-release/github' ] };

测试数据管理

1. 测试数据生成器

// test/helpers/test-data-generator.js export class TestDataGenerator { static generateTestCases(count = 100) { const cases = []; for (let i = 0; i < count; i++) { cases.push({ seed: `test-${i}`, expected: { integer: this.expectedInteger(`test-${i}`), string: this.expectedString(`test-${i}`), name: this.expectedName(`test-${i}`) } }); } return cases; } static expectedInteger(seed) { // 基于种子预计算期望值 const randBox = new RandBox(seed); return randBox.integer(1, 100); } }

2. 快照测试

test('output consistency snapshot test', t => { const randBox = new RandBox('snapshot-seed'); const output = { integers: Array(10).fill().map(() => randBox.integer(1, 100)), strings: Array(5).fill().map(() => randBox.string({ length: 10 })), names: Array(5).fill().map(() => randBox.name()), emails: Array(5).fill().map(() => randBox.email()) }; t.snapshot(output, 'Random output should be consistent'); });

下期预告

在最后一篇文章中,我将分享 RandBox 的文档建设、社区运营和生态发展,包括:

  • 现代化文档网站的构建
  • API 文档的自动生成
  • 社区贡献指南的制定
  • 插件生态的规划与实现

敬请期待《从零开始打造 RandBox(5/5)- 文档建设与生态发展》!


关于 RandBox

🏠 官网: https://randbox.top  📦 GitHub: https://github.com/027xiguapi/randbox  📚 文档: https://randbox.top/zh/docs 

如果这个项目对你有帮助,欢迎给我们一个 ⭐ Star!

最后更新于: