前端工程越来越复杂,光靠工程师的常规工作来维护项目变得越发困难。在前端开发中引入自动化测试技术,让项目质量可以通过自动化工具来保障,将解决这个难题。从实际应用情况来说,大小公司也都越来越重视测试,大公司工程大,必然要测试;小公司分工没有那么细,要求”一角多能“,前端工程师更要承担测试工作。另外,掌握测试不只是一种技能,它更能提升你的架构思维、编码能力和把控项目整体稳定性的能力。
又称模块测试,是针对软件设计的最小单位——程序模块进行正确性检验的测试工作。其目的在于检查每个程序单元能否正确实现详细设计说明中的模块功能、性能、接口和设计约束等要求,发现各模块内部可能存在的各种错误。单元测试需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试
也叫做组装测试。通常在单元测试的基础上,将所有的程序模块进行有序的、递增的测试。集成测试是检验程序单元或部件的接口关系,逐步集成为符合概要设计要求的程序部件或整个系统
Jest 是功能最全的测试运行器。它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。不过你需要一个能够将单文件组件导入到测试中的预处理器。我们已经创建了 vue-jest 预处理器来处理最常见的单文件组件特性,但仍不是 vue-loader 100% 的功能。jest 以 JS 开发,前端工程师可以零语言成本入门,是当前大多数工程师及公司的主流选择 
mocha-webpack 是一个 webpack + Mocha 的包裹器,同时包含了更顺畅的接口和侦听模式。这些设置可以帮助我们通过 webpack + vue-loader 得到完整的单文件组件支持,但这本身是需要很多配置的。


②优势

import { shallowMount, mount, createLocalVue } from '@vue/test-utils'
import store from '@/store/'
import ViewUI from 'view-design'
import * as filters from '@/libs/filter'
import config from '@/config'
import VueRouter from 'vue-router'
import PlacePage from '@/views/BaseInfo/PlacePage/index.vue'
import House from '@/views/BaseInfo/House/index.vue'
// import * as filters from "@/libs/filter";
// 相关api请求
const routes = [{ path: '/house', component: House }]
// 模拟router
const router = new VueRouter({
routes
})
const localVue = createLocalVue()
Object.keys(filters).forEach((key) => localVue.filter(key, filters[key]))
localVue.use(VueRouter)
localVue.config.productionTip = false
localVue.use(ViewUI, {
transfer: true,
size: 'default',
// capture: false,
select: {
arrow: 'md-arrow-dropdown',
arrowSize: 20
}
})
localVue.prototype.$config = config
describe('House.vue', () => {
router.push('/house')
const wrapper = mount(PlacePage, {
localVue,
router,
store
})
it('House.vue渲染正常', () => {
expect(wrapper.exists()).toBe(true)
})
})
vue-test-utils 是 vue 官方的单元测试框架,提供了一系列非常方便的工具,使我们更轻松地为 vue 构建的应用来编写单元测试。更详细内容请查看:Vue Test Utils 是 Vue.js 官方的单元测试实用工具库
jest是功能最全的测试运行器。它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。不过你需要一个能够将单文件组件导入到测试中的预处理器。vue 已经创建了 vue-jest 预处理器来处理最常见的单文件组件特性,但仍不是 vue-loader 100% 的功能。
BaseInfo.House.getFloor = jest.fn()
BaseInfo.House.getFloor.mockResolvedValue(returnData(publicData.floorList))
解决断言默认 5000ms 超时问题,可在 test/it 中单独设置超时参数。也可在 jest.config.js 中已设置 60s 为默认超时时间,提出测试用例中公共部分-配置环境部分。配置于 setupFile 中。(setupFile:运行些代码以配置或设置测试环境的模块的路径列表。每个 setupFile 将对每个测试文件运行一次。由于每个测试都在其自己的环境中运行,因此这些脚本将在执行测试代码本身之前立即在测试环境中执行。另setupFiles 在环境中安装测试框架之前执行,在环境中安装测试框架后立即运行一些代码为setupFilesAfterEnv。) 更多请参考jest 配置项
module.exports = {
preset: '@vue/cli-plugin-unit-jest', // Jest配置基础的预设
globals: {
// 全局变量
GosunGIS: GosunGIS,
Cesium: Cesium
},
setupFiles: ['jest-canvas-mock', './tests/config/public.js'],
errorOnDeprecated: true,
testTimeout: 60000 // 超时默认为5000ms,此处设置为60s
}
// 默认配合
var defaults = {
adapter: getDefaultAdapter()
....
}
// 分发函数
function getDefaultAdapter () {
var adapter;
if (typeof process !== 'undefined' && Object.prototype.toString.call(process)
=== '[object process]') {
// node环境下使用 node.http
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// web 环境下使用 xhr
adapter = require('./adapters/xhr'); // 基于XMLHttpRequest
}
return adapter;
// 使用 adapter 做mock mock数据路由,根据url 返回mock数据
const mockRouter = { ...}
const http = Axios.create({
adapter: config => {
// 判断是否存在mock数据
let has = mockRouter.has(config.url)
// 调用默认请求接口, 发送正常请求及返回
if (!data) {
// 删除配置中的 adapter, 使用默认值
delete config.adapter
// 通过配置发起请求
return Axios(config)
}
// 模拟服务,返回mock数据
return new Promise((res, rej) => {
const resInfo = {
data: mockRouter.getData(config)
status: 200,
statusText: 'OK'
}
// 调用响应函数
res(resInfo)
})
}
})
}
axios.defaults.baseURL = 'https://181.181.0.218'
$ npm install --save isomorphic-fetch
import fetch from 'isomorphic-fetch'
const baseUrl = 'http://188.188.33.24:82'
const loginData = {
loginName: 'zidy001',
passWord: 'a123456'
}
// 使用fetch获取数据
const jestFetch = async function ({ url, data, params, headers, method }) {
// 请求接口前先确认token
if (!window.token) {
const { data: d } = await getToken()
window.token = d.token
}
return new Promise((resolve, reject) => {
params = params ? '?' + params : ''
const o = {
method: method
}
if (data) {
o.body = JSON.stringify(data)
}
if (headers) {
o.headers = headers
} else {
// 获取token
console.log('token', window.token)
o.headers = {
'access-token': window.token
}
}
console.log(params, o)
fetch(baseUrl + url + params, o)
.then((response) => {
return response.json()
})
.then((response) => {
resolve(response)
})
.catch((e) => reject(e))
})
}
const getToken = function () {
return new Promise((resolve, reject) => {
fetch(baseUrl + '/smart/smart-auth/user/login', {
body: JSON.stringify(loginData),
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
.then((response) => {
return response.json()
})
.then((response) => {
resolve(response)
})
.catch((e) => reject(e))
})
}
BaseInfo.House.getPlaceList = ({ placeType = '01' }) => {
return jestFetch({
url: '/smart/smart-base/house/define',
params: 'placeType=' + placeType,
method: 'GET'
})
} it('根据场所类型获取场所信息', async done => {
const ref = await BaseInfo.House.getPlaceList({ placeType: '' })
console.log(ref)
// { timestamp: 1611805779944,status: 0,message: '',data: [{ code:
'360102008001020001', name: '总部基地'
}, ...], total: 9, row: 9}
expect(ref).toBeTruthy()
done()
})
"scripts": {
"test:unit": "vue-cli-service test:unit",
"coverage": "jest --coverage",
// "test": "jest"
}
npx jest --coverage
