wot-design-uni/.github/TESTING.md
不如摸鱼去 7e84c5c91f
feat: 引入vitest做组件测试
* chore: 🚀 引入vitest做组件测试

* chore: 🚀 引入vitest做组件测试

* chore: 🚀 update workflow

* chore: 🚀 update workflow

* chore: 🚀 update workflow

* chore: 🚀 update workflow

* chore: 🚀 update nodejs version

* chore: 🚀 update nodejs version
2025-05-06 13:38:08 +08:00

7.9 KiB
Raw Permalink Blame History

wot-design-uni 测试指南

本文档提供了 wot-design-uni 组件库的测试策略、工作流程和最佳实践指南。

目录

测试策略

wot-design-uni 采用以下测试策略:

分层测试

  • 单元测试:测试组件的独立功能和属性
  • 集成测试:测试组件之间的交互和组合使用
  • 快照测试:确保 UI 不会意外变化

测试覆盖率目标

  • 整体代码覆盖率85%+
  • 关键组件(如 ConfigProvider、Button、Input 等90%+
  • 工具函数95%+

测试粒度

  • 每个组件至少有一个测试文件
  • 每个组件的主要功能都应有对应的测试用例
  • 边缘情况和错误处理也应有测试用例

测试平台

  • H5 平台:所有组件都在 H5 平台上测试
  • 条件编译:虽然我们只在 H5 平台上测试,但测试环境会正确处理条件编译代码

测试工作流

wot-design-uni 使用 GitHub Actions 自动化测试流程,主要工作流文件是 .github/workflows/component-testing.yml

触发条件

测试工作流在以下情况下会自动触发:

  1. 推送到主分支:当代码推送到 main、master 或 dev 分支,且修改了组件相关文件时
  2. 创建 Pull Request:当创建针对 main、master 或 dev 分支的 PR且修改了组件相关文件时
  3. 定时运行:每周一凌晨 3 点自动运行所有测试
  4. 手动触发:可以在 GitHub Actions 页面手动触发,并指定要测试的组件

工作流步骤

  1. 确定测试矩阵

    • 根据变更的文件确定需要测试的组件
  2. ESLint 检查

    • 运行 ESLint 检查,确保代码质量
  3. 组件测试

    • 针对确定的组件运行 H5 平台测试
    • 生成测试覆盖率报告
    • 上传测试结果到 Codecov
  4. 测试摘要

    • 生成测试摘要报告
    • 在 PR 中添加测试结果评论

如何运行测试

本地运行测试

运行所有测试

# 运行所有测试
pnpm test

# 运行所有测试并生成覆盖率报告
pnpm coverage

# 在 H5 平台上运行所有测试
pnpm test:h5

GitHub Actions 中运行测试

  1. 导航到仓库的 Actions 标签页
  2. 从左侧列表中选择 "Component Testing" 工作流
  3. 点击 "Run workflow" 按钮
  4. 可以选择性地指定要测试的组件名称例如wd-button
  5. 点击 "Run workflow" 开始测试

查看测试结果

测试完成后,你可以:

  1. 在工作流运行详情页面查看测试结果
  2. 下载测试报告和覆盖率报告
  3. 在 Codecov 上查看详细的覆盖率信息
  4. 如果是 PR可以在 PR 评论中看到测试摘要

编写测试

测试文件结构

测试文件应放在 tests/components 目录下,命名为 {组件名}.test.ts

每个测试文件的基本结构如下:

import { mount } from '@vue/test-utils'
import { describe, test, expect, vi } from 'vitest'
import WdComponent from '../../src/uni_modules/wot-design-uni/components/wd-component/wd-component.vue'

describe('WdComponent', () => {
  // 测试基本渲染
  test('基本渲染', () => {
    const wrapper = mount(WdComponent)
    expect(wrapper.classes()).toContain('wd-component')
  })

  // 更多测试...
})

测试用例编写指南

1. 测试组件渲染

test('基本渲染', () => {
  const wrapper = mount(WdButton)
  expect(wrapper.classes()).toContain('wd-button')
})

2. 测试组件属性

test('按钮类型', () => {
  const wrapper = mount(WdButton, {
    props: { type: 'primary' }
  })
  expect(wrapper.classes()).toContain('wd-button--primary')
})

3. 测试组件事件

test('点击事件', async () => {
  const wrapper = mount(WdButton)
  await wrapper.trigger('click')
  expect(wrapper.emitted('click')).toBeTruthy()
})

4. 测试组件插槽

test('默认插槽', () => {
  const wrapper = mount(WdButton, {
    slots: { default: '按钮文本' }
  })
  expect(wrapper.text()).toContain('按钮文本')
})

5. 测试异步行为

test('异步加载', async () => {
  const wrapper = mount(WdComponent, {
    props: { loading: true }
  })

  expect(wrapper.classes()).toContain('is-loading')

  await wrapper.setProps({ loading: false })
  expect(wrapper.classes()).not.toContain('is-loading')
})

条件编译测试

虽然我们只在 H5 平台上测试,但可以使用条件编译来测试平台特定代码:

test('平台特定功能', () => {
  // 我们在 H5 平台上测试
  // process.env.UNI_PLATFORM 会被设置为 'h5'
  if (process.env.UNI_PLATFORM === 'h5') {
    // H5 特定测试
    // ...
  }
})

最佳实践

1. 使用真实组件

  • 测试真实组件,而不是模拟组件
  • 只在必要时模拟依赖
// 推荐
const wrapper = mount(WdButton)

// 不推荐(除非必要)
const MockButton = {
  template: '<button class="wd-button"></button>'
}
const wrapper = mount(MockButton)

2. 测试边缘情况

  • 测试组件在各种边缘情况下的行为
  • 包括错误处理、极限值等
test('处理无效输入', () => {
  const wrapper = mount(WdInput, {
    props: { maxlength: 5 }
  })

  wrapper.setValue('123456')
  expect(wrapper.vm.value).toBe('12345')
})

3. 保持测试简单

  • 每个测试只测试一个功能点
  • 避免复杂的测试逻辑
// 推荐
test('按钮禁用状态', () => {
  const wrapper = mount(WdButton, {
    props: { disabled: true }
  })
  expect(wrapper.classes()).toContain('is-disabled')
})

test('按钮禁用时不触发点击事件', async () => {
  const wrapper = mount(WdButton, {
    props: { disabled: true }
  })
  await wrapper.trigger('click')
  expect(wrapper.emitted('click')).toBeFalsy()
})

// 不推荐
test('按钮禁用状态和事件', async () => {
  const wrapper = mount(WdButton, {
    props: { disabled: true }
  })
  expect(wrapper.classes()).toContain('is-disabled')
  await wrapper.trigger('click')
  expect(wrapper.emitted('click')).toBeFalsy()
})

4. 使用中文测试描述

  • 使用中文编写测试用例的标题,保持一致性
// 推荐
test('基本渲染', () => {
  // ...
})

// 不推荐
test('renders with default props', () => {
  // ...
})

5. 定期更新测试

  • 随着组件的更新,及时更新测试用例
  • 保持测试覆盖率不下降

常见问题

1. 测试中的异步问题

如果测试中的异步行为不按预期工作,可以尝试:

// 使用 await nextTick()
import { nextTick } from 'vue'

test('异步行为', async () => {
  const wrapper = mount(WdComponent)
  wrapper.vm.doSomethingAsync()
  await nextTick()
  // 断言...
})

// 或使用 setTimeout
test('延迟行为', async () => {
  const wrapper = mount(WdComponent)
  wrapper.vm.doSomethingWithDelay()
  await new Promise(resolve => setTimeout(resolve, 100))
  // 断言...
})

2. 模拟 uni-app API

在测试中模拟 uni-app API

// 在 setup.ts 中
vi.mock('uni-app', () => ({
  uni: {
    showToast: vi.fn(),
    navigateTo: vi.fn(),
    // 其他需要模拟的 API...
  }
}))

// 在测试中
import { uni } from 'uni-app'

test('调用 uni API', async () => {
  const wrapper = mount(WdComponent)
  await wrapper.find('.trigger').trigger('click')
  expect(uni.showToast).toHaveBeenCalled()
})

参考资料