# 测试文档 ## 测试概述 AIOTAGRO 管理系统采用全面的测试策略,确保系统质量和稳定性。测试覆盖单元测试、集成测试和端到端测试。 ## 测试环境 ### 环境要求 - **Node.js**: 18.0.0+ - **pnpm**: 8.0.0+ - **浏览器**: Chrome 90+, Firefox 85+, Safari 14+ ### 测试工具栈 | 工具 | 版本 | 用途 | |------|------|------| | Vitest | 1.0.0+ | 单元测试框架 | | Vue Test Utils | 2.4.0+ | Vue 组件测试 | | Playwright | 1.40.0+ | E2E 测试 | | Testing Library | 6.0.0+ | 组件测试工具 | ## 单元测试 ### 测试配置 ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { globals: true, environment: 'jsdom', include: ['**/__tests__/**/*.spec.ts'], coverage: { reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'dist/', '**/*.d.ts', '**/types/**' ] } } }) ``` ### 组件测试示例 ```typescript // __tests__/components/UserInfo.spec.ts import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import UserInfo from '@/components/UserInfo.vue' describe('UserInfo', () => { it('renders user information correctly', () => { const user = { id: 1, name: '张三', email: 'zhangsan@example.com', avatar: '/avatar.jpg' } const wrapper = mount(UserInfo, { props: { user } }) expect(wrapper.text()).toContain('张三') expect(wrapper.text()).toContain('zhangsan@example.com') expect(wrapper.find('img').attributes('src')).toBe('/avatar.jpg') }) it('emits edit event when edit button is clicked', async () => { const user = { id: 1, name: '张三', email: 'zhangsan@example.com' } const wrapper = mount(UserInfo, { props: { user } }) await wrapper.find('[data-testid="edit-btn"]').trigger('click') expect(wrapper.emitted('edit')).toBeTruthy() }) }) ``` ### 工具函数测试 ```typescript // __tests__/utils/format.spec.ts import { describe, it, expect } from 'vitest' import { formatDate, formatCurrency } from '@/utils/format' describe('format utils', () => { describe('formatDate', () => { it('formats date correctly', () => { const date = new Date('2023-12-01') expect(formatDate(date)).toBe('2023-12-01') }) it('handles invalid date', () => { expect(formatDate(null)).toBe('') expect(formatDate(undefined)).toBe('') }) }) describe('formatCurrency', () => { it('formats currency correctly', () => { expect(formatCurrency(1234.56)).toBe('¥1,234.56') expect(formatCurrency(0)).toBe('¥0.00') }) }) }) ``` ## 集成测试 ### API 集成测试 ```typescript // __tests__/api/user.spec.ts import { describe, it, expect, vi, beforeEach } from 'vitest' import { userApi } from '@/api/user' import { mockServer } from '../mocks/server' describe('User API', () => { beforeEach(() => { mockServer.listen() }) afterEach(() => { mockServer.resetHandlers() }) afterAll(() => { mockServer.close() }) it('fetches user list successfully', async () => { const response = await userApi.getUsers({ page: 1, size: 10 }) expect(response.status).toBe(200) expect(response.data.list).toHaveLength(2) expect(response.data.total).toBe(2) }) it('handles API errors correctly', async () => { mockServer.use( rest.get('/api/users', (req, res, ctx) => { return res(ctx.status(500), ctx.json({ message: 'Internal Server Error' })) }) ) await expect(userApi.getUsers({ page: 1, size: 10 })).rejects.toThrow() }) }) ``` ### 状态管理测试 ```typescript // __tests__/stores/user.spec.ts import { describe, it, expect } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { useUserStore } from '@/stores/user' describe('User Store', () => { beforeEach(() => { setActivePinia(createPinia()) }) it('initializes with default values', () => { const store = useUserStore() expect(store.user).toBeNull() expect(store.isLoggedIn).toBe(false) expect(store.loading).toBe(false) }) it('sets user correctly', () => { const store = useUserStore() const user = { id: 1, name: '张三', email: 'zhangsan@example.com' } store.setUser(user) expect(store.user).toEqual(user) expect(store.isLoggedIn).toBe(true) }) it('clears user on logout', () => { const store = useUserStore() const user = { id: 1, name: '张三', email: 'zhangsan@example.com' } store.setUser(user) store.logout() expect(store.user).toBeNull() expect(store.isLoggedIn).toBe(false) }) }) ``` ## E2E 测试 ### 测试配置 ```typescript // playwright.config.ts import { defineConfig, devices } from '@playwright/test' export default defineConfig({ testDir: './__tests__/e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'html', use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'] }, }, { name: 'webkit', use: { ...devices['Desktop Safari'] }, }, ], webServer: { command: 'pnpm dev:antd', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, }, }) ``` ### 登录流程测试 ```typescript // __tests__/e2e/login.spec.ts import { test, expect } from '@playwright/test' test.describe('Login Flow', () => { test('should login successfully with valid credentials', async ({ page }) => { await page.goto('/login') // 填写登录表单 await page.fill('[data-testid="username"]', 'admin') await page.fill('[data-testid="password"]', '123456') await page.click('[data-testid="login-btn"]') // 验证登录成功 await expect(page).toHaveURL('/dashboard') await expect(page.locator('[data-testid="user-name"]')).toContainText('管理员') }) test('should show error message with invalid credentials', async ({ page }) => { await page.goto('/login') // 填写错误凭证 await page.fill('[data-testid="username"]', 'wronguser') await page.fill('[data-testid="password"]', 'wrongpass') await page.click('[data-testid="login-btn"]') // 验证错误提示 await expect(page.locator('[data-testid="error-message"]')).toBeVisible() await expect(page).toHaveURL('/login') }) test('should redirect to login when accessing protected page without authentication', async ({ page }) => { // 直接访问受保护页面 await page.goto('/user-management') // 验证重定向到登录页 await expect(page).toHaveURL('/login') }) }) ``` ### 用户管理测试 ```typescript // __tests__/e2e/user-management.spec.ts import { test, expect } from '@playwright/test' test.describe('User Management', () => { test.beforeEach(async ({ page }) => { // 登录 await page.goto('/login') await page.fill('[data-testid="username"]', 'admin') await page.fill('[data-testid="password"]', '123456') await page.click('[data-testid="login-btn"]') await page.goto('/user-management') }) test('should display user list', async ({ page }) => { await expect(page.locator('[data-testid="user-table"]')).toBeVisible() await expect(page.locator('[data-testid="user-row"]').first()).toBeVisible() }) test('should create new user', async ({ page }) => { // 点击新建按钮 await page.click('[data-testid="create-user-btn"]') // 填写用户信息 await page.fill('[data-testid="user-name"]', '测试用户') await page.fill('[data-testid="user-email"]', 'test@example.com') await page.click('[data-testid="save-btn"]') // 验证创建成功 await expect(page.locator('[data-testid="success-message"]')).toBeVisible() await expect(page.locator('[data-testid="user-table"]')).toContainText('测试用户') }) test('should delete user', async ({ page }) => { // 点击删除按钮 await page.click('[data-testid="delete-user-btn"]').first() await page.click('[data-testid="confirm-delete-btn"]') // 验证删除成功 await expect(page.locator('[data-testid="success-message"]')).toBeVisible() }) }) ``` ## 性能测试 ### 加载性能测试 ```typescript // __tests__/performance/loading.spec.ts import { test, expect } from '@playwright/test' test.describe('Performance Tests', () => { test('should load dashboard within 3 seconds', async ({ page }) => { const startTime = Date.now() await page.goto('/dashboard') const loadTime = Date.now() - startTime expect(loadTime).toBeLessThan(3000) }) test('should render user table with 1000 rows efficiently', async ({ page }) => { await page.goto('/user-management') // 模拟大量数据 await page.evaluate(() => { window.performance.mark('table-render-start') }) // 等待表格渲染完成 await page.waitForSelector('[data-testid="user-table"]') const renderTime = await page.evaluate(() => { window.performance.mark('table-render-end') window.performance.measure('table-render', 'table-render-start', 'table-render-end') const measure = window.performance.getEntriesByName('table-render')[0] return measure.duration }) expect(renderTime).toBeLessThan(1000) }) }) ``` ## 安全测试 ### XSS 防护测试 ```typescript // __tests__/security/xss.spec.ts import { test, expect } from '@playwright/test' test.describe('Security Tests', () => { test('should sanitize user input to prevent XSS', async ({ page }) => { await page.goto('/user-management') // 尝试注入 XSS 代码 const xssPayload = '' await page.fill('[data-testid="user-name"]', xssPayload) await page.click('[data-testid="save-btn"]') // 验证输入被正确转义 const userNameCell = await page.locator('[data-testid="user-name"]').first() const innerHTML = await userNameCell.innerHTML() expect(innerHTML).not.toContain('