mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 09:08:51 +08:00
* chore: 🚀 引入vitest做组件测试 * chore: 🚀 引入vitest做组件测试 * chore: 🚀 update workflow * chore: 🚀 update workflow * chore: 🚀 update workflow * chore: 🚀 update workflow * chore: 🚀 update nodejs version * chore: 🚀 update nodejs version
348 lines
7.9 KiB
Markdown
348 lines
7.9 KiB
Markdown
# 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 中添加测试结果评论
|
||
|
||
## 如何运行测试
|
||
|
||
### 本地运行测试
|
||
|
||
#### 运行所有测试
|
||
|
||
```bash
|
||
# 运行所有测试
|
||
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`。
|
||
|
||
每个测试文件的基本结构如下:
|
||
|
||
```typescript
|
||
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. 测试组件渲染
|
||
|
||
```typescript
|
||
test('基本渲染', () => {
|
||
const wrapper = mount(WdButton)
|
||
expect(wrapper.classes()).toContain('wd-button')
|
||
})
|
||
```
|
||
|
||
#### 2. 测试组件属性
|
||
|
||
```typescript
|
||
test('按钮类型', () => {
|
||
const wrapper = mount(WdButton, {
|
||
props: { type: 'primary' }
|
||
})
|
||
expect(wrapper.classes()).toContain('wd-button--primary')
|
||
})
|
||
```
|
||
|
||
#### 3. 测试组件事件
|
||
|
||
```typescript
|
||
test('点击事件', async () => {
|
||
const wrapper = mount(WdButton)
|
||
await wrapper.trigger('click')
|
||
expect(wrapper.emitted('click')).toBeTruthy()
|
||
})
|
||
```
|
||
|
||
#### 4. 测试组件插槽
|
||
|
||
```typescript
|
||
test('默认插槽', () => {
|
||
const wrapper = mount(WdButton, {
|
||
slots: { default: '按钮文本' }
|
||
})
|
||
expect(wrapper.text()).toContain('按钮文本')
|
||
})
|
||
```
|
||
|
||
#### 5. 测试异步行为
|
||
|
||
```typescript
|
||
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 平台上测试,但可以使用条件编译来测试平台特定代码:
|
||
|
||
```typescript
|
||
test('平台特定功能', () => {
|
||
// 我们在 H5 平台上测试
|
||
// process.env.UNI_PLATFORM 会被设置为 'h5'
|
||
if (process.env.UNI_PLATFORM === 'h5') {
|
||
// H5 特定测试
|
||
// ...
|
||
}
|
||
})
|
||
```
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 使用真实组件
|
||
|
||
- 测试真实组件,而不是模拟组件
|
||
- 只在必要时模拟依赖
|
||
|
||
```typescript
|
||
// 推荐
|
||
const wrapper = mount(WdButton)
|
||
|
||
// 不推荐(除非必要)
|
||
const MockButton = {
|
||
template: '<button class="wd-button"></button>'
|
||
}
|
||
const wrapper = mount(MockButton)
|
||
```
|
||
|
||
### 2. 测试边缘情况
|
||
|
||
- 测试组件在各种边缘情况下的行为
|
||
- 包括错误处理、极限值等
|
||
|
||
```typescript
|
||
test('处理无效输入', () => {
|
||
const wrapper = mount(WdInput, {
|
||
props: { maxlength: 5 }
|
||
})
|
||
|
||
wrapper.setValue('123456')
|
||
expect(wrapper.vm.value).toBe('12345')
|
||
})
|
||
```
|
||
|
||
### 3. 保持测试简单
|
||
|
||
- 每个测试只测试一个功能点
|
||
- 避免复杂的测试逻辑
|
||
|
||
```typescript
|
||
// 推荐
|
||
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. 使用中文测试描述
|
||
|
||
- 使用中文编写测试用例的标题,保持一致性
|
||
|
||
```typescript
|
||
// 推荐
|
||
test('基本渲染', () => {
|
||
// ...
|
||
})
|
||
|
||
// 不推荐
|
||
test('renders with default props', () => {
|
||
// ...
|
||
})
|
||
```
|
||
|
||
### 5. 定期更新测试
|
||
|
||
- 随着组件的更新,及时更新测试用例
|
||
- 保持测试覆盖率不下降
|
||
|
||
## 常见问题
|
||
|
||
### 1. 测试中的异步问题
|
||
|
||
如果测试中的异步行为不按预期工作,可以尝试:
|
||
|
||
```typescript
|
||
// 使用 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:
|
||
|
||
```typescript
|
||
// 在 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()
|
||
})
|
||
```
|
||
|
||
## 参考资料
|
||
|
||
- [Vue Test Utils 文档](https://test-utils.vuejs.org/)
|
||
- [Vitest 文档](https://vitest.dev/)
|
||
- [uni-app 条件编译](https://uniapp.dcloud.net.cn/tutorial/platform.html)
|
||
- [Jest 文档](https://jestjs.io/docs/getting-started)
|
||
- [Codecov 文档](https://docs.codecov.io/docs)
|