插件测试

为什么要测试?

Umi 3 我们采用微内核的架构,意味着大部分功能以插件的形式加载。

所以插件质量很大程度决定了 Umi 整体功能的稳定性

当插件有良好的测试用例,能带给很多保障:

  1. 功能迭代、持续集成
  2. 更详细的用法
  3. 利于代码重构

那么 Umi 插件的测试包括:

  • 单元测试(必选)占 95%
    • 纯函数测试
    • 临时文件测试
    • html 测试
  • E2E(可选)占 5%
  • 基准测试(可选)

测试框架

注:建议用于测试的 Node.js 版本 ≥ 10

只需要在 package.json 上配置好 scripts 即可:

  1. {
  2. "scripts": {
  3. "test": "umi-test"
  4. },
  5. "optionalDependencies": {
  6. "puppeteer": "^2.1.0"
  7. },
  8. "devDependencies": {
  9. "umi": "^3.0.0-beta.7",
  10. "@types/jest": "^25.1.2",
  11. "@umijs/test": "^3.0.0-beta.1"
  12. }
  13. }

测试约定

目录规范

  1. .
  2. ├── package.json
  3. ├── src
  4. ├── fixtures # 适用于插件单测的 umi 项目集
  5. └── normal
  6. └── pages
  7. ├── index.test.ts # 插件测试用例
  8. ├── index.ts # 插件主文件
  9. ├── utils.test.ts # 工具类函数测试
  10. └── utils.ts
  11. ├── example # 可用于 E2E 测试,一个完整的 umi 项目
  12. ├── test # e2e 测试用例
  13. └── index.e2e.ts
  14. ├── tsconfig.json
  15. ├── .fatherrc.ts
  16. └── yarn.lock

其中 src/fixtures/* 可用于测试 umi 各生命周期的项目,配置如下:

  1. // src/fixtures/normal/.umirc.ts
  2. export default {
  3. history: 'memory',
  4. mountElementId: '',
  5. routes: [{ path: '/', component: './index' }],
  6. };
jest 配置模块映射为了保持测试项目与真实 umi 项目一致性,我们需要将一些模块路径做映射,有 bug,没跑通:js // jest.config.js module.exports = { moduleNameMapper: { // 确保 import {} from 'umi' 正常 work '^@@/core/umiExports$': '<rootDir>/src/fixtures/.umi-test/core/umiExports.ts', }, };

单元测试

插件单元测试可以拆分成:

  • 纯函数测试:不依赖 umi 的纯函数进行测试
  • 临时文件测试:.umi-test 项目入口文件的测试
  • html 测试:对生成出来的 index.html 进行测试

我们以 umi-plugin-bar 插件为例,循序渐进地学习 Umi 插件测试。

插件功能

umi-plugin-bar 插件提供的功能有:

  • umi 可以导出常用的 utils 方法
  • 根据配置的 config.ga = { code: 'yourId' },加载一段 ga 统计脚本

纯函数测试

这里我们约定测试用例使用 test 书写单测,不推荐使用 describe + it 测试用例嵌套。

纯函数不依赖 umi,测试起来相对简单,建议将复杂功能点拆分成一个个纯函数,有利于插件功能更易测试。

  1. // src/utils.test.ts
  2. import { getUserName } from './utils';
  3. test('getUserName', () => {
  4. expect(getUserName('hello world')).toEqual('hello world');
  5. });

临时文件测试

为了测试导出的工具类函数在组件里能正常使用,先创建一个首页 src/fixtures/normal/index.tsx

  1. // 真实使用:import { getUsername } from 'umi';
  2. // TODO: jest moduleNameMapper 映射 @@/core/umiExports 有 bug
  3. import { getUserName } from '../.umi-test/plugin-utils/utils';
  4. export default () => <h1>{getUsername('Hello World')}</h1>;

对依赖 umi 的部分,可以通过从 umi 中创建一个 Service 对象。(@umijs/coreService 不内置插件)

然后用 @testing-library/react 组件渲染库来渲染出我们的组件。

  1. // src/index.test.ts
  2. import { join } from 'path';
  3. import { Service } from 'umi';
  4. import { render } from '@testing-library/react';
  5. const fixtures = join(__dirname, './fixtures');
  6. test('normal tmp', async () => {
  7. const cwd = join(fixtures, 'normal');
  8. const service = new Service({
  9. cwd,
  10. plugins: [require.resolve('./')],
  11. });
  12. // 用于产生临时文件
  13. await service.run({
  14. name: 'g',
  15. args: {
  16. _: ['g', 'tmp'],
  17. },
  18. });
  19. const reactNode = require(join(cwd, '.umi-test', 'umi.ts')).default;
  20. const { container } = render(reactNode);
  21. expect(container.textContent).toEqual('Hello World');
  22. });

html 测试

src/fixtures/normal/.umirc.ts 配置中添加 ga: { code: 'testId' } 方便测试 html 功能。

临时文件测试,测试 html 生成时,我们只需将 service 执行的参数 tmp 换成 html

  1. // index.test.ts
  2. test('normal html', async () => {
  3. const cwd = join(fixtures, 'normal');
  4. const service = new Service({
  5. cwd,
  6. plugins: [require.resolve('./')],
  7. });
  8. await service.run({
  9. name: 'g',
  10. args: {
  11. _: ['g', 'html'],
  12. },
  13. });
  14. const html = readFileSync(join(cwd, 'dist', 'index.html'), 'utf-8');
  15. expect(html).toContain('https://www.googletagmanager.com/gtag/js?id=testId');
  16. });

运行

运行 yarn test,测试用例就通过了,🎉

  1. yarn test
  2. $ umi-test
  3. PASS src/utils.test.ts
  4. getUserName (3ms)
  5. PASS src/index.test.ts
  6. normal (1661ms)
  7. normal html (529ms)
  8. Test Suites: 2 passed, 2 total
  9. Tests: 3 passed, 3 total
  10. Snapshots: 0 total
  11. Time: 4.257s
  12. Ran all test suites.
  13. Write: dist/index.html
  14. Done in 5.40s.

如果你喜欢 TDD(测试驱动开发),可以使用 yarn test -w 监听,更多用法

E2E 测试

TODO

示例代码

完整实例代码可参照:

TODO

  • Umi UI 插件测试方案