什么是前端测试
1.数据模拟与结果预测
2.实践模拟与结果或行为预测
测试的主要内容
- 测试通用业务组件?业务变更快速,单元测试波动较大。
X
- 测试用户行为?用户行为存在上下文关系,组合起来是一个很恐怖的数字,这个交给测试人员去测就好了。
X
- 那到底该测什么呢?要测试主要是 功能型组件,vue插件,二次封装的库等等
例如断言组件的公共接口,测试用例将会断言一些输入 (用户交互或 prop 改变) 提供给某组件之后是否导致预期结果 (渲染结果或触发自定义事件)
组件测试不应该测什么
单纯测试组件模板中的 HTML
比如,测试组件模板中有几个 div、input、button,以及元素的 class、id 属性等与业务逻辑无关的纯 UI 测试。这里并非说我们不需要关注组件的 UI,而是出于以下几个考量:
- 使用单元测试测试组件的 UI 会导致测试非常繁琐,为了测试覆盖的全面会导致针对一个组件写出大量的测试,不但降低了开发效率还体验非常差
- 这种单纯的 UI 测试是非常脆弱的,这类测试和组件模板是强耦合的,一旦我们对 HTML 结构进行调整,测试就会挂掉,这就造成了测试非常难以维护。而实际上我们往往并不关心组件模板中的具体 HTML 结构是怎样的,我们只关心组件呈现出来的样子
- 像 Jest 等前端测试框架已经提供了快照测试来帮助我们对比修改引起的 UI 变化,并且我们也可以使用Storybook这类工具实现可视化的 UI 测试
单元测试的作用
降低bug发生几率,快速定位bug,减少重复的手工测试。
提高代码质量,为项目带来更高的代码可维护性。
方便项目的交接工作,测试脚本就是最好的需求描述。
前端测试的种类
- 单元测试
- 集成测试
- 端到端测试
测试工具
karma mocha chai sinon Jasmine Jest ava
添加测试设置
已经创建的项目添加test部分
添加@vue/cli 脚手架测试插件
vue add @vue/cli-plugin-unit-jest
已经使用了@vue/cli 脚手架的时候如何引入
vue invoke @vue/test-util
测试用例
vue2.x
jest.config.js
collectCoverageFrom
覆盖率涵盖的需要统计的文件,以及排除的文件
collectCoverageFrom:[
"**/src/**/**.{js,vue}",
"!**/src/**.{js,vue}",
"!**/src/config/**.{js,vue}",
"!**/src/views/**/**.{js}",
"!**/node_modules/**",
],
coverageDirectory
测试率覆盖报告输出目录
coverageDirectory: '<rootDir>/tests/unit/coverage',
覆盖率报告说明
%Stmts(statement coverage): 语句覆盖率,是否每个语句都执行了
%Branch(branch coverage): 分支覆盖率,是否每个if代码块都执行了
%Funcs(branch coverage): 函数覆盖率,是否每个函数都调用了
%Lines(line coverage): 行覆盖率,是否每一行都执行了
组件测试
测试的组件中包含其它组件
问题:无法识别被测组件中的子组件
解决方案:在测试当前组件的时候需要注册当前组件引用的第三方组件或个人定义的当文件组件
import {createLocalVue, mount, shallowMount} from "@vue/test-utils";
const localVue = createLocalVue();
localVue.component("a-button",Button);
使用createLocalVue 是为了测试组件不污染全局设置
根据选择器获取元素个数
import {createLocalVue, mount, shallowMount} from "@vue/test-utils";
const localVue = createLocalVue();
localVue.use(Form);
expect(container.findAll('.ant-form-item').length).toEqual(3);
可测试的组件
vue add unit-jest
测试中存在属性变更使用
Vue.nextTick(()=>{
})
涉及路由的单元测试
为避免调用 Vue.use(…) 污染测试的全局命名空间,我们将会在测试中创建基础的路由;这让我们能在单元测试期间更细粒度的控制应用的状态。
因此使用createLocalVue创建本地vue
const localVue = createLocalVue();
localVue.use(VueRouter);
这里可以使用use,但是使用use注册组件却有问题
localVue.use(Result).use(Button);
发生报错信息
[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the “name” option.
发现use的使用只适用部分内容;
对于引用第三方的组件有的是不适用的,通过实践发现以下方式可行:
localVue.component("a-result",Result);
localVue.component("a-button",Button);
另一个要注意的是这里用了 mount 而非 shallowMount。如果用了 shallowMount,则 就会被忽略,不管当前路由是什么,渲染的其实都是一个无用的替身组件。
mount和shallowMount的区别
mount仅仅挂载当前组件实例;而shallowMount挂载当前组件实例以外,还会挂载子组件。
包含路由组件
No-fond 组件
传递属性则组件显示传递值
如果没有传递则显示默认值
<template>
<a-result
:status="status"
:title="title"
:subTitle="subTitle"
>
<template v-slot:extra>
<router-link to="/index">
<a-button type="primary" link>Back Home</a-button>
</router-link>
</template>
</a-result>
</template>
<script>
export default {
name: "no-find",
props: ["otherStatus", "otherTitle", "otherSubTitle"],
data(){
return {
status: (this.otherStatus == undefined ? "404" : this.otherStatus),
title: this.otherTitle == undefined ? '404' : this.otherTitle,
subTitle: this.otherSubTitle == undefined ? "Sorry, the page you visited does not exist." : this.otherSubTitle
}
}
};
</script>
1.使用shallowMount可以识别router-link,mount无法识别router-link
2.shallowMount能够渲染最外层组件,无法获取解析后的子组件,mount可以获取解析后的子组件
3.如果需要使用mount,但是也包含router-link:
- 创建路由
- 将路由作为参数
const router = new VueRouter({ myRouter });
const wrapper = mount(NoFound,{
localVue,
router
});
expect(wrapper.findComponent(Result).html()).toContain("Sorry, the page you visited does not exist");
如何测试触发router-link路由跳转,这块还不太清楚
Error in mounted hook
computed: {
//这里需要把store 动态的数据放到computed里面才会同步更新 视图
getChannels() {
return this.$store.state.channelList
}
}
return this.$store.getters.getChannelList;
vue组件中methods方法的测试
测试组件的内部方法
[vue-test-utils]: overwriting methods via the methods
property is deprecated and will be removed in the next major version. There is no clear migration path
for the methods
property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex method extract it from the component and te
st it in isolation. Otherwise, the suggestion is to rethink those tests
如果一些方法只是在组件内部调用其他方法而没有任何暴露给外部的行为(比如更改了组件的 UI、请求外部 API 等),那这些方法是不需要测试的。我们希望一个组件就像一个黑盒一样,我们不关心其内部的处理逻辑而只关注其外部呈现。
表单变更等触发的方法
input
通过设置input的值
调用触发方法
textInput.element.value = value
textInput.trigger(‘input’)
select
select.element.value = value
select.trigger(‘change’)
生命周期中调用methods的测试
shallow(TestComp, { methods: {testMethod: () => {}} }))
该种方法目前已废弃
在created()中调用异步函数
在mounted()调用异步函数
{
localVue,
mounted(){//该方法执行可是调用的异步仍旧执行了}
}
let spy = jest.spyOn(Index.methods, 'getCategoryData');
container = shallowMount(Index,options);
没有stub getCategoryData
测试中异步交互的模拟
jest.mock('axios', () => ({
get: jest.fn(() => Promise.resolve({
"resCode": "200",
"resMessage": "get_info success",
"data": ["C", "R"]
}))
}));
import axios from 'axios';
测试通过,这个主要是用于模拟触发的异步方法
这里的’axios’不是install安装的axios,而是mock的axios,所以import axios一定要在jest.mock(‘axios’)语句后
mock data
这个如果直接使用data就不需要模拟
store 使用的测试
这个可以直接参考官网,比较清晰明确
测试博客
之前看到的比较好的测试相关内容