跳到主要内容

测试策略文档

概述

本文档详细介绍了邮轮穿舱件管理系统前端项目的测试策略,包括单元测试、组件测试和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

索引

本测试策略为邮轮穿舱件管理系统前端项目提供了全面的测试指导。通过实施分层测试策略,结合适当的工具和最佳实践,可以确保系统的质量和稳定性。

关键要点:

  1. 单元测试:重点测试工具函数和状态管理逻辑
  2. 组件测试:验证Vue组件的行为和交互
  3. E2E测试:确保完整的用户流程正常工作

下一步行动:

  • 配置测试环境和工作流
  • 实现核心功能的测试用例
  • 建立持续集成流程
  • 定期审查和优化测试策略

参考文件汇总: