Skip to content

Commit 7e84c5c

Browse files
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
1 parent 60f2fe6 commit 7e84c5c

145 files changed

Lines changed: 27803 additions & 1373 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/TESTING.md

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
# wot-design-uni 测试指南
2+
3+
本文档提供了 wot-design-uni 组件库的测试策略、工作流程和最佳实践指南。
4+
5+
## 目录
6+
7+
- [测试策略](#测试策略)
8+
- [测试工作流](#测试工作流)
9+
- [如何运行测试](#如何运行测试)
10+
- [编写测试](#编写测试)
11+
- [最佳实践](#最佳实践)
12+
- [常见问题](#常见问题)
13+
- [参考资料](#参考资料)
14+
15+
## 测试策略
16+
17+
wot-design-uni 采用以下测试策略:
18+
19+
### 分层测试
20+
21+
- **单元测试**:测试组件的独立功能和属性
22+
- **集成测试**:测试组件之间的交互和组合使用
23+
- **快照测试**:确保 UI 不会意外变化
24+
25+
### 测试覆盖率目标
26+
27+
- **整体代码覆盖率**:85%+
28+
- **关键组件**(如 ConfigProvider、Button、Input 等):90%+
29+
- **工具函数**:95%+
30+
31+
### 测试粒度
32+
33+
- 每个组件至少有一个测试文件
34+
- 每个组件的主要功能都应有对应的测试用例
35+
- 边缘情况和错误处理也应有测试用例
36+
37+
### 测试平台
38+
39+
- **H5 平台**:所有组件都在 H5 平台上测试
40+
- **条件编译**:虽然我们只在 H5 平台上测试,但测试环境会正确处理条件编译代码
41+
42+
## 测试工作流
43+
44+
wot-design-uni 使用 GitHub Actions 自动化测试流程,主要工作流文件是 `.github/workflows/component-testing.yml`
45+
46+
### 触发条件
47+
48+
测试工作流在以下情况下会自动触发:
49+
50+
1. **推送到主分支**:当代码推送到 main、master 或 dev 分支,且修改了组件相关文件时
51+
2. **创建 Pull Request**:当创建针对 main、master 或 dev 分支的 PR,且修改了组件相关文件时
52+
3. **定时运行**:每周一凌晨 3 点自动运行所有测试
53+
4. **手动触发**:可以在 GitHub Actions 页面手动触发,并指定要测试的组件
54+
55+
### 工作流步骤
56+
57+
1. **确定测试矩阵**
58+
- 根据变更的文件确定需要测试的组件
59+
60+
2. **ESLint 检查**
61+
- 运行 ESLint 检查,确保代码质量
62+
63+
3. **组件测试**
64+
- 针对确定的组件运行 H5 平台测试
65+
- 生成测试覆盖率报告
66+
- 上传测试结果到 Codecov
67+
68+
4. **测试摘要**
69+
- 生成测试摘要报告
70+
- 在 PR 中添加测试结果评论
71+
72+
## 如何运行测试
73+
74+
### 本地运行测试
75+
76+
#### 运行所有测试
77+
78+
```bash
79+
# 运行所有测试
80+
pnpm test
81+
82+
# 运行所有测试并生成覆盖率报告
83+
pnpm coverage
84+
85+
# 在 H5 平台上运行所有测试
86+
pnpm test:h5
87+
```
88+
89+
### GitHub Actions 中运行测试
90+
91+
1. 导航到仓库的 Actions 标签页
92+
2. 从左侧列表中选择 "Component Testing" 工作流
93+
3. 点击 "Run workflow" 按钮
94+
4. 可以选择性地指定要测试的组件名称(例如:wd-button)
95+
5. 点击 "Run workflow" 开始测试
96+
97+
### 查看测试结果
98+
99+
测试完成后,你可以:
100+
101+
1. 在工作流运行详情页面查看测试结果
102+
2. 下载测试报告和覆盖率报告
103+
3. 在 Codecov 上查看详细的覆盖率信息
104+
4. 如果是 PR,可以在 PR 评论中看到测试摘要
105+
106+
## 编写测试
107+
108+
### 测试文件结构
109+
110+
测试文件应放在 `tests/components` 目录下,命名为 `{组件名}.test.ts`
111+
112+
每个测试文件的基本结构如下:
113+
114+
```typescript
115+
import { mount } from '@vue/test-utils'
116+
import { describe, test, expect, vi } from 'vitest'
117+
import WdComponent from '../../src/uni_modules/wot-design-uni/components/wd-component/wd-component.vue'
118+
119+
describe('WdComponent', () => {
120+
// 测试基本渲染
121+
test('基本渲染', () => {
122+
const wrapper = mount(WdComponent)
123+
expect(wrapper.classes()).toContain('wd-component')
124+
})
125+
126+
// 更多测试...
127+
})
128+
```
129+
130+
### 测试用例编写指南
131+
132+
#### 1. 测试组件渲染
133+
134+
```typescript
135+
test('基本渲染', () => {
136+
const wrapper = mount(WdButton)
137+
expect(wrapper.classes()).toContain('wd-button')
138+
})
139+
```
140+
141+
#### 2. 测试组件属性
142+
143+
```typescript
144+
test('按钮类型', () => {
145+
const wrapper = mount(WdButton, {
146+
props: { type: 'primary' }
147+
})
148+
expect(wrapper.classes()).toContain('wd-button--primary')
149+
})
150+
```
151+
152+
#### 3. 测试组件事件
153+
154+
```typescript
155+
test('点击事件', async () => {
156+
const wrapper = mount(WdButton)
157+
await wrapper.trigger('click')
158+
expect(wrapper.emitted('click')).toBeTruthy()
159+
})
160+
```
161+
162+
#### 4. 测试组件插槽
163+
164+
```typescript
165+
test('默认插槽', () => {
166+
const wrapper = mount(WdButton, {
167+
slots: { default: '按钮文本' }
168+
})
169+
expect(wrapper.text()).toContain('按钮文本')
170+
})
171+
```
172+
173+
#### 5. 测试异步行为
174+
175+
```typescript
176+
test('异步加载', async () => {
177+
const wrapper = mount(WdComponent, {
178+
props: { loading: true }
179+
})
180+
181+
expect(wrapper.classes()).toContain('is-loading')
182+
183+
await wrapper.setProps({ loading: false })
184+
expect(wrapper.classes()).not.toContain('is-loading')
185+
})
186+
```
187+
188+
### 条件编译测试
189+
190+
虽然我们只在 H5 平台上测试,但可以使用条件编译来测试平台特定代码:
191+
192+
```typescript
193+
test('平台特定功能', () => {
194+
// 我们在 H5 平台上测试
195+
// process.env.UNI_PLATFORM 会被设置为 'h5'
196+
if (process.env.UNI_PLATFORM === 'h5') {
197+
// H5 特定测试
198+
// ...
199+
}
200+
})
201+
```
202+
203+
## 最佳实践
204+
205+
### 1. 使用真实组件
206+
207+
- 测试真实组件,而不是模拟组件
208+
- 只在必要时模拟依赖
209+
210+
```typescript
211+
// 推荐
212+
const wrapper = mount(WdButton)
213+
214+
// 不推荐(除非必要)
215+
const MockButton = {
216+
template: '<button class="wd-button"></button>'
217+
}
218+
const wrapper = mount(MockButton)
219+
```
220+
221+
### 2. 测试边缘情况
222+
223+
- 测试组件在各种边缘情况下的行为
224+
- 包括错误处理、极限值等
225+
226+
```typescript
227+
test('处理无效输入', () => {
228+
const wrapper = mount(WdInput, {
229+
props: { maxlength: 5 }
230+
})
231+
232+
wrapper.setValue('123456')
233+
expect(wrapper.vm.value).toBe('12345')
234+
})
235+
```
236+
237+
### 3. 保持测试简单
238+
239+
- 每个测试只测试一个功能点
240+
- 避免复杂的测试逻辑
241+
242+
```typescript
243+
// 推荐
244+
test('按钮禁用状态', () => {
245+
const wrapper = mount(WdButton, {
246+
props: { disabled: true }
247+
})
248+
expect(wrapper.classes()).toContain('is-disabled')
249+
})
250+
251+
test('按钮禁用时不触发点击事件', async () => {
252+
const wrapper = mount(WdButton, {
253+
props: { disabled: true }
254+
})
255+
await wrapper.trigger('click')
256+
expect(wrapper.emitted('click')).toBeFalsy()
257+
})
258+
259+
// 不推荐
260+
test('按钮禁用状态和事件', async () => {
261+
const wrapper = mount(WdButton, {
262+
props: { disabled: true }
263+
})
264+
expect(wrapper.classes()).toContain('is-disabled')
265+
await wrapper.trigger('click')
266+
expect(wrapper.emitted('click')).toBeFalsy()
267+
})
268+
```
269+
270+
### 4. 使用中文测试描述
271+
272+
- 使用中文编写测试用例的标题,保持一致性
273+
274+
```typescript
275+
// 推荐
276+
test('基本渲染', () => {
277+
// ...
278+
})
279+
280+
// 不推荐
281+
test('renders with default props', () => {
282+
// ...
283+
})
284+
```
285+
286+
### 5. 定期更新测试
287+
288+
- 随着组件的更新,及时更新测试用例
289+
- 保持测试覆盖率不下降
290+
291+
## 常见问题
292+
293+
### 1. 测试中的异步问题
294+
295+
如果测试中的异步行为不按预期工作,可以尝试:
296+
297+
```typescript
298+
// 使用 await nextTick()
299+
import { nextTick } from 'vue'
300+
301+
test('异步行为', async () => {
302+
const wrapper = mount(WdComponent)
303+
wrapper.vm.doSomethingAsync()
304+
await nextTick()
305+
// 断言...
306+
})
307+
308+
// 或使用 setTimeout
309+
test('延迟行为', async () => {
310+
const wrapper = mount(WdComponent)
311+
wrapper.vm.doSomethingWithDelay()
312+
await new Promise(resolve => setTimeout(resolve, 100))
313+
// 断言...
314+
})
315+
```
316+
317+
### 2. 模拟 uni-app API
318+
319+
在测试中模拟 uni-app API:
320+
321+
```typescript
322+
// 在 setup.ts 中
323+
vi.mock('uni-app', () => ({
324+
uni: {
325+
showToast: vi.fn(),
326+
navigateTo: vi.fn(),
327+
// 其他需要模拟的 API...
328+
}
329+
}))
330+
331+
// 在测试中
332+
import { uni } from 'uni-app'
333+
334+
test('调用 uni API', async () => {
335+
const wrapper = mount(WdComponent)
336+
await wrapper.find('.trigger').trigger('click')
337+
expect(uni.showToast).toHaveBeenCalled()
338+
})
339+
```
340+
341+
## 参考资料
342+
343+
- [Vue Test Utils 文档](https://test-utils.vuejs.org/)
344+
- [Vitest 文档](https://vitest.dev/)
345+
- [uni-app 条件编译](https://uniapp.dcloud.net.cn/tutorial/platform.html)
346+
- [Jest 文档](https://jestjs.io/docs/getting-started)
347+
- [Codecov 文档](https://docs.codecov.io/docs)

.github/workflows/alipay.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
- name: Setup Node.js
4141
uses: actions/setup-node@v4
4242
with:
43-
node-version: 18
43+
node-version: 20
4444

4545
- uses: pnpm/action-setup@v4
4646
name: Install pnpm

0 commit comments

Comments
 (0)