热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

第十二集:从零开始实现一套pc端vue的ui组件库(jest单元测试)

第十二集:从零开始实现(jest单元测试)1.聊聊测试    本次我会与大家分享一下我学测试时候记的笔记知识以及本次项目里面做的几个测试.    前端代码的单元测试与集成测试属于雷

第十二集: 从零开始实现( jest单元测试 )

1.聊聊测试

    本次我会与大家分享一下我学测试时候记的笔记知识以及本次项目里面做的几个测试.
    前端代码的单元测试与集成测试属于雷声大雨点小, 很多人一提到它都说是个好东西, 试问又有几个公司的vue项目是严格要求跑单元测试与集成测试的那?? 测试没通过是否暂停上线? 除了大公司没有几家做得到吧, 毕竟大多数公司只是让专业的测试团队进行’人肉测试’.
    现在前端体系搞得好庞大, 围绕着前端开发的技术与知识点层出不穷, 更别说各种技术之间那剪不断理还乱的纠葛, 我听有人说过: “我只想好好写前端代码, 其他的不管行不行”, 这句话是个病句, 这些杂七杂八的技术也都是前端技术, 如果你只会写你所谓的’前端代码’, 那你真的只能是一辈子’初学者’了┑( ̄Д  ̄)┍.
    对于这个人人都说好, 但是人人不咋用是咋回事那??🙅‍♂️接下来我们就他的优缺点进行罗列.

2.优缺点

缺点

  1. 前端的测试技术体系还未成形, 本套ui用的就是vue-cli集成的jest 真心不好用….
  2. 有一定的学习成本, 我面试过很多6年以上经验的, 连’设计模式’都搞不懂, 更别说让他学测试了…
  3. 可有可无的处境, 很多工程没有测试跑的好好的, 写了反而bug多多
  4. 不想进步的人的阻拦, 真别小看这条, 很多技术人员会制造各种理由, 不想跳出舒适区.
  5. 每次改需求或是优化代码, 则都需要改两份代码, 人力消耗大.

优点

  1. 多一种思考维度, 多一门技术护身, 对于要以技术养家的人来说, 这条也很重要.
  2. 为主体逻辑的畅通保驾护航, 整套测试能跑下来就不会有太大的错误
  3. b格高, 让别人看了能放心用你的东西, 这也是硬实力

3.用法与分类

大体上分为两类:

  1. BDD 把所有逻辑都写好, 然后根据你的整体逻辑制定你的测试, 好处当然是好理解,更有整体思维, 缺点就是覆盖率低, 并不是很保险.
  2. TDD 把测试写好再进行开发, 这个模式挺有意思, 先写测试, 也就是在脑中先整体布局, 每一步都是自己思考好了再去做的测试覆盖率可能是100%, 他的缺点就太明显了, 开发人员技术必须硬, 而且如果改需求…有的忙了.

基本搭建
我是在vue项目里面直接选择的jest测试
单独实验的朋友可以自行安装 npm i jest -D
去配置一下

"scripts": {
"test": "jest --watchAll"
},

命令行里运行npm run test即可
如果电脑运行说没有这个命令的话, 可以用npx 或者在全局安装一下
如果是es项目的话, 要集成一下 npm i @babel/core @babel/preset-env -D
jest内置了 对babel的依赖, 他看到.babelrc就会去配合解析的

基本使用
jest 会自动查找 xx.test.js的文件, 配置如下
随便修改成你喜欢的语义化就好, element-ui采用的是spec
这面这个文件可以通过, jest init生成
jest.config.js

testMatch: [
'**/tests/unit/**/*.(spec|test).(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],

// 1: 最外层describe相当于一个大的父容器盒子, 把测试进行分'块'
// 在出错的时候, 控制台会报出是哪一'块'出错了
describe('按钮相关代码', () => {
// 2: '小块'测试单元, 具体的某些职责的测试,
test('测试 按钮点击小伙', () => {
// 3: 断言, 也就是真正判断某些值是否正确的一步
expect(1).toBe(1);
});
});

以偶上述为例

// 意思就是判断, 1 是否 === 1
expect(1).toBe(1);
// 由此可知, expect函数负责接收要测试的值
// toBe则为 所谓的 ===, 与他里面的值进行比较
// 那既然有 === 肯定就会有更多种类型的判断了
// 他学名叫配置器

多种类型的’配置器’

  1. toEqual 并不是 == 严格说他是忽略引用,只比内容,内部估计是做了序列化 所以 {a:1}.toEqual({a:1}) true
  2. toBeFalsy 可否转化为 false
  3. toBeTruthy 可否转化为true
  4. toBeUndefined 是 undefined
  5. toBeDefined 不是 undefined
  6. toBeNull === null
  7. not 翻转修饰符, expect(1).not.toBe(2); 1不是2
  8. toBeGreaterThanOrEqual(3) 大于等于3
  9. toBeLessThanOrEqual(3) 小于等于3
  10. toBeLessThan(3) 小于3
  11. toBeGreaterThan(3) 大于3
  12. ‘abc’.toMatch(‘b’) // 是否包含’b’字符串, 可以写正则

生命周期

beforeEach(() => {
// 每个test执行之前都会执行我
});
afterEach(() => {
// 每个test执行之后都会执行我
});
beforeAll(() => {
// 所有test执行之前执行我
});
afterAll(() => {
// 所有test都执行完执行我
});
describe('按钮相关代码', () => {
test('测试 按钮点击小伙', () => {
expect(1).toBe(1);
});
});

这个时代所有插件的配置都趋于’函数化’
上面的生命周期函数很符合设计模式, 我们在写项目的时候也可以借鉴一下.

看完上面这些是不是感觉测试页很容易, 坑的在后面结合vue项目时.

4.vue里面

vue里面当然天差地别, 渲染方式都不一样了, 这个还好有vue自己团队提供的支持

介绍几个vue里面的概念

  1. mount: 可以理解我vue里面的实例化组件的方法, 官网这么说:’创建一个包含被挂载和渲染的 Vue 组件的 Wrapper’, 也就是一个完整的渲染, 他的优点就是完整, 但是缺点也明显就是效率低
  2. shallowMount 和 mount 一样,创建一个包含被挂载和渲染的 Vue 组件的 Wrapper,不同的是被存根的子组件。也就是仅仅挂载当前组件实例;
  3. Wrapper 是一个包括了一个挂载组件或 vnode,以及测试该组件或 vnode 的方法。直观点讲就是专门用于测试的实例

由于篇幅有限, 我就直接拿我工程里面的举例子了;
其实到底要测些什么这方面, 我理解的也不是很透, 所以只是简单的几个例子, 一起学习一起讨论.
按钮组件
按钮的测试
vue-cc-ui/tests/unit/Button.test.js

// shallowMount是@vue/test-utils官方提供的测试工具
import { shallowMount } from '@vue/test-utils';
import Button from '../../src/components/Button';
// 这是参考网上封装的获取dom的方法, 下面会有说明👇
import { findTestWrapper } from '../utils/util';
describe('测试button组件', () => {
it('1: 可以渲染出button组件', () => {
// 利用shallowMount实例化我的button组件
const wrapper = shallowMount(Button);
// 关键词contains, 判断 Wrapper 是否包含了一个匹配选择器的元素或组件。
// 也就是我想判断, 这个button组件渲染完毕, 页面上是否真的有一个button元素
expect(wrapper.contains('button')).toBe(true);
});
it('2: button组件点击时会触发click事件', () => {
// 依旧是先渲染
const wrapper = shallowMount(Button);
// 找到button实例, 这里的at(0), 类似数组的[0];
const button = findTestWrapper(wrapper,'button').at(0);
// 在button身上触发其click方法
button.trigger('click');
// emitted : 返回一个包含由 Wrapper vm 触发的自定义事件的对象。
// 也就是监听是否页面里面出发了 this.$emit('click')事件
// toBeTruthy 这个我们👆上面讲过了
expect(wrapper.emitted().click).toBeTruthy();
});
it('3: 传入icon参数, 可以显示icon组件', () => {
// shallowMount初始化时, 可以传递参数进去
// 下面的操作大家都懂
const wrapper = shallowMount(Button,{
propsData:{
icon:'cc-up'
}
});
// 找到和这个icon元素
const icon = findTestWrapper(wrapper,'icon').at(0);
// 在我传递了icon之后, 这个icon组件必须存在
expect(icon).toBeTruthy();
});
});

上面的例子里面提到了一个公共方法我来解释一下

export const findTestWrapper = (wrapper, tag) => {
return wrapper.findAll(`[data-test="${tag}"]`);
};

我们在书写代码的时候, 为了方便以后的测试, 也会添加一些测试属性, 比如下面这种


{{name}}

取值:

findTestWrapper(wrapper,'name')

findAll 是 wrapper身上的方法, 与之对应还有find 只找寻一个

输入框的测试

import { shallowMount } from '@vue/test-utils';
import Input from '../../src/components/Input';
import { findTestWrapper } from '../utils/util';
describe('测试button组件', () => {
it('1: 可以渲染出Input组件', () => {
// 这个属于基础步骤了
const wrapper = shallowMount(Input);
expect(wrapper.contains('input')).toBe(true);
});
it('2: 输入value与显示的内容相同, 并且修改联动', () => {
// 测试是否双向绑定
const wrapper = shallowMount(Input,{
propsData:{
value:'内容1'
}
});
// 取到输入框实例
const input = findTestWrapper(wrapper,'input').at(0);
// element就是直接取到dom了...这个dom也是未dom
// value可以模拟的拿出显示的值
expect(input.element.value).toBe('内容1')
// 改变也随之改变
wrapper.setProps({ value: '内容2' })
// 只要一起变了就满足需求
expect(input.element.value).toBe('内容2')
});
// 我的输入框是有清除功能的额
it('3: 清除内容按钮有效', () => {
const wrapper = shallowMount(Input,{
propsData:{
value:'内容1',
clear:true
}
});
// hover 时候才会出现!!
// 这是组件的内部触发条件, setData可以强行改变组件内部的data数据
wrapper.setData({
hovering:true
})
const clear = findTestWrapper(wrapper,'clear').at(0);
// 这里也讲过toBeTruthy可以判断是否可转true
// 也就是这个定义的实例是否存在
expect(clear).toBeTruthy();
// 触发清除事件
clear.trigger('click');
expect(wrapper.emitted().input).toBeTruthy();
});
it('4: 传入icon参数, 可以显示icon组件', () => {
const wrapper = shallowMount(Input,{
propsData:{
icon:'cc-up'
}
});
const icon = findTestWrapper(wrapper,'icon').at(0);
expect(icon).toBeTruthy();
});
it('5: 切换type, 出现文本框', () => {
const wrapper = shallowMount(Input,{
propsData:{
type:'textarea'
}
});
const textarea = findTestWrapper(wrapper,'textarea').at(0);
expect(textarea).toBeTruthy();
});
});

测试分页器

import { shallowMount } from '@vue/test-utils';
import Pagination from '../../src/components/Pagination';
import { findTestWrapper } from '../utils/util';
describe('测试分页器组件', () => {
it('1: 可以渲染出分页器组件', () => {
const wrapper = shallowMount(Pagination,{
propsData:{
pageTotal:5,
value:1
}
});
// classes 返回 Wrapper DOM 节点的 class。返回 class 名称的数组。或在提供 class 名的时候返回一个布尔值。这个的意思就是 这个dom的class 是 'cc-pagination'
expect(wrapper.classes()).toContain('cc-pagination');
});
it('2: 传入1000页是否显示1000页', () => {
const wrapper = shallowMount(Pagination, {
propsData:{
pageTotal:1000,
pageSize:1000,
value:1
}
});
const li = findTestWrapper(wrapper, 'item');
// 这个元素我获取到了1000个
expect(li.length).toBe(1000);
});
it('3: 点击第三页是否跳转到第三页', () => {
const wrapper = shallowMount(Pagination, {
propsData:{
pageTotal:10,
pageSize:10,
value:1
}
});
wrapper.vm.handlClick(3)
// 发送事件
expect(wrapper.emitted().input).toBeTruthy();
// 发送事件的参数, 注意,是数组的形式
// 这个事件发送的第一个参数[0]
expect(wrapper.emitted().input[0]).toEqual([3])
});
});

写到这里大家对测试也应该有了很多自己的想法, 没试过的小伙伴不妨试一试.

配置

上面没有提: 开启实时检测

"test:unit": "vue-cli-service test:unit --watch",

// 不管改没改, 所有文件都监控
"test:unit": "vue-cli-service test:unit --watchAll",

end

一套ui组件不写测试也是说不过去的, 写的过程也遇到很多很多的坑, 比如说两个相互以插槽嵌套的组件, 两个又都有’必传参数’的限制, vue没有很好的解决这个问题, 文档看了好久, 跟我的感觉就是有用的东西太少, 没办法这就是现状, 希望测试相关技术支持越来越完善吧.

大家都可以一起交流, 共同学习,共同进步, 早日实现自我价值!!

项目github地址: 链接描述
个人技术博客(ui官网):链接描述


推荐阅读
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 在JavaScript中,函数没有重载的概念,如果声明了多个重名的函数,不管函数的形参个数是否一样,只有最后一个有效。如果调用函数时传入的参数个数与函数定义时的参数个数不符,会出现不同的情况。函数调用时,传入的参数个数少于函数定义时的参数个数,未传入的参数会被当做undefined处理,可能会导致错误。而传入的参数个数多于函数定义时的参数个数,多余的参数不会被使用,但不会报错。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Android系统移植与调试之如何修改Android设备状态条上音量加减键在横竖屏切换的时候的显示于隐藏
    本文介绍了如何修改Android设备状态条上音量加减键在横竖屏切换时的显示与隐藏。通过修改系统文件system_bar.xml实现了该功能,并分享了解决思路和经验。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
  • JavaScript和HTML之间的交互是经由过程事宜完成的。事宜:文档或浏览器窗口中发作的一些特定的交互霎时。能够运用侦听器(或处置惩罚递次来预订事宜),以便事宜发作时实行相应的 ... [详细]
  • 本文介绍了Python函数的定义与调用的方法,以及函数的作用,包括增强代码的可读性和重用性。文章详细解释了函数的定义与调用的语法和规则,以及函数的参数和返回值的用法。同时,还介绍了函数返回值的多种情况和多个值的返回方式。通过学习本文,读者可以更好地理解和使用Python函数,提高代码的可读性和重用性。 ... [详细]
author-avatar
nanakuailed
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有