我们前端开发过程中,编写测试代码,有以下这些好处:
更快的发现bug,让绝大多数bug在开发阶段发现解决,提高产品质量
比起写注释,单元测试可能是更好的选择,通过运行测试代码,观察输入和输出,
有时会比注释更能让别人理解你的代码(当然,重要的注释还是要写的。。。)
有利于重构,如果一个项目的测试代码写的比较完善,重构过程中改动时可以迅速的通过测试代码是否通过来检查重构是否正确,大大提高重构效率
编写测试代码的过程,往往可以让我们深入思考业务流程,让我们的代码写的更完善和规范。
基础的配置和使用可参考Vue3 + ts + jest 单元测试 配置以及使用
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);
});
});
const wrapper = mount(InputNumberGroup)
深层渲染mount 可以将父组件下面的子组件全部渲染
const wrapper = mountshallow(InputNumberGroup)
而mountshallow只会渲染父组件,不会去渲染子组件
global将element ui 组件 挂在到wrapper上 类似 createApp.use(elemeent)
创建一个分组,可以在这里面编写相应的测试计划。
It 断言 他有两个参数 第一个是字符串 一般用于说明测试组件的那个内容 ,
第二个参数为一个函数 里面用于编写判断,当判断错误时可以精准的查找到错误位置
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() 声明即可。*
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);
}),
})
Wrapper.classes('is-active')
Wrapper.classes:参数为想要获取的class名 返回是否拥有该class的dom或者类名数组。
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 样式
}),
这两个方法接收一个选择器作为参数,比如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获取元素
可以获取dom结构上的,所有属性,可以用来判断某些样式是否存在dom上面
it('测试dom元素,HTML标签包含什么', () => {
expect(wrapper.find('.input-number-group').html()).toContain("div");
}),
it('测试dom元素 内容', async() => {
console.log(wrapper.find(".is-active").text()); //输出文本内容
expect(wrapper.find(".is-active").text()).toBe("布局"); //判断文本内容是否正确
});
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 插槽名字 用于设置具名插槽内容
it('layout toMatchSnapshot', async() => {
const wrapper = mount(treeView);
const {vm} =wrapper;
await vm.$nextTick();
expect(wrapper.element).toMatchSnapshot(); //生成快照
});
首先在快照之前 使用nextTick是为了防有些dom元素没有渲染完成,就生成快照,会导致样式有问题
使用快照功能 可以在tests文件下生成__snapshots__文件夹,
在__snapshots__目录中产生一个xxx.test.js.snap文件 会将所测试的组件生成html结构,
方便观察元素是否成功渲染

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);
});
事件点击之前,快照渲染的页面

事件点击之后,页面重新渲染

首先获取到dom,然后使用trigger去触发想要测试的事件,在此使用await 是为了确保在断言之前 你的dom操作会执行完成
注意,trigger接受第二个参数会将选项传递给触发的事件
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();
});
});
可以监听到emit事件的触发,参数存储在一个数组中,因此可以验证哪些参数与每个事件一起发出。
判断是否触发相应事件
expect(wrapper.emitted()).toHaveProperty('change')
可以判断发射的是什么事件
expect(wrapper.emitted().change).toHaveLength(1)
判断发射事件的次数
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);
});
});

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

执行yarn test-utils之后 会在目录下生成一个coverage文件,
在index.html文件中右键选择 在浏览器中打开(快捷键 option + B)就可以看到测试报告。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po