code-abyss 1.6.16 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +2 -2
  2. package/skills/SKILL.md +24 -16
  3. package/skills/domains/ai/SKILL.md +2 -2
  4. package/skills/domains/ai/prompt-and-eval.md +279 -0
  5. package/skills/domains/architecture/SKILL.md +2 -3
  6. package/skills/domains/architecture/security-arch.md +87 -0
  7. package/skills/domains/data-engineering/SKILL.md +188 -26
  8. package/skills/domains/development/SKILL.md +1 -4
  9. package/skills/domains/devops/SKILL.md +3 -5
  10. package/skills/domains/devops/performance.md +63 -0
  11. package/skills/domains/devops/testing.md +97 -0
  12. package/skills/domains/frontend-design/SKILL.md +12 -3
  13. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  14. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  15. package/skills/domains/frontend-design/engineering.md +287 -0
  16. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  17. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  18. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  19. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  20. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  21. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  22. package/skills/domains/infrastructure/SKILL.md +174 -34
  23. package/skills/domains/mobile/SKILL.md +211 -21
  24. package/skills/domains/orchestration/SKILL.md +1 -0
  25. package/skills/domains/security/SKILL.md +4 -6
  26. package/skills/domains/security/blue-team.md +57 -0
  27. package/skills/domains/security/red-team.md +54 -0
  28. package/skills/domains/security/threat-intel.md +50 -0
  29. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  30. package/skills/run_skill.js +134 -0
  31. package/skills/tools/gen-docs/SKILL.md +6 -4
  32. package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
  33. package/skills/tools/verify-change/SKILL.md +8 -6
  34. package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
  35. package/skills/tools/verify-module/SKILL.md +6 -4
  36. package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
  37. package/skills/tools/verify-quality/SKILL.md +5 -3
  38. package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
  39. package/skills/tools/verify-security/SKILL.md +7 -5
  40. package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
  41. package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
  42. package/skills/domains/COVERAGE_PLAN.md +0 -232
  43. package/skills/domains/ai/model-evaluation.md +0 -790
  44. package/skills/domains/ai/prompt-engineering.md +0 -703
  45. package/skills/domains/architecture/compliance.md +0 -299
  46. package/skills/domains/architecture/data-security.md +0 -184
  47. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  48. package/skills/domains/data-engineering/data-quality.md +0 -894
  49. package/skills/domains/data-engineering/stream-processing.md +0 -791
  50. package/skills/domains/development/dart.md +0 -963
  51. package/skills/domains/development/kotlin.md +0 -834
  52. package/skills/domains/development/php.md +0 -659
  53. package/skills/domains/development/swift.md +0 -755
  54. package/skills/domains/devops/e2e-testing.md +0 -914
  55. package/skills/domains/devops/performance-testing.md +0 -734
  56. package/skills/domains/devops/testing-strategy.md +0 -667
  57. package/skills/domains/frontend-design/build-tools.md +0 -743
  58. package/skills/domains/frontend-design/performance.md +0 -734
  59. package/skills/domains/frontend-design/testing.md +0 -699
  60. package/skills/domains/infrastructure/gitops.md +0 -735
  61. package/skills/domains/infrastructure/iac.md +0 -855
  62. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  63. package/skills/domains/mobile/android-dev.md +0 -979
  64. package/skills/domains/mobile/cross-platform.md +0 -795
  65. package/skills/domains/mobile/ios-dev.md +0 -931
  66. package/skills/domains/security/secrets-management.md +0 -834
  67. package/skills/domains/security/supply-chain.md +0 -931
  68. package/skills/domains/security/threat-modeling.md +0 -828
  69. package/skills/run_skill.py +0 -153
  70. package/skills/tests/README.md +0 -225
  71. package/skills/tests/SUMMARY.md +0 -362
  72. package/skills/tests/__init__.py +0 -3
  73. package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
  74. package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
  75. package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
  76. package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
  77. package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
  78. package/skills/tests/test_change_analyzer.py +0 -558
  79. package/skills/tests/test_doc_generator.py +0 -538
  80. package/skills/tests/test_module_scanner.py +0 -376
  81. package/skills/tests/test_quality_checker.py +0 -516
  82. package/skills/tests/test_security_scanner.py +0 -426
  83. package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
  84. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
  85. package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
  86. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  87. package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
  88. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  89. package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
  90. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  91. package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
  92. package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
@@ -1,699 +0,0 @@
1
- ---
2
- name: testing
3
- description: 前端测试技术。Vitest、Playwright、Jest、Cypress、测试金字塔、E2E测试、单元测试、集成测试。当用户提到前端测试、Vitest、Playwright、E2E测试、单元测试、测试覆盖率时使用。
4
- ---
5
-
6
- # 🎨 🧪 前端测试 · Frontend Testing
7
-
8
- ## 测试金字塔
9
-
10
- ```
11
- /\
12
- / \ E2E Tests (10%)
13
- /----\
14
- / \ Integration Tests (20%)
15
- /--------\
16
- / \ Unit Tests (70%)
17
- /____________\
18
- ```
19
-
20
- | 层级 | 数量 | 速度 | 成本 | 信心 |
21
- |------|------|------|------|------|
22
- | E2E | 少 | 慢 | 高 | 高 |
23
- | 集成 | 中 | 中 | 中 | 中 |
24
- | 单元 | 多 | 快 | 低 | 低 |
25
-
26
- ## 测试策略决策树
27
-
28
- ```
29
- 需要测试什么?
30
-
31
- ├─ 纯函数/工具 → 单元测试 (Vitest/Jest)
32
-
33
- ├─ React 组件
34
- │ ├─ UI 渲染 → 组件测试 (Testing Library)
35
- │ ├─ 交互逻辑 → 集成测试
36
- │ └─ 视觉回归 → Chromatic/Percy
37
-
38
- ├─ API 集成 → MSW Mock + 集成测试
39
-
40
- └─ 用户流程 → E2E 测试 (Playwright/Cypress)
41
- ```
42
-
43
- ## Vitest (推荐)
44
-
45
- ### 基础配置
46
-
47
- ```typescript
48
- // vitest.config.ts
49
- import { defineConfig } from 'vitest/config'
50
- import react from '@vitejs/plugin-react'
51
-
52
- export default defineConfig({
53
- plugins: [react()],
54
- test: {
55
- globals: true,
56
- environment: 'jsdom',
57
- setupFiles: './src/test/setup.ts',
58
- coverage: {
59
- provider: 'v8',
60
- reporter: ['text', 'json', 'html'],
61
- exclude: ['node_modules/', 'src/test/'],
62
- },
63
- },
64
- })
65
- ```
66
-
67
- ### 单元测试
68
-
69
- ```typescript
70
- // utils.test.ts
71
- import { describe, it, expect } from 'vitest'
72
- import { formatCurrency, debounce } from './utils'
73
-
74
- describe('formatCurrency', () => {
75
- it('formats number to currency', () => {
76
- expect(formatCurrency(1234.56)).toBe('$1,234.56')
77
- })
78
-
79
- it('handles zero', () => {
80
- expect(formatCurrency(0)).toBe('$0.00')
81
- })
82
-
83
- it('handles negative numbers', () => {
84
- expect(formatCurrency(-100)).toBe('-$100.00')
85
- })
86
- })
87
-
88
- describe('debounce', () => {
89
- it('delays function execution', async () => {
90
- let count = 0
91
- const fn = debounce(() => count++, 100)
92
-
93
- fn()
94
- fn()
95
- fn()
96
-
97
- expect(count).toBe(0)
98
-
99
- await new Promise((resolve) => setTimeout(resolve, 150))
100
- expect(count).toBe(1)
101
- })
102
- })
103
- ```
104
-
105
- ### React 组件测试
106
-
107
- ```typescript
108
- // Button.test.tsx
109
- import { render, screen, fireEvent } from '@testing-library/react'
110
- import { describe, it, expect, vi } from 'vitest'
111
- import { Button } from './Button'
112
-
113
- describe('Button', () => {
114
- it('renders with text', () => {
115
- render(<Button>Click me</Button>)
116
- expect(screen.getByText('Click me')).toBeInTheDocument()
117
- })
118
-
119
- it('calls onClick when clicked', () => {
120
- const handleClick = vi.fn()
121
- render(<Button onClick={handleClick}>Click</Button>)
122
-
123
- fireEvent.click(screen.getByText('Click'))
124
- expect(handleClick).toHaveBeenCalledTimes(1)
125
- })
126
-
127
- it('is disabled when disabled prop is true', () => {
128
- render(<Button disabled>Click</Button>)
129
- expect(screen.getByRole('button')).toBeDisabled()
130
- })
131
-
132
- it('applies variant styles', () => {
133
- render(<Button variant="primary">Click</Button>)
134
- expect(screen.getByRole('button')).toHaveClass('btn-primary')
135
- })
136
- })
137
- ```
138
-
139
- ### Hooks 测试
140
-
141
- ```typescript
142
- // useCounter.test.ts
143
- import { renderHook, act } from '@testing-library/react'
144
- import { describe, it, expect } from 'vitest'
145
- import { useCounter } from './useCounter'
146
-
147
- describe('useCounter', () => {
148
- it('initializes with default value', () => {
149
- const { result } = renderHook(() => useCounter())
150
- expect(result.current.count).toBe(0)
151
- })
152
-
153
- it('increments counter', () => {
154
- const { result } = renderHook(() => useCounter())
155
-
156
- act(() => {
157
- result.current.increment()
158
- })
159
-
160
- expect(result.current.count).toBe(1)
161
- })
162
-
163
- it('decrements counter', () => {
164
- const { result } = renderHook(() => useCounter(5))
165
-
166
- act(() => {
167
- result.current.decrement()
168
- })
169
-
170
- expect(result.current.count).toBe(4)
171
- })
172
-
173
- it('resets counter', () => {
174
- const { result } = renderHook(() => useCounter(10))
175
-
176
- act(() => {
177
- result.current.increment()
178
- result.current.reset()
179
- })
180
-
181
- expect(result.current.count).toBe(10)
182
- })
183
- })
184
- ```
185
-
186
- ### 异步测试
187
-
188
- ```typescript
189
- // api.test.ts
190
- import { describe, it, expect, vi, beforeEach } from 'vitest'
191
- import { fetchUser, createUser } from './api'
192
-
193
- // Mock fetch
194
- global.fetch = vi.fn()
195
-
196
- describe('API', () => {
197
- beforeEach(() => {
198
- vi.clearAllMocks()
199
- })
200
-
201
- it('fetches user successfully', async () => {
202
- const mockUser = { id: '1', name: 'John' }
203
- ;(fetch as any).mockResolvedValueOnce({
204
- ok: true,
205
- json: async () => mockUser,
206
- })
207
-
208
- const user = await fetchUser('1')
209
- expect(user).toEqual(mockUser)
210
- expect(fetch).toHaveBeenCalledWith('/api/users/1')
211
- })
212
-
213
- it('handles fetch error', async () => {
214
- ;(fetch as any).mockResolvedValueOnce({
215
- ok: false,
216
- status: 404,
217
- })
218
-
219
- await expect(fetchUser('999')).rejects.toThrow('User not found')
220
- })
221
-
222
- it('creates user', async () => {
223
- const newUser = { name: 'Jane', email: 'jane@example.com' }
224
- const createdUser = { id: '2', ...newUser }
225
-
226
- ;(fetch as any).mockResolvedValueOnce({
227
- ok: true,
228
- json: async () => createdUser,
229
- })
230
-
231
- const result = await createUser(newUser)
232
- expect(result).toEqual(createdUser)
233
- })
234
- })
235
- ```
236
-
237
- ## MSW (Mock Service Worker)
238
-
239
- ### 配置 MSW
240
-
241
- ```typescript
242
- // src/mocks/handlers.ts
243
- import { http, HttpResponse } from 'msw'
244
-
245
- export const handlers = [
246
- http.get('/api/users/:id', ({ params }) => {
247
- const { id } = params
248
- return HttpResponse.json({
249
- id,
250
- name: 'John Doe',
251
- email: 'john@example.com',
252
- })
253
- }),
254
-
255
- http.post('/api/users', async ({ request }) => {
256
- const body = await request.json()
257
- return HttpResponse.json(
258
- { id: '123', ...body },
259
- { status: 201 }
260
- )
261
- }),
262
-
263
- http.delete('/api/users/:id', () => {
264
- return new HttpResponse(null, { status: 204 })
265
- }),
266
- ]
267
-
268
- // src/mocks/server.ts
269
- import { setupServer } from 'msw/node'
270
- import { handlers } from './handlers'
271
-
272
- export const server = setupServer(...handlers)
273
-
274
- // src/test/setup.ts
275
- import { beforeAll, afterEach, afterAll } from 'vitest'
276
- import { server } from '../mocks/server'
277
-
278
- beforeAll(() => server.listen())
279
- afterEach(() => server.resetHandlers())
280
- afterAll(() => server.close())
281
- ```
282
-
283
- ### 使用 MSW 测试
284
-
285
- ```typescript
286
- // UserProfile.test.tsx
287
- import { render, screen, waitFor } from '@testing-library/react'
288
- import { describe, it, expect } from 'vitest'
289
- import { server } from '../mocks/server'
290
- import { http, HttpResponse } from 'msw'
291
- import { UserProfile } from './UserProfile'
292
-
293
- describe('UserProfile', () => {
294
- it('displays user data', async () => {
295
- render(<UserProfile userId="1" />)
296
-
297
- await waitFor(() => {
298
- expect(screen.getByText('John Doe')).toBeInTheDocument()
299
- expect(screen.getByText('john@example.com')).toBeInTheDocument()
300
- })
301
- })
302
-
303
- it('handles loading state', () => {
304
- render(<UserProfile userId="1" />)
305
- expect(screen.getByText('Loading...')).toBeInTheDocument()
306
- })
307
-
308
- it('handles error state', async () => {
309
- server.use(
310
- http.get('/api/users/:id', () => {
311
- return new HttpResponse(null, { status: 500 })
312
- })
313
- )
314
-
315
- render(<UserProfile userId="1" />)
316
-
317
- await waitFor(() => {
318
- expect(screen.getByText('Error loading user')).toBeInTheDocument()
319
- })
320
- })
321
- })
322
- ```
323
-
324
- ## Playwright (E2E 推荐)
325
-
326
- ### 配置 Playwright
327
-
328
- ```typescript
329
- // playwright.config.ts
330
- import { defineConfig, devices } from '@playwright/test'
331
-
332
- export default defineConfig({
333
- testDir: './e2e',
334
- fullyParallel: true,
335
- forbidOnly: !!process.env.CI,
336
- retries: process.env.CI ? 2 : 0,
337
- workers: process.env.CI ? 1 : undefined,
338
- reporter: 'html',
339
- use: {
340
- baseURL: 'http://localhost:3000',
341
- trace: 'on-first-retry',
342
- screenshot: 'only-on-failure',
343
- },
344
- projects: [
345
- {
346
- name: 'chromium',
347
- use: { ...devices['Desktop Chrome'] },
348
- },
349
- {
350
- name: 'firefox',
351
- use: { ...devices['Desktop Firefox'] },
352
- },
353
- {
354
- name: 'webkit',
355
- use: { ...devices['Desktop Safari'] },
356
- },
357
- {
358
- name: 'Mobile Chrome',
359
- use: { ...devices['Pixel 5'] },
360
- },
361
- ],
362
- webServer: {
363
- command: 'npm run dev',
364
- url: 'http://localhost:3000',
365
- reuseExistingServer: !process.env.CI,
366
- },
367
- })
368
- ```
369
-
370
- ### 基础 E2E 测试
371
-
372
- ```typescript
373
- // e2e/login.spec.ts
374
- import { test, expect } from '@playwright/test'
375
-
376
- test.describe('Login', () => {
377
- test('successful login', async ({ page }) => {
378
- await page.goto('/login')
379
-
380
- await page.fill('input[name="email"]', 'user@example.com')
381
- await page.fill('input[name="password"]', 'password123')
382
- await page.click('button[type="submit"]')
383
-
384
- await expect(page).toHaveURL('/dashboard')
385
- await expect(page.locator('h1')).toContainText('Dashboard')
386
- })
387
-
388
- test('shows error for invalid credentials', async ({ page }) => {
389
- await page.goto('/login')
390
-
391
- await page.fill('input[name="email"]', 'wrong@example.com')
392
- await page.fill('input[name="password"]', 'wrongpass')
393
- await page.click('button[type="submit"]')
394
-
395
- await expect(page.locator('.error')).toContainText('Invalid credentials')
396
- })
397
-
398
- test('validates required fields', async ({ page }) => {
399
- await page.goto('/login')
400
- await page.click('button[type="submit"]')
401
-
402
- await expect(page.locator('input[name="email"]:invalid')).toBeVisible()
403
- })
404
- })
405
- ```
406
-
407
- ### Page Object Model
408
-
409
- ```typescript
410
- // e2e/pages/LoginPage.ts
411
- import { Page, Locator } from '@playwright/test'
412
-
413
- export class LoginPage {
414
- readonly page: Page
415
- readonly emailInput: Locator
416
- readonly passwordInput: Locator
417
- readonly submitButton: Locator
418
- readonly errorMessage: Locator
419
-
420
- constructor(page: Page) {
421
- this.page = page
422
- this.emailInput = page.locator('input[name="email"]')
423
- this.passwordInput = page.locator('input[name="password"]')
424
- this.submitButton = page.locator('button[type="submit"]')
425
- this.errorMessage = page.locator('.error')
426
- }
427
-
428
- async goto() {
429
- await this.page.goto('/login')
430
- }
431
-
432
- async login(email: string, password: string) {
433
- await this.emailInput.fill(email)
434
- await this.passwordInput.fill(password)
435
- await this.submitButton.click()
436
- }
437
- }
438
-
439
- // 使用 Page Object
440
- test('login with page object', async ({ page }) => {
441
- const loginPage = new LoginPage(page)
442
- await loginPage.goto()
443
- await loginPage.login('user@example.com', 'password123')
444
-
445
- await expect(page).toHaveURL('/dashboard')
446
- })
447
- ```
448
-
449
- ### API 测试
450
-
451
- ```typescript
452
- // e2e/api.spec.ts
453
- import { test, expect } from '@playwright/test'
454
-
455
- test.describe('API', () => {
456
- test('GET /api/users', async ({ request }) => {
457
- const response = await request.get('/api/users')
458
- expect(response.ok()).toBeTruthy()
459
-
460
- const users = await response.json()
461
- expect(users).toHaveLength(10)
462
- expect(users[0]).toHaveProperty('id')
463
- expect(users[0]).toHaveProperty('name')
464
- })
465
-
466
- test('POST /api/users', async ({ request }) => {
467
- const response = await request.post('/api/users', {
468
- data: {
469
- name: 'New User',
470
- email: 'new@example.com',
471
- },
472
- })
473
-
474
- expect(response.status()).toBe(201)
475
- const user = await response.json()
476
- expect(user.name).toBe('New User')
477
- })
478
-
479
- test('handles authentication', async ({ request }) => {
480
- const response = await request.get('/api/protected', {
481
- headers: {
482
- Authorization: 'Bearer token123',
483
- },
484
- })
485
-
486
- expect(response.ok()).toBeTruthy()
487
- })
488
- })
489
- ```
490
-
491
- ### 视觉回归测试
492
-
493
- ```typescript
494
- // e2e/visual.spec.ts
495
- import { test, expect } from '@playwright/test'
496
-
497
- test.describe('Visual Regression', () => {
498
- test('homepage screenshot', async ({ page }) => {
499
- await page.goto('/')
500
- await expect(page).toHaveScreenshot('homepage.png')
501
- })
502
-
503
- test('button states', async ({ page }) => {
504
- await page.goto('/components')
505
-
506
- const button = page.locator('button.primary')
507
- await expect(button).toHaveScreenshot('button-default.png')
508
-
509
- await button.hover()
510
- await expect(button).toHaveScreenshot('button-hover.png')
511
-
512
- await button.focus()
513
- await expect(button).toHaveScreenshot('button-focus.png')
514
- })
515
-
516
- test('responsive layout', async ({ page }) => {
517
- await page.goto('/')
518
-
519
- // Desktop
520
- await page.setViewportSize({ width: 1920, height: 1080 })
521
- await expect(page).toHaveScreenshot('desktop.png')
522
-
523
- // Tablet
524
- await page.setViewportSize({ width: 768, height: 1024 })
525
- await expect(page).toHaveScreenshot('tablet.png')
526
-
527
- // Mobile
528
- await page.setViewportSize({ width: 375, height: 667 })
529
- await expect(page).toHaveScreenshot('mobile.png')
530
- })
531
- })
532
- ```
533
-
534
- ## 测试最佳实践
535
-
536
- ### AAA 模式
537
-
538
- ```typescript
539
- test('user can add item to cart', async ({ page }) => {
540
- // Arrange - 准备测试环境
541
- await page.goto('/products')
542
- const product = page.locator('[data-testid="product-1"]')
543
-
544
- // Act - 执行操作
545
- await product.locator('button.add-to-cart').click()
546
-
547
- // Assert - 验证结果
548
- await expect(page.locator('.cart-count')).toHaveText('1')
549
- await expect(page.locator('.notification')).toContainText('Added to cart')
550
- })
551
- ```
552
-
553
- ### 测试隔离
554
-
555
- ```typescript
556
- import { test } from '@playwright/test'
557
-
558
- test.describe('Todo App', () => {
559
- test.beforeEach(async ({ page }) => {
560
- // 每个测试前重置状态
561
- await page.goto('/')
562
- await page.evaluate(() => localStorage.clear())
563
- })
564
-
565
- test('add todo', async ({ page }) => {
566
- await page.fill('input[name="todo"]', 'Buy milk')
567
- await page.click('button[type="submit"]')
568
- await expect(page.locator('.todo-item')).toHaveText('Buy milk')
569
- })
570
-
571
- test('delete todo', async ({ page }) => {
572
- // 独立的测试,不依赖前一个测试
573
- await page.fill('input[name="todo"]', 'Buy milk')
574
- await page.click('button[type="submit"]')
575
- await page.click('.todo-item button.delete')
576
- await expect(page.locator('.todo-item')).toHaveCount(0)
577
- })
578
- })
579
- ```
580
-
581
- ### 数据驱动测试
582
-
583
- ```typescript
584
- const testCases = [
585
- { input: 'hello', expected: 'HELLO' },
586
- { input: 'world', expected: 'WORLD' },
587
- { input: '123', expected: '123' },
588
- ]
589
-
590
- testCases.forEach(({ input, expected }) => {
591
- test(`converts "${input}" to "${expected}"`, () => {
592
- expect(toUpperCase(input)).toBe(expected)
593
- })
594
- })
595
-
596
- // Playwright 参数化
597
- const browsers = ['chromium', 'firefox', 'webkit']
598
-
599
- browsers.forEach((browserName) => {
600
- test(`works on ${browserName}`, async ({ browser }) => {
601
- const context = await browser.newContext()
602
- const page = await context.newPage()
603
- await page.goto('/')
604
- // 测试逻辑
605
- })
606
- })
607
- ```
608
-
609
- ## 覆盖率配置
610
-
611
- ```typescript
612
- // vitest.config.ts
613
- export default defineConfig({
614
- test: {
615
- coverage: {
616
- provider: 'v8',
617
- reporter: ['text', 'json', 'html', 'lcov'],
618
- include: ['src/**/*.{ts,tsx}'],
619
- exclude: [
620
- 'src/**/*.test.{ts,tsx}',
621
- 'src/**/*.spec.{ts,tsx}',
622
- 'src/test/**',
623
- 'src/**/*.d.ts',
624
- ],
625
- thresholds: {
626
- lines: 80,
627
- functions: 80,
628
- branches: 75,
629
- statements: 80,
630
- },
631
- },
632
- },
633
- })
634
- ```
635
-
636
- ## CI/CD 集成
637
-
638
- ```yaml
639
- # .github/workflows/test.yml
640
- name: Test
641
-
642
- on: [push, pull_request]
643
-
644
- jobs:
645
- unit-tests:
646
- runs-on: ubuntu-latest
647
- steps:
648
- - uses: actions/checkout@v3
649
- - uses: actions/setup-node@v3
650
- with:
651
- node-version: 18
652
- - run: npm ci
653
- - run: npm run test:unit
654
- - run: npm run test:coverage
655
-
656
- e2e-tests:
657
- runs-on: ubuntu-latest
658
- steps:
659
- - uses: actions/checkout@v3
660
- - uses: actions/setup-node@v3
661
- with:
662
- node-version: 18
663
- - run: npm ci
664
- - run: npx playwright install --with-deps
665
- - run: npm run test:e2e
666
- - uses: actions/upload-artifact@v3
667
- if: always()
668
- with:
669
- name: playwright-report
670
- path: playwright-report/
671
- ```
672
-
673
- ## 最佳实践清单
674
-
675
- - ✅ 遵循测试金字塔:70% 单元 + 20% 集成 + 10% E2E
676
- - ✅ 使用 AAA 模式组织测试
677
- - ✅ 测试行为而非实现细节
678
- - ✅ 保持测试独立和隔离
679
- - ✅ 使用有意义的测试描述
680
- - ✅ Mock 外部依赖(API、时间、随机数)
681
- - ✅ 测试边界条件和错误情况
682
- - ✅ 维护合理的覆盖率(80%+)
683
- - ✅ 在 CI/CD 中自动运行测试
684
- - ✅ 使用 Page Object 模式组织 E2E 测试
685
-
686
- ## 工具清单
687
-
688
- | 工具 | 用途 |
689
- |------|------|
690
- | Vitest | 单元测试框架 |
691
- | Playwright | E2E 测试框架 |
692
- | Testing Library | React 组件测试 |
693
- | MSW | API Mock |
694
- | Cypress | E2E 测试(备选) |
695
- | Chromatic | 视觉回归测试 |
696
- | Storybook | 组件开发和测试 |
697
- | Istanbul | 覆盖率报告 |
698
-
699
- ---