测试策略文档
概述
本文档详细介绍了邮轮穿舱件管理系统前端项目的测试策略,包括单元测试、组件测试和E2E测试的实现方法和最佳实践。基于对项目代码结构的深入分析,本策略旨在确保系统质量、稳定性和可维护性。
项目架构分析
技术栈依赖关系
flowchart TD
A[Vue.js 2.6.14] --> B[Vue Router 3.6.5]
A --> C[Vuex 3.6.2]
A --> D[Vuetify 2.6.0]
A --> E[Axios 1.7.5]
B --> F[路由守卫]
C --> G[状态管理]
D --> H[UI组件]
E --> I[HTTP请求]
F --> J[认证检查]
G --> K[用户状态]
H --> L[响应式布局]
I --> M[API集成]
技术栈说明:
- Vue.js 2.x:核心框架,采用Options API
- Vue Router:单页面应用路由管理
- Vuex:集中式状态管理
- Vuetify:Material Design UI框架
- Axios:HTTP客户端,用于API调用
参考文件:
项目目录结构
flowchart TD
A[src/] --> B[components/]
A --> C[views/]
A --> D[router/]
A --> E[store/]
A --> F[utils/]
A --> G[http/]
B --> B1[NavigatorPage.vue]
B --> B2[FooterPage.vue]
B --> B3[TitlePage.vue]
C --> C1[HomeView.vue]
C --> C2[LoginView.vue]
C --> C3[admin/]
C --> C4[workpiece/]
C --> C5[image/]
C3 --> C31[UserAdmin.vue]
C3 --> C32[ImageAdmin.vue]
D --> D1[路由配置]
E --> E1[状态管理]
F --> F1[工具函数]
G --> G1[HTTP配置]
参考文件:
单元测试策略
工具函数测试
认证工具函数测试
// tests/unit/utils/auth.spec.js
import { checkAuthorizaion, logout, getUserInfoFromToken } from '@/utils'
describe('认证工具函数', () => {
beforeEach(() => {
// 清除所有存储
localStorage.clear()
sessionStorage.clear()
document.cookie = ''
})
describe('checkAuthorizaion', () => {
it('应该返回true当token有效时', () => {
// 设置有效token
const validToken = 'valid.jwt.token'
document.cookie = `token=${validToken}`
// Mock jwtDecode返回有效数据
jest.spyOn(jwtDecode, 'default').mockReturnValue({
exp: Date.now() / 1000 + 3600 // 1小时后过期
})
expect(checkAuthorizaion()).toBe(true)
})
it('应该返回false当token过期时', () => {
const expiredToken = 'expired.jwt.token'
document.cookie = `token=${expiredToken}`
jest.spyOn(jwtDecode, 'default').mockReturnValue({
exp: Date.now() / 1000 - 3600 // 1小时前过期
})
expect(checkAuthorizaion()).toBe(false)
})
})
describe('logout', () => {
it('应该清除所有认证信息', () => {
// 设置认证状态
document.cookie = 'token=test; username=admin'
sessionStorage.setItem('token', 'test')
logout()
expect(document.cookie).toBe('')
expect(sessionStorage.getItem('token')).toBeNull()
})
})
})
参考文件:
状态管理测试
// tests/unit/store/store.spec.js
import Vuex from 'vuex'
import { createLocalVue } from '@vue/test-utils'
import storeConfig from '@/store'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('Vuex Store', () => {
let store
beforeEach(() => {
store = new Vuex.Store(storeConfig)
})
describe('状态管理', () => {
it('应该正确初始化状态', () => {
expect(store.state.navigator_drawer).toBe(false)
expect(store.state.authenticated).toBe(false)
expect(store.state.user_info).toBeNull()
expect(store.state.user_token).toBeNull()
})
it('应该正确设置用户信息', () => {
const userInfo = { username: 'admin', role: 'administrator' }
store.commit('set_user_info', userInfo)
expect(store.state.user_info).toEqual(userInfo)
})
it('应该正确切换导航抽屉状态', () => {
store.commit('toggle_navigator_drawer')
expect(store.state.navigator_drawer).toBe(true)
store.commit('toggle_navigator_drawer')
expect(store.state.navigator_drawer).toBe(false)
})
})
describe('Actions', () => {
it('应该通过actions提交mutations', async () => {
await store.dispatch('set_authenticated', true)
expect(store.state.authenticated).toBe(true)
})
})
describe('Getters', () => {
it('应该正确返回认证状态', () => {
store.commit('set_authenticated', true)
expect(store.getters.get_authenticated).toBe(true)
})
})
})
参考文件:
组件测试策略
导航组件测试
// tests/unit/components/NavigatorPage.spec.js
import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import NavigatorPage from '@/components/NavigatorPage.vue'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('NavigatorPage.vue', () => {
let store
let wrapper
const mockStore = {
state: {
navigator_drawer: false
},
dispatch: jest.fn()
}
const mockRouter = {
replace: jest.fn()
}
beforeEach(() => {
store = new Vuex.Store(mockStore)
wrapper = shallowMount(NavigatorPage, {
localVue,
store,
mocks: {
$router: mockRouter
}
})
})
it('应该正确渲染导航项', () => {
const navItems = wrapper.vm.items
expect(navItems).toHaveLength(12)
expect(navItems[0]).toEqual({
title: '首页',
link: '/home',
icon: 'mdi-home',
color: 'primary'
})
})
it('应该正确处理导航点击', async () => {
const testLink = '/home'
await wrapper.vm.linkto(testLink)
expect(mockRouter.replace).toHaveBeenCalledWith({ path: testLink })
expect(mockStore.dispatch).toHaveBeenCalledWith('set_navigator_drawer', false)
})
it('应该正确计算drawerSwitch属性', () => {
// 测试getter
expect(wrapper.vm.drawerSwitch).toBe(false)
// 测试setter
wrapper.vm.drawerSwitch = true
expect(mockStore.dispatch).toHaveBeenCalledWith('set_navigator_drawer', true)
})
})
参考文件:
首页组件测试
// tests/unit/views/HomeView.spec.js
import { mount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import HomeView from '@/views/HomeView.vue'
const localVue = createLocalVue()
localVue.use(Vuex)
describe('HomeView.vue', () => {
let store
let wrapper
let httpMock
beforeEach(() => {
httpMock = {
get: jest.fn(() => Promise.resolve({
data: {
user_count: 10,
ticket_count: 25,
image_count: 150,
log_count: 500
}
}))
}
store = new Vuex.Store({
state: {
authenticated: true,
user_info: { username: 'admin' }
}
})
wrapper = mount(HomeView, {
localVue,
store,
mocks: {
$http: httpMock,
$router: {
push: jest.fn()
}
}
})
})
it('应该正确加载统计数据', async () => {
await wrapper.vm.$nextTick()
expect(httpMock.get).toHaveBeenCalledWith('/stat/countInfo')
expect(wrapper.vm.user_count).toBe(10)
expect(wrapper.vm.ticket_count).toBe(25)
})
it('应该显示登录状态提示', async () => {
// 测试未登录状态
store.state.authenticated = false
await wrapper.vm.$nextTick()
const alert = wrapper.find('v-alert')
expect(alert.exists()).toBe(true)
expect(alert.text()).toContain('未登录状态')
})
it('应该正确处理导航点击', () => {
const testRoute = '/user'
wrapper.vm.navigateTo(testRoute)
expect(wrapper.vm.$router.push).toHaveBeenCalledWith(testRoute)
})
})
参考文件:
E2E测试策略
测试框架配置
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:8080',
viewportWidth: 1920,
viewportHeight: 1080,
setupNodeEvents(on, config) {
// 配置测试环境
},
},
env: {
apiUrl: 'http://localhost:3000/api'
}
})
用户流程测试
// cypress/e2e/user-journey.cy.js
describe('用户完整流程测试', () => {
beforeEach(() => {
// 清除所有数据
cy.clearLocalStorage()
cy.clearCookies()
// 访问首页
cy.visit('/')
})
it('应该完成登录到管理的完整流程', () => {
// 1. 检查未登录状态
cy.contains('未登录状态').should('be.visible')
cy.get('v-alert[type="warning"]').should('exist')
// 2. 导航到登录页面
cy.get('v-navigation-drawer').within(() => {
cy.contains('登录').click()
})
// 3. 登录操作
cy.url().should('include', '/login')
cy.get('input[name="username"]').type('testuser')
cy.get('input[name="password"]').type('password123')
cy.get('button[type="submit"]').click()
// 4. 验证登录成功
cy.url().should('include', '/home')
cy.contains('已登录').should('be.visible')
// 5. 导航到用户管理
cy.get('v-navigation-drawer').within(() => {
cy.contains('用户管理').click()
})
// 6. 验证用户管理页面
cy.url().should('include', '/user')
cy.contains('用户管理').should('be.visible')
// 7. 登出操作
cy.get('v-navigation-drawer').within(() => {
cy.contains('登出').click()
})
// 8. 验证登出成功
cy.url().should('include', '/home')
cy.contains('未登录状态').should('be.visible')
})
})
路由守卫测试
// cypress/e2e/router-guard.cy.js
describe('路由守卫测试', () => {
it('应该阻止未认证用户访问受保护路由', () => {
// 直接访问受保护路由
cy.visit('/user')
// 应该被重定向到登录页面
cy.url().should('include', '/login')
cy.url().should('include', 'redirect=%2Fuser')
})
it('应该允许认证用户访问受保护路由', () => {
// 模拟登录状态
cy.setCookie('token', 'valid-jwt-token')
cy.window().then((win) => {
win.sessionStorage.setItem('token', 'valid-jwt-token')
})
// 访问受保护路由
cy.visit('/user')
// 应该成功访问
cy.url().should('include', '/user')
cy.contains('用户管理').should('be.visible')
})
})
参考文件:
测试最佳实践
测试金字塔策略
flowchart TD
A[测试金字塔] --> B[70% 单元测试]
A --> C[20% 组件测试]
A --> D[10% E2E测试]
B --> B1[工具函数]
B --> B2[状态管理]
B --> B3[工具类]
C --> C1[Vue组件]
C --> C2[路由组件]
C --> C3[页面组件]
D --> D1[用户流程]
D --> D2[集成测试]
D --> D3[系统测试]
测试数据管理
// tests/unit/__fixtures__/userData.js
export const mockUserData = {
authenticated: true,
user_info: {
username: 'admin',
role: 'administrator',
permissions: ['user:read', 'user:write']
},
user_token: 'mock-jwt-token'
}
export const mockApiResponse = {
user_count: 15,
ticket_count: 30,
image_count: 200,
log_count: 1000
}
// 工厂函数创建测试数据
export const createTestUser = (overrides = {}) => ({
...mockUserData,
...overrides
})
测试覆盖率目标
| 测试类型 | 覆盖率目标 | 关键指标 |
|---|---|---|
| 单元测试 | ≥ 80% | 工具函数、状态管理 |
| 组件测试 | ≥ 70% | 组件逻辑、事件处理 |
| E2E测试 | ≥ 60% | 关键用户流程 |
持续集成集成
GitHub Actions配置
# .github/workflows/test.yml
name: Test Suite
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Run component tests
run: npm run test:component
- name: Run E2E tests
run: npm run test:e2e
- name: Upload coverage
uses: codecov/codecov-action@v3
索引
本测试策略为邮轮穿舱件管理系统前端项目提供了全面的测试指导。通过实施分层测试策略,结合适当的工具和最佳实践,可以确保系统的质量和稳定性。
关键要点:
- 单元测试:重点测试工具函数和状态管理逻辑
- 组件测试:验证Vue组件的行为和交互
- E2E测试:确保完整的用户流程正常工作
下一步行动:
- 配置测试环境和工作流
- 实现核心功能的测试用例
- 建立持续集成流程
- 定期审查和优化测试策略
参考文件汇总: