# 结伴客项目测试文档 ## 1. 测试概述 ### 1.1 测试目标 确保结伴客项目各个模块的功能正确性、性能稳定性、安全可靠性,为产品上线提供质量保障。 ### 1.2 测试范围 - **后端API服务**:接口功能、性能、安全测试 - **小程序应用**:功能、兼容性、用户体验测试 - **管理后台**:功能、权限、数据一致性测试 - **数据库**:数据完整性、性能、备份恢复测试 - **系统集成**:各模块间集成测试 ### 1.3 测试策略 ```mermaid graph TD A[测试策略] --> B[单元测试] A --> C[集成测试] A --> D[系统测试] A --> E[验收测试] B --> B1[代码覆盖率>80%] B --> B2[自动化执行] C --> C1[API集成测试] C --> C2[数据库集成测试] D --> D1[功能测试] D --> D2[性能测试] D --> D3[安全测试] E --> E1[用户验收测试] E --> E2[业务流程验证] ## 2. 测试环境 ### 2.1 环境配置 | 环境类型 | 用途 | 配置 | 数据库 | |---------|------|------|--------| | 开发环境 | 开发调试 | 本地Docker | MySQL 8.0 | | 测试环境 | 功能测试 | 测试服务器 | MySQL 8.0 | | 预发布环境 | 集成测试 | 生产同配置 | MySQL 8.0 | | 生产环境 | 线上服务 | 高可用集群 | MySQL 8.0主从 | ### 2.2 测试数据管理 ```yaml # 测试数据配置 test_data: users: - username: "test_user_001" email: "test001@example.com" role: "user" - username: "test_admin_001" email: "admin001@example.com" role: "admin" trips: - title: "测试旅行001" destination: "北京" start_date: "2024-06-01" end_date: "2024-06-07" animals: - name: "测试动物001" type: "cat" location: "北京动物园" ``` ## 3. 测试计划 ### 3.1 测试阶段 ```mermaid gantt title 测试计划时间线 dateFormat YYYY-MM-DD section 单元测试 后端单元测试 :ut1, 2024-03-01, 7d 前端单元测试 :ut2, 2024-03-01, 7d section 集成测试 API集成测试 :it1, after ut1, 5d 数据库集成测试 :it2, after ut1, 3d section 系统测试 功能测试 :st1, after it1, 10d 性能测试 :st2, after it1, 7d 安全测试 :st3, after it1, 5d section 验收测试 用户验收测试 :at1, after st1, 7d ``` ### 3.2 测试用例设计 #### 3.2.1 后端API测试用例 ```javascript // 用户注册接口测试用例 describe('用户注册API', () => { test('正常注册流程', async () => { const userData = { username: 'testuser001', email: 'test@example.com', password: 'Test123456', phone: '13800138000' }; const response = await request(app) .post('/api/auth/register') .send(userData) .expect(200); expect(response.body.code).toBe(0); expect(response.body.data.user.username).toBe(userData.username); }); test('重复邮箱注册', async () => { const response = await request(app) .post('/api/auth/register') .send({ username: 'testuser002', email: 'test@example.com', // 重复邮箱 password: 'Test123456' }) .expect(400); expect(response.body.code).toBe(40001); expect(response.body.message).toContain('邮箱已存在'); }); }); ``` #### 3.2.2 小程序功能测试用例 ```javascript // 小程序页面测试 describe('旅行结伴页面', () => { test('页面正常加载', async () => { const page = await miniProgram.reLaunch('/pages/trip/list'); await page.waitFor(2000); const title = await page.$('.page-title'); expect(await title.text()).toBe('旅行结伴'); }); test('创建旅行结伴', async () => { const page = await miniProgram.navigateTo('/pages/trip/create'); await page.setData({ 'form.title': '测试旅行', 'form.destination': '北京', 'form.startDate': '2024-06-01', 'form.endDate': '2024-06-07' }); await page.tap('.submit-btn'); await page.waitFor(1000); expect(page.path).toBe('/pages/trip/detail'); }); }); ``` ## 4. 性能测试 ### 4.1 性能指标 | 指标类型 | 目标值 | 测试方法 | |---------|--------|----------| | 接口响应时间 | < 500ms | JMeter压测 | | 页面加载时间 | < 2s | Lighthouse | | 并发用户数 | 1000+ | 压力测试 | | 数据库查询 | < 100ms | SQL性能分析 | | 内存使用率 | < 80% | 系统监控 | ### 4.2 JMeter压测脚本 ```xml false false continue false 100 50 10 ``` ### 4.3 Artillery性能测试 ```yaml # artillery-config.yml config: target: 'http://localhost:3000' phases: - duration: 60 arrivalRate: 10 - duration: 120 arrivalRate: 50 - duration: 60 arrivalRate: 100 scenarios: - name: "用户注册登录流程" flow: - post: url: "/api/auth/register" json: username: "test_{{ $randomString() }}" email: "{{ $randomString() }}@test.com" password: "Test123456" - post: url: "/api/auth/login" json: email: "{{ email }}" password: "Test123456" ``` ## 5. 安全测试 ### 5.1 安全测试范围 - **身份认证安全**:JWT令牌、密码加密 - **授权控制**:角色权限、接口鉴权 - **数据安全**:SQL注入、XSS攻击 - **传输安全**:HTTPS、数据加密 - **系统安全**:文件上传、敏感信息泄露 ### 5.2 OWASP ZAP安全扫描 ```yaml # zap-baseline-scan.yml version: '3' services: zap: image: owasp/zap2docker-stable command: zap-baseline.py -t http://host.docker.internal:3000 -r zap-report.html volumes: - ./reports:/zap/wrk/:rw ``` ### 5.3 安全测试用例 ```javascript // SQL注入测试 describe('SQL注入防护测试', () => { test('用户名SQL注入', async () => { const maliciousInput = "admin'; DROP TABLE users; --"; const response = await request(app) .post('/api/auth/login') .send({ username: maliciousInput, password: 'password' }); // 应该返回错误,而不是执行SQL expect(response.status).toBe(400); expect(response.body.message).toContain('参数格式错误'); }); }); // XSS攻击测试 describe('XSS防护测试', () => { test('评论内容XSS过滤', async () => { const xssPayload = ''; const response = await request(app) .post('/api/comments') .set('Authorization', `Bearer ${token}`) .send({ content: xssPayload, tripId: 1 }); expect(response.body.data.content).not.toContain('' const response = await request(app) .post('/api/animals') .send({ name: xssPayload, description: 'test' }) .expect(400) expect(response.body.message).toContain('输入包含非法字符') }) }) ``` #### 性能测试 ```javascript // 示例:API响应时间测试 describe('性能测试', () => { test('动物列表API响应时间应小于2秒', async () => { const startTime = Date.now() const response = await request(app) .get('/api/animals') .expect(200) const responseTime = Date.now() - startTime expect(responseTime).toBeLessThan(2000) }) test('并发请求处理能力', async () => { const promises = Array.from({ length: 100 }, () => request(app).get('/api/animals').expect(200) ) const startTime = Date.now() await Promise.all(promises) const totalTime = Date.now() - startTime expect(totalTime).toBeLessThan(10000) // 100个并发请求在10秒内完成 }) }) ``` ## 📝 测试用例设计 ### 用户认证模块测试用例 #### 用户注册测试 ```gherkin Feature: 用户注册 作为一个新用户 我想要注册账户 以便使用系统功能 Scenario: 成功注册新用户 Given 我在注册页面 When 我输入有效的用户信息 | 字段 | 值 | | 用户名 | testuser123 | | 邮箱 | test@example.com | | 密码 | Password123! | | 确认密码 | Password123! | And 我点击注册按钮 Then 我应该看到注册成功消息 And 我应该收到验证邮件 Scenario: 用户名已存在 Given 系统中已存在用户名为"existinguser"的用户 When 我尝试注册用户名为"existinguser"的账户 Then 我应该看到"用户名已存在"的错误消息 Scenario: 密码强度不足 Given 我在注册页面 When 我输入弱密码"123456" Then 我应该看到密码强度提示 And 注册按钮应该被禁用 ``` #### 动物管理测试用例 ```gherkin Feature: 动物信息管理 作为管理员 我想要管理动物信息 以便为用户提供准确的动物数据 Scenario: 添加新动物 Given 我以管理员身份登录 When 我填写动物信息表单 | 字段 | 值 | | 名称 | 小白 | | 物种 | 狗 | | 年龄 | 2岁 | | 性别 | 雌性 | | 描述 | 温顺可爱的小狗 | And 我上传动物照片 And 我点击保存按钮 Then 动物信息应该被成功保存 And 我应该在动物列表中看到新添加的动物 Scenario: 搜索动物 Given 系统中有多个动物记录 When 我在搜索框中输入"小白" And 我点击搜索按钮 Then 我应该看到包含"小白"的搜索结果 And 结果应该按相关性排序 ``` ### API接口测试用例 #### 动物API测试 ```javascript describe('动物API测试', () => { let authToken let testAnimalId beforeAll(async () => { // 获取认证token const loginResponse = await request(app) .post('/api/auth/login') .send({ username: 'admin', password: 'admin123' }) authToken = loginResponse.body.token }) describe('GET /api/animals', () => { test('应该返回动物列表', async () => { const response = await request(app) .get('/api/animals') .expect(200) expect(response.body).toHaveProperty('data') expect(response.body).toHaveProperty('total') expect(response.body).toHaveProperty('page') expect(Array.isArray(response.body.data)).toBe(true) }) test('应该支持分页参数', async () => { const response = await request(app) .get('/api/animals?page=1&limit=5') .expect(200) expect(response.body.data.length).toBeLessThanOrEqual(5) expect(response.body.page).toBe(1) }) test('应该支持搜索功能', async () => { const response = await request(app) .get('/api/animals?search=狗') .expect(200) response.body.data.forEach(animal => { expect( animal.name.includes('狗') || animal.species.includes('狗') || animal.description.includes('狗') ).toBe(true) }) }) }) describe('POST /api/animals', () => { test('管理员应该能够创建动物', async () => { const animalData = { name: '测试动物', species: '狗', breed: '金毛', gender: 'male', age_months: 24, description: '测试用动物' } const response = await request(app) .post('/api/animals') .set('Authorization', `Bearer ${authToken}`) .send(animalData) .expect(201) expect(response.body.data).toHaveProperty('id') expect(response.body.data.name).toBe(animalData.name) testAnimalId = response.body.data.id }) test('未认证用户不能创建动物', async () => { const animalData = { name: '测试动物', species: '狗' } await request(app) .post('/api/animals') .send(animalData) .expect(401) }) test('应该验证必填字段', async () => { const response = await request(app) .post('/api/animals') .set('Authorization', `Bearer ${authToken}`) .send({}) .expect(400) expect(response.body.errors).toContain('name is required') expect(response.body.errors).toContain('species is required') }) }) describe('PUT /api/animals/:id', () => { test('应该能够更新动物信息', async () => { const updateData = { name: '更新后的名称', description: '更新后的描述' } const response = await request(app) .put(`/api/animals/${testAnimalId}`) .set('Authorization', `Bearer ${authToken}`) .send(updateData) .expect(200) expect(response.body.data.name).toBe(updateData.name) expect(response.body.data.description).toBe(updateData.description) }) test('更新不存在的动物应该返回404', async () => { await request(app) .put('/api/animals/999999') .set('Authorization', `Bearer ${authToken}`) .send({ name: '测试' }) .expect(404) }) }) describe('DELETE /api/animals/:id', () => { test('应该能够删除动物', async () => { await request(app) .delete(`/api/animals/${testAnimalId}`) .set('Authorization', `Bearer ${authToken}`) .expect(200) // 验证动物已被删除 await request(app) .get(`/api/animals/${testAnimalId}`) .expect(404) }) }) }) ``` ## 🎭 前端测试 ### 组件测试 #### Vue组件测试 ```javascript // AnimalCard.test.js import { mount } from '@vue/test-utils' import { describe, it, expect, vi } from 'vitest' import AnimalCard from '@/components/AnimalCard.vue' describe('AnimalCard组件', () => { const mockAnimal = { id: 1, name: '小白', species: '狗', age_months: 24, gender: 'female', description: '可爱的小狗', images: ['https://example.com/image1.jpg'], status: 'available' } it('应该正确渲染动物信息', () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) expect(wrapper.find('.animal-name').text()).toBe('小白') expect(wrapper.find('.animal-species').text()).toBe('狗') expect(wrapper.find('.animal-age').text()).toContain('2岁') expect(wrapper.find('.animal-description').text()).toBe('可爱的小狗') }) it('应该显示动物图片', () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) const img = wrapper.find('.animal-image') expect(img.exists()).toBe(true) expect(img.attributes('src')).toBe(mockAnimal.images[0]) expect(img.attributes('alt')).toBe(mockAnimal.name) }) it('点击认领按钮应该触发事件', async () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) const adoptButton = wrapper.find('.adopt-button') await adoptButton.trigger('click') expect(wrapper.emitted('adopt')).toBeTruthy() expect(wrapper.emitted('adopt')[0]).toEqual([mockAnimal.id]) }) it('已认领的动物不应该显示认领按钮', () => { const adoptedAnimal = { ...mockAnimal, status: 'adopted' } const wrapper = mount(AnimalCard, { props: { animal: adoptedAnimal } }) expect(wrapper.find('.adopt-button').exists()).toBe(false) expect(wrapper.find('.adopted-label').exists()).toBe(true) }) it('应该处理图片加载错误', async () => { const wrapper = mount(AnimalCard, { props: { animal: mockAnimal } }) const img = wrapper.find('.animal-image') await img.trigger('error') expect(wrapper.find('.image-placeholder').exists()).toBe(true) }) }) ``` #### 页面测试 ```javascript // AnimalList.test.js import { mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import { describe, it, expect, vi, beforeEach } from 'vitest' import AnimalList from '@/pages/AnimalList.vue' import { useAnimalStore } from '@/stores/animal' // Mock API vi.mock('@/api/animal', () => ({ getAnimals: vi.fn(() => Promise.resolve({ data: [ { id: 1, name: '小白', species: '狗' }, { id: 2, name: '小黑', species: '猫' } ], total: 2, page: 1 })) })) describe('AnimalList页面', () => { beforeEach(() => { setActivePinia(createPinia()) }) it('应该加载并显示动物列表', async () => { const wrapper = mount(AnimalList) // 等待异步加载完成 await wrapper.vm.$nextTick() await new Promise(resolve => setTimeout(resolve, 100)) expect(wrapper.findAll('.animal-card')).toHaveLength(2) expect(wrapper.find('.animal-card').text()).toContain('小白') }) it('应该支持搜索功能', async () => { const wrapper = mount(AnimalList) const searchInput = wrapper.find('.search-input') await searchInput.setValue('小白') await searchInput.trigger('input') // 验证搜索参数被传递 const animalStore = useAnimalStore() expect(animalStore.searchParams.keyword).toBe('小白') }) it('应该支持筛选功能', async () => { const wrapper = mount(AnimalList) const speciesFilter = wrapper.find('.species-filter') await speciesFilter.setValue('狗') await speciesFilter.trigger('change') const animalStore = useAnimalStore() expect(animalStore.searchParams.species).toBe('狗') }) it('应该处理加载状态', () => { const wrapper = mount(AnimalList) // 模拟加载状态 const animalStore = useAnimalStore() animalStore.loading = true expect(wrapper.find('.loading-spinner').exists()).toBe(true) }) it('应该处理错误状态', async () => { // Mock API错误 vi.mocked(getAnimals).mockRejectedValueOnce(new Error('网络错误')) const wrapper = mount(AnimalList) await wrapper.vm.$nextTick() expect(wrapper.find('.error-message').exists()).toBe(true) expect(wrapper.find('.error-message').text()).toContain('加载失败') }) }) ``` ### E2E测试 #### Playwright E2E测试 ```javascript // e2e/animal-adoption.spec.js import { test, expect } from '@playwright/test' test.describe('动物认领流程', () => { test.beforeEach(async ({ page }) => { // 登录用户 await page.goto('/login') await page.fill('[data-testid="username"]', 'testuser') await page.fill('[data-testid="password"]', 'password123') await page.click('[data-testid="login-button"]') await expect(page).toHaveURL('/') }) test('完整的动物认领流程', async ({ page }) => { // 1. 浏览动物列表 await page.goto('/animals') await expect(page.locator('.animal-card')).toHaveCount.greaterThan(0) // 2. 搜索特定动物 await page.fill('[data-testid="search-input"]', '小白') await page.click('[data-testid="search-button"]') await expect(page.locator('.animal-card')).toContainText('小白') // 3. 查看动物详情 await page.click('.animal-card:first-child') await expect(page).toHaveURL(/\/animals\/\d+/) await expect(page.locator('.animal-detail')).toBeVisible() // 4. 申请认领 await page.click('[data-testid="adopt-button"]') await expect(page).toHaveURL(/\/adoption\/apply/) // 5. 填写认领申请表 await page.fill('[data-testid="applicant-name"]', '张三') await page.fill('[data-testid="applicant-phone"]', '13800138000') await page.fill('[data-testid="applicant-email"]', 'zhangsan@example.com') await page.fill('[data-testid="applicant-address"]', '北京市朝阳区') await page.selectOption('[data-testid="housing-type"]', 'apartment') await page.fill('[data-testid="adoption-reason"]', '我很喜欢小动物,希望给它一个温暖的家') // 6. 提交申请 await page.click('[data-testid="submit-application"]') await expect(page.locator('.success-message')).toContainText('申请提交成功') // 7. 查看申请状态 await page.goto('/user/adoptions') await expect(page.locator('.adoption-application')).toContainText('审核中') }) test('动物搜索和筛选功能', async ({ page }) => { await page.goto('/animals') // 测试搜索功能 await page.fill('[data-testid="search-input"]', '狗') await page.click('[data-testid="search-button"]') const animalCards = page.locator('.animal-card') const count = await animalCards.count() for (let i = 0; i < count; i++) { const card = animalCards.nth(i) const text = await card.textContent() expect(text).toMatch(/狗|犬/) } // 测试筛选功能 await page.selectOption('[data-testid="species-filter"]', '猫') await page.waitForLoadState('networkidle') const catCards = page.locator('.animal-card') const catCount = await catCards.count() for (let i = 0; i < catCount; i++) { const card = catCards.nth(i) const text = await card.textContent() expect(text).toContain('猫') } }) test('响应式设计测试', async ({ page }) => { // 测试桌面端 await page.setViewportSize({ width: 1200, height: 800 }) await page.goto('/animals') await expect(page.locator('.animal-grid')).toHaveClass(/grid-cols-3/) // 测试平板端 await page.setViewportSize({ width: 768, height: 1024 }) await page.reload() await expect(page.locator('.animal-grid')).toHaveClass(/grid-cols-2/) // 测试移动端 await page.setViewportSize({ width: 375, height: 667 }) await page.reload() await expect(page.locator('.animal-grid')).toHaveClass(/grid-cols-1/) await expect(page.locator('.mobile-nav')).toBeVisible() }) }) ``` ## 🚀 性能测试 ### 负载测试 #### JMeter测试计划 ```xml 结伴客系统性能测试计划 false false BASE_URL http://localhost:3001 continue false 10 100 60 false 1 page false 20 limit ${BASE_URL} /api/animals GET 2000 200 Assertion.response_code false 1 ``` #### K6性能测试脚本 ```javascript // performance/load-test.js import http from 'k6/http' import { check, sleep } from 'k6' import { Rate } from 'k6/metrics' // 自定义指标 const errorRate = new Rate('errors') // 测试配置 export const options = { stages: [ { duration: '2m', target: 10 }, // 预热阶段 { duration: '5m', target: 50 }, // 负载增加 { duration: '10m', target: 100 }, // 稳定负载 { duration: '5m', target: 200 }, // 峰值负载 { duration: '2m', target: 0 }, // 负载下降 ], thresholds: { http_req_duration: ['p(95)<2000'], // 95%的请求响应时间小于2秒 http_req_failed: ['rate<0.1'], // 错误率小于10% errors: ['rate<0.1'], // 自定义错误率小于10% }, } const BASE_URL = 'http://localhost:3001' export default function () { // 测试动物列表API const animalsResponse = http.get(`${BASE_URL}/api/animals?page=1&limit=20`) const animalsCheck = check(animalsResponse, { '动物列表状态码为200': (r) => r.status === 200, '动物列表响应时间<2s': (r) => r.timings.duration < 2000, '动物列表包含数据': (r) => JSON.parse(r.body).data.length > 0, }) errorRate.add(!animalsCheck) // 测试动物详情API if (animalsCheck) { const animals = JSON.parse(animalsResponse.body).data if (animals.length > 0) { const randomAnimal = animals[Math.floor(Math.random() * animals.length)] const detailResponse = http.get(`${BASE_URL}/api/animals/${randomAnimal.id}`) const detailCheck = check(detailResponse, { '动物详情状态码为200': (r) => r.status === 200, '动物详情响应时间<1s': (r) => r.timings.duration < 1000, '动物详情包含ID': (r) => JSON.parse(r.body).data.id === randomAnimal.id, }) errorRate.add(!detailCheck) } } // 测试搜索API const searchResponse = http.get(`${BASE_URL}/api/animals?search=狗&page=1&limit=10`) const searchCheck = check(searchResponse, { '搜索状态码为200': (r) => r.status === 200, '搜索响应时间<3s': (r) => r.timings.duration < 3000, }) errorRate.add(!searchCheck) sleep(1) // 模拟用户思考时间 } // 测试完成后的处理 export function handleSummary(data) { return { 'performance-report.html': htmlReport(data), 'performance-summary.json': JSON.stringify(data), } } function htmlReport(data) { return ` 性能测试报告

结伴客性能测试报告

测试概要

总请求数: ${data.metrics.http_reqs.count}
平均响应时间: ${data.metrics.http_req_duration.avg.toFixed(2)}ms
95%响应时间: ${data.metrics.http_req_duration['p(95)'].toFixed(2)}ms
错误率: ${(data.metrics.http_req_failed.rate * 100).toFixed(2)}%
` } ``` ## 🔧 测试工具配置 ### Jest配置 ```javascript // jest.config.js module.exports = { testEnvironment: 'node', roots: ['/src', '/tests'], testMatch: [ '**/__tests__/**/*.js', '**/?(*.)+(spec|test).js' ], collectCoverageFrom: [ 'src/**/*.js', '!src/**/*.test.js', '!src/config/**', '!src/migrations/**' ], coverageDirectory: 'coverage', coverageReporters: ['text', 'lcov', 'html'], setupFilesAfterEnv: ['/tests/setup.js'], testTimeout: 10000, verbose: true, collectCoverage: true, coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } } } ``` ### Vitest配置 ```javascript // vitest.config.js import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' export default defineConfig({ plugins: [vue()], test: { environment: 'jsdom', globals: true, setupFiles: ['./tests/setup.js'], coverage: { provider: 'c8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/', 'tests/', '**/*.d.ts', '**/*.config.js' ] } }, resolve: { alias: { '@': resolve(__dirname, 'src') } } }) ``` ### Playwright配置 ```javascript // playwright.config.js import { defineConfig, devices } from '@playwright/test' export default defineConfig({ testDir: './e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html'], ['json', { outputFile: 'test-results/results.json' }] ], use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure' }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } }, { name: 'Mobile Safari', use: { ...devices['iPhone 12'] } } ], webServer: { command: 'npm run dev', port: 3000, reuseExistingServer: !process.env.CI } }) ``` ## 📊 测试报告 ### 覆盖率报告 ```javascript // 生成覆盖率报告脚本 const { execSync } = require('child_process') const fs = require('fs') const path = require('path') function generateCoverageReport() { console.log('生成测试覆盖率报告...') // 运行测试并生成覆盖率 execSync('npm run test:coverage', { stdio: 'inherit' }) // 读取覆盖率数据 const coverageFile = path.join(__dirname, 'coverage/coverage-summary.json') const coverage = JSON.parse(fs.readFileSync(coverageFile, 'utf8')) // 生成HTML报告 const htmlReport = generateHtmlReport(coverage) fs.writeFileSync('coverage-report.html', htmlReport) console.log('覆盖率报告已生成: coverage-report.html') } function generateHtmlReport(coverage) { const total = coverage.total return ` 测试覆盖率报告

结伴客测试覆盖率报告

总体覆盖率

行覆盖率: ${total.lines.pct}%
函数覆盖率: ${total.functions.pct}%
分支覆盖率: ${total.branches.pct}%
语句覆盖率: ${total.statements.pct}%

详细信息

总行数: ${total.lines.total}

已覆盖行数: ${total.lines.covered}

未覆盖行数: ${total.lines.skipped}

建议

    ${total.lines.pct < 80 ? '
  • 行覆盖率低于80%,需要增加测试用例
  • ' : ''} ${total.functions.pct < 80 ? '
  • 函数覆盖率低于80%,需要测试更多函数
  • ' : ''} ${total.branches.pct < 80 ? '
  • 分支覆盖率低于80%,需要测试更多分支条件
  • ' : ''}
` } function getColorClass(percentage) { if (percentage >= 80) return 'high' if (percentage >= 60) return 'medium' return 'low' } generateCoverageReport() ``` ## 🔄 CI/CD集成 ### GitHub Actions测试工作流 ```yaml # .github/workflows/test.yml name: 测试工作流 on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: unit-tests: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: jiebanke_test ports: - 3306:3306 options: >- --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 redis: image: redis:6 ports: - 6379:6379 options: >- --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v3 - name: 设置Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: 安装依赖 run: | cd backend npm ci - name: 运行数据库迁移 run: | cd backend npm run migrate env: DB_HOST: localhost DB_PORT: 3306 DB_NAME: jiebanke_test DB_USER: root DB_PASS: root - name: 运行单元测试 run: | cd backend npm run test:coverage env: NODE_ENV: test DB_HOST: localhost DB_PORT: 3306 DB_NAME: jiebanke_test DB_USER: root DB_PASS: root REDIS_HOST: localhost REDIS_PORT: 6379 - name: 上传覆盖率报告 uses: codecov/codecov-action@v3 with: file: ./backend/coverage/lcov.info flags: backend name: backend-coverage frontend-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: 设置Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: 安装依赖 run: | cd frontend npm ci - name: 运行前端测试 run: | cd frontend npm run test:coverage - name: 上传覆盖率报告 uses: codecov/codecov-action@v3 with: file: ./frontend/coverage/lcov.info flags: frontend name: frontend-coverage e2e-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: 设置Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: 安装依赖 run: npm ci - name: 安装Playwright run: npx playwright install --with-deps - name: 启动应用 run: | npm run build npm run start & sleep 30 - name: 运行E2E测试 run: npx playwright test - name: 上传测试报告 uses: actions/upload-artifact@v3 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30 performance-tests: runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - name: 设置Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: 安装K6 run: | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list sudo apt-get update sudo apt-get install k6 - name: 启动应用 run: | npm run build npm run start & sleep 30 - name: 运行性能测试 run: k6 run performance/load-test.js - name: 上传性能报告 uses: actions/upload-artifact@v3 with: name: performance-report path: performance-report.html ``` ## 📚 总结 本测试文档全面覆盖了结伴客项目的测试策略和实施方案,包括: ### 测试体系特点 1. **全面覆盖**: 从单元测试到E2E测试的完整测试金字塔 2. **自动化程度高**: CI/CD集成,自动运行测试和生成报告 3. **质量保证**: 代码覆盖率要求和性能指标监控 4. **多维度测试**: 功能、性能、安全、兼容性全方位测试 ### 关键测试工具 - **Jest/Vitest**: 单元测试和集成测试 - **Playwright**: 端到端测试 - **K6/JMeter**: 性能测试 - **GitHub Actions**: CI/CD自动化 ### 质量指标 - 代码覆盖率 ≥ 80% - API响应时间 < 2秒 - 系统可用性 99.9% - 错误率 < 0.1% ### 持续改进 1. 定期审查和更新测试用例 2. 监控测试执行时间和稳定性 3. 根据业务变化调整测试策略 4. 培训团队成员测试最佳实践 通过完善的测试体系,确保结伴客项目的高质量交付和稳定运行。 --- **文档版本**: v1.0.0 **最后更新**: 2024年1月15日 **维护人员**: 测试团队