前端 单元测试介绍 - 以及在项目中使用 (史上最全)

news2024/11/26 18:28:04

前言

我们前端开发过程中,编写测试代码,有以下这些好处:

更快的发现bug,让绝大多数bug在开发阶段发现解决,提高产品质量
比起写注释,单元测试可能是更好的选择,通过运行测试代码,观察输入和输出,
有时会比注释更能让别人理解你的代码(当然,重要的注释还是要写的。。。)

有利于重构,如果一个项目的测试代码写的比较完善,重构过程中改动时可以迅速的通过测试代码是否通过来检查重构是否正确,大大提高重构效率
编写测试代码的过程,往往可以让我们深入思考业务流程,让我们的代码写的更完善和规范。

基础的配置和使用可参考Vue3 + ts + jest 单元测试 配置以及使用

1. 如何创建一个测试用例

import { mount, shallowMount } from '@vue/test-utils';
import InputNumberGroup from '@/components/Common/InputNumberGroup/index.vue';
import ElementPlus from 'element-plus';
const wrapper = mount(InputNumberGroup, {
  global: {
    plugins: [ElementPlus]
  }
});

describe('测试组件 in input-number-group',   () => {
  it('测试class 是否具有相应的类名', () => {
    expect(wrapper.classes('input-number-group')).toBe(true);
  });
});

mount()与shallowMount()关键字

const wrapper = mount(InputNumberGroup)
深层渲染mount 可以将父组件下面的子组件全部渲染 
const wrapper = mountshallow(InputNumberGroup)
而mountshallow只会渲染父组件,不会去渲染子组件
global将element ui 组件 挂在到wrapper上 类似 createApp.use(elemeent)

describe()

创建一个分组,可以在这里面编写相应的测试计划。

It()关键字

It 断言 他有两个参数 第一个是字符串 一般用于说明测试组件的那个内容 ,
第二个参数为一个函数 里面用于编写判断,当判断错误时可以精准的查找到错误位置

expect()

Jest为我们提供了expect函数用来包装被测试的方法并返回一个对象,
该对象中包含一系列的匹配器来让我们更方便的进行断言,上面的toBe函数即为一个匹配器

匹配器

toBe() 精准匹配
toBeNull只匹配null
toBeUndefined只匹配undefined
toBeDefine与toBeUndefined相反
toBeTruthy匹配任何if语句为真
toBeFalsy匹配任何if语句为假
expect(fn).toHaveBeenCalled() // 判断函数是否被调用expect(fn).toHaveBeenCalledTimes(number)
 // 判断函数被调用次数
expect(['one','two']).toContain('one'); // 含有某个元素

数字匹配器

大于。toBeGreaterThan()
大于或者等于。toBeGreaterThanOrEqual()
小于。toBeLessThan()
小于或等于。toBeLessThanOrEqual()

toBe和toEqual同样适用于数字。
使用toMatch()测试字符串,传递的参数是正则表达式。
如何检测数组中是否包含特定某一项,可以使用toContain()

运行多个测试

只需要在 describe() 作用域中增加多个 it() 声明即可。*

2. 单元测试-ui测试

2.1 首先判断dom元素是否存在

import { mount, shallowMount } from '@vue/test-utils';
import InputNumberGroup from '@/components/Common/InputNumberGroup/index.vue'; //子组件
import ElementPlus from 'element-plus';
const wrapper = mount(InputNumberGroup, {
  global: {
    plugins: [ElementPlus]
  }
});

describe('测试组件 in input-number-group',   () => {
  it('测试dom元素 是否存在', () => {
    expect(wrapper.classes('input-number-group')).toBe(true);
  }),
  })

Classes()

Wrapper.classes('is-active')
Wrapper.classes:参数为想要获取的class名 返回是否拥有该class的dom或者类名数组。

2.2 判断dom元素是否存在相应的class样式

  it('测试dom元素 身上是否存在其他class样式', () => {
    const dom = wrapper.find('.el-input-number'); // 获取dom元素
    console.log(dom.attributes('class')); //打印dom身上的元素
    expect(dom.attributes('class')).toContain("el-input-number--small"); //判断该dom元素上是否存在el-input-number--small 样式
  }),

样式选择器find() 和 findAll()方法

这两个方法接收一个选择器作为参数,比如CSS选择器或者Vue实例都可以。

let el = wrapper.find('.message').find('span').element返回第一个满足条件的dom
let el = wrapper.findAll('.message').findAll('span').element返回所有满足条件dom ,但是返回值为一个数组
let el = wrapper.findAll('.message').findAll('span').at(0) 去获取元素的第一个
wrapper.find({ ref: 'my-button' })根据ref获取元素
wrapper.find({ name: 'MyCounter' })根据name获取元素

attributes() 属性

可以获取dom结构上的,所有属性,可以用来判断某些样式是否存在dom上面

2.3 判断dom元素,HTML标签中包含什么

  it('测试dom元素,HTML标签包含什么', () => {
    expect(wrapper.find('.input-number-group').html()).toContain("div"); 
  }),

2.4 判断dom元素文本内容

  it('测试dom元素 内容', async() => {
     console.log(wrapper.find(".is-active").text()); //输出文本内容
    expect(wrapper.find(".is-active").text()).toBe("布局");  //判断文本内容是否正确
  })

2.5 判断插槽

import { mount, shallowMount  } from '@vue/test-utils';
import inktankButton from '@/components/base/inktank-button.vue'; //子组件
import ElementPlus from 'element-plus';
const columnWrapper =`<div>hello</div>`;
const wrapper = mount(inktankButton, {
  global: {
    plugins: [ElementPlus],
  },
  slots: {
    default:`<div>hello</div>`,   //会自动匹配默认插槽
    slotName: `<div>slot具名插槽</div>`, // 将会匹配 `<slot name="slotName" />`
  },
});
exports[`测试组件 in inktankButton 测试class 是否具有相应的类名 1`] = `
<button
  class="el-button"
  type="button">
  <span class="" >
    <div>
      hello
    </div>
    <div>
      slot具名插槽
    </div>
  </span>
</button>

Slots插槽参数

首先在mount / mountshallow函数内设置slots参数,该参数用于定义组件内的插槽
default 设置默认插槽内容
Slotname 插槽名字 用于设置具名插槽内容

2.6 快照功能

  it('layout toMatchSnapshot', async() => {
    const wrapper = mount(treeView);
      const {vm} =wrapper;
      await vm.$nextTick();
     expect(wrapper.element).toMatchSnapshot(); //生成快照
  });

toMatchSnapshot()

首先在快照之前 使用nextTick是为了防有些dom元素没有渲染完成,就生成快照,会导致样式有问题
使用快照功能 可以在tests文件下生成__snapshots__文件夹,
在__snapshots__目录中产生一个xxx.test.js.snap文件 会将所测试的组件生成html结构,
方便观察元素是否成功渲染

在这里插入图片描述

3. 单元测试-事件测试

3.1触发点击事件

  it("layout tree 点击事件" , async () => {
    const wrapper = mount(treeView); //组件实例
    const {vm} =wrapper; 
    await wrapper.vm.$nextTick(); // 同步获取dom元素渲染
    expect(wrapper.element).toMatchSnapshot(); // 快照渲染
    const firstNodeContentWrapper = wrapper.find('.el-tree-node__content'); //获取触发事件的元素
    const firstNodeWrapper = wrapper.find('.el-tree-node');
    await firstNodeContentWrapper.trigger('click'); //触发事件
    await vm.$nextTick(); 
    expect(wrapper.element).toMatchSnapshot(); //再次快照
    expect(firstNodeWrapper.classes('is-expanded')).toBe(true);//元素判断
    expect(firstNodeWrapper.classes('is-current')).toBe(false);
  });

事件点击之前,快照渲染的页面
在这里插入图片描述

事件点击之后,页面重新渲染
在这里插入图片描述

Trigger()

首先获取到dom,然后使用trigger去触发想要测试的事件,在此使用await 是为了确保在断言之前 你的dom操作会执行完成
注意,trigger接受第二个参数会将选项传递给触发的事件

3.2 触发emit

import { mount, shallowMount } from '@vue/test-utils';
import InputNumberGroup from '@/components/Common/InputNumberGroup/index.vue'; //子组件
import ElementPlus from 'element-plus';
const wrapper = mount(InputNumberGroup, {
  global: {
    plugins: [ElementPlus]
  }
});
describe('测试组件 in input-number-group',   () => {
  it('正常click 触发事件', async () => {
    await wrapper.trigger('click'); //触发点击事件
    expect(wrapper.emitted().click).toBeTruthy();
  });
  it('正常change 触发事件', () => {
    wrapper.trigger('change', { keyCode: 65 });// 触发change事件
    console.log(wrapper.emitted().change); 
    expect(wrapper.emitted().change).toBeTruthy();// 判断emit 提交change事件是否触发
    expect(wrapper.emitted().change).toHaveLength(1); // 判断emit 提交change事件次数
    expect(wrapper.emitted().change[0].keyCode).toEqual(65);// 判断emit 提交change事件参数
    expect(wrapper.emitted().change.length).toBe(1); // 判断emit 提交change事件次数
  });
  it('正常blur 触发事件', () => {
    wrapper.trigger('blur');
    expect(wrapper.emitted().blur).toBeTruthy();
  });
});

emitted()

可以监听到emit事件的触发,参数存储在一个数组中,因此可以验证哪些参数与每个事件一起发出。
判断是否触发相应事件
	expect(wrapper.emitted()).toHaveProperty('change')
可以判断发射的是什么事件
	 expect(wrapper.emitted().change).toHaveLength(1)
判断发射事件的次数

4. 单元测试-方法测试

import { isValidKey  }  from "../../../../utils/index";
describe('测试 isValidKey', () => {
  it("test isValidKey 对象里面是否存在",()=>{
    const i = isValidKey('obj' ,{obj:11 ,obj2 :22}); //添加虚拟数据,调用方法
    expect(i).toBe(true); //判断返回值
  });
});
import { strMapToObj, objToStrMap, mapToJson, jsonToMap } from '@/utils/mapUtil'
describe('测试 map相关方法', () => {
    let mapA = new Map();
    mapA.set('1', 'num1');
    mapA.set('true', 'bool1');

    it('map转化为对象', () => {
        const map = [
            ['1', 'str1'],
            [1, 'num1'],
            [true, 'bool1']
        ]
        const data = strMapToObj(map);
        expect(data).toEqual({ "1": "num1", "true": "bool1", });
    }),
        it('对象转换为Map', () => {
            const obj = { "1": "num1", "true": "bool1" }
            const data = objToStrMap(obj);
            expect(data).toEqual(mapA);
        })
        ,
        it(' map转换为json', () => {
            const map = new Map();
            map.set('1', 'true');
            map.set('2', 'false');
            const data = mapToJson(map);
            expect(data).toEqual("{\"1\":\"true\",\"2\":\"false\"}");
        }),
        it('json转换为map', () => {
            const jsonStr = "{\"1\":\"num1\",\"true\":\"bool1\"}"
            const data = jsonToMap(jsonStr);
            expect(data).toEqual(mapA);
        })
})
import  {isUUID ,generateUuid}   from "../../../../utils/uuid";
describe('测试 uuid', () => {
  it("test uuid 方法isUUID", async()=>{
      const testContent = isUUID('Layout');
      console.log(testContent);
      expect(testContent).toBe(false);
  });
  it("test uuid 方法generateUuid", async()=>{
    const testContent = generateUuid();
    console.log(testContent);
    expect(testContent).toEqual(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
 });
});

5. 测试总结在这里插入图片描述

    %stmts是语句覆盖率(statement coverage):是不是每个语句都执行了
    %Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了
    %Funcs函数覆盖率(function coverage):是不是每个函数都调用了
    %Lines行覆盖率(line coverage):是不是每一行都执行了

在这里插入图片描述
执行yarn test-utils之后 会在目录下生成一个coverage文件,
在index.html文件中右键选择 在浏览器中打开(快捷键 option + B)就可以看到测试报告。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/42509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ARM 异常处理方式简单介绍

一、什么是异常 正常工作之外的流程都叫异常&#xff1b; 也就是说&#xff0c;除了用户模式和系统模式外&#xff0c;其他情况都是异常&#xff0c;见下图&#xff1a; 异常会打断正在执行的工作&#xff0c;并且一般我们希望异常处理完成后继续回来执行原来的工作&#xff…

3-azido-1-Propanamine,88192-19-2,3-叠氮基丙胺 性质特点有哪些?

●中文名&#xff1a;3-叠氮基丙胺&#xff0c;3-叠氮基-丙胺 ●英文名&#xff1a;3-azido-1-Propanamine ●外观以及性质&#xff1a; 西安凯新生物科技有限公司供应的&#xff1a;​3-azido-1-Propanamine为淡黄色或无色油状&#xff0c;含有叠氮基团&#xff0c;叠氮基可以…

Node.js 入门教程 23 使用 npm 的语义版本控制 24 卸载 npm 软件包 25 npm 全局或本地的软件包

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录Node.js 入门教程23 使用 npm 的语义版本控制24 卸载 npm 软件包25 npm 全局或本地的软件包23 使用 npm 的语义版本控制 如果 Node.js 软件…

第147篇 笔记-预言机(Oracle)

定义&#xff1a;区块链预言机是将区块链连接到外部系统的实体&#xff0c;从而使智能合约能够基于现实世界的输入和输出执行。 预言机为分散的 Web3 生态系统提供了一种访问现有数据源、遗留系统和高级计算的方式。去中心化预言机网络&#xff08;DON&#xff09;支持创建混合…

[附源码]计算机毕业设计springboot“小世界”私人空间

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Java学历、技术哪个更重要?学历不好还能进大厂吗?

Java程序员的入行门槛并不高&#xff0c;并不看重你的学历和其他各方面&#xff0c;唯一看重的就是你技术是否过硬&#xff0c;能否独立参与到企业级开发的项目中去&#xff0c;说简单点入行只看技术。但是你如果想要长远发展服日后走上管理岗位&#xff0c;最好还是自考个本科…

使用 SwiftUI 构建可搜索列表,为您的 iOS 应用程序创建具有自动完成功能的可搜索列表(教程含源码)

设计新应用程序时面临的一大挑战是确保您的用户可以轻松浏览内容。如果体验太难或花费太多时间,无论您的内容有多好,很多用户都会转向另一个应用程序选项或放弃。 期望用户滚动浏览一长串选项是不切实际的,添加搜索功能可以极大地改善用户体验。更进一步,在用户键入时让列…

2.RabbitMQ安装

2.RabbitMQ安装 注意&#xff1a;安装时使用的系统是CentOS-7,MQ基本概念和RabbitMQ的相关知识请查看写的文章。 1、安装依赖环境 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel…

17、Health Check 健康检查

强大的自愈能力是kubernetes容器编排引擎的重要特性。自愈的默认实现方式是自动重启发生故障的容器。除此之外&#xff0c;还可通过Liveness和Readiness探测机制设置更精细的健康检查&#xff0c;进而实现如下要求&#xff1a;零停机部署避免部署无效的镜像更加安全的滚动升级一…

有没有免费的视频剪辑软件?快来看看这些视频裁剪软件

我们有时候将视频拍好后&#xff0c;会觉得视频中有些画面的边缘出现了瑕疵&#xff0c;就想要将那些边缘裁剪掉&#xff0c;但是却不知道要怎么操作才能裁剪视频的画面。其实想要裁剪视频的画面很简单&#xff0c;我们只需要借助一些视频处理工具就可以实现裁剪视频画面的操作…

Swift 周报 第十八期 |技术汇总

前言 本期是 Swift 编辑组自主整理周报的第九期&#xff0c;每个模块已初步成型。各位读者如果有好的提议&#xff0c;欢迎在文末留言。 欢迎投稿或推荐内容。目前计划每两周周一发布&#xff0c;欢迎志同道合的朋友一起加入周报整理。 蝴蝶的生命之所以如此短暂&#xff0c…

解决git中出现的“fatal ‘xxxx‘ does not appear to be a git repository”错误的方法

今天来分享一下我在使用git中出现的一个错误提示&#xff0c;话不多说&#xff0c;我们直接来分析~ 这个错误是我在通过SSH方式pull远程仓库时候出现的&#xff0c;错误提示如下&#xff1a; fatal: xxx(你的仓库别名) does not appear to be a git repository fatal: Could n…

【C++学习】string的模拟实现

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 上篇文章中本喵介绍了C标准库中string类的使用&#xff0c;下面本喵来模拟实现一下string类。库中的s…

【Spring框架】爆gan两万六千字,助你通关IoC和DI

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;【Spring】 &#x1f96d;本文内…

navicat连接mysql数据库

一、打开navicat软件 二、创建一个测试连接 1、点击【连接】&#xff0c;选择【MySQL】 2、创建连接。 3、连接出现报错 三、解决方式&#xff1a; 1、键盘上wins r 同时按&#xff0c;输入cmd&#xff0c;调出命令行窗口。 2、通过cmd登陆mysql 3、输入以下语句修改密码 更…

拿下50亿后,岚图能否一绘蓝图?

近两年&#xff0c;我国新能源汽车市场呈现出一派百家争鸣、高歌猛进的势头。2021年&#xff0c;A股新能源汽车指数全年上涨42.72%&#xff0c;大幅跑赢沪深300、中证500和汽车指数。 然而进入2022年寒冬&#xff0c;新能源汽车市场却集体打了个“哆嗦”。 美股特斯拉自今年4…

设备安装CoreELEC系统,并配置遥控:实现低成本NAS影音播放器

目录0. 前言CoreELEC简介动机硬件1.准备工作1.1下载镜像1.2 制作启动U盘2.安装CoreELEC2.1 从U盘启动2.2 CoreELEC写入盒子emmc3.设置遥控器本文原首发于zdm&#xff0c;由于该平台审核机制出现问题且编辑器极其不好用&#xff0c;所以发布于此 仅作为记录操作的用途 0. 前言 …

代码随想录66——额外题目【回溯、贪心】:52N皇后II、649Dota2 参议院、1221分割平衡字符串

文章目录1.52N皇后II1.1.题目1.2.解答2.649Dota2 参议院2.1.题目2.2.解答3.1221分割平衡字符串3.1.题目3.2.解答1.52N皇后II 参考&#xff1a;代码随想录&#xff0c;52N皇后II&#xff1b;力扣题目链接 1.1.题目 1.2.解答 这道题和之前做过的 51.N皇后 是一模一样的&#x…

怿星科技参加2022(第六届)高工智能汽车年会

2022&#xff08;第六届&#xff09;高工智能汽车年会将于下周三在上海虹桥拉开帷幕&#xff0c;怿星科技作为本次活动的赞助商&#xff0c;将在11月30日下午的【座舱算力与系统】分论坛与大家分享关于智能汽车软硬分离探索与实践的专题演讲。此外&#xff0c;在活动期间&#…

docker 之间相互通讯

方式一&#xff1a;通过IP直接访问(不推荐) 查询容器对应的IP命令&#xff1a; docker inspect 容器 | grep IPAddress 通过docker容器启动的实例分配的ip地址&#xff0c;直接访问&#xff0c;docker重启时IP会发生变化&#xff0c;所以不推荐 方式二&#xff1a;通过端口…