vue 打开一个iframe_Vue 之五 —— 单元测试

b44e3d7f0e6a7c15ae38c5e1e2e9febb.png
单元测试(unit testing):是指对软件中的最小可测试单元进行检查和验证。代码的终极目标有两个,第一个是实现需求,第二个是提高代码质量和可维护性。单元测试是为了提高代码质量和可维护性,是实现代码的第二个目标的一种方法。对vue组件的测试是希望组件行为符合我们的预期。

本文将从框架选型,环境搭建,使用方式,vue组件测试编写原则四个方面讲述如何在vue项目中落地单元测试。


一、框架选型

cypress / vue-test-utils

选择vue-test-utils 是因为它是官方推荐的vue component 单元测试库。

选择cypress而不是jest 主要是因为:

  • 测试环境的一致性: 在cypress上面跑的测试代码是在浏览器环境上的,而非像jest等在node上的。另外由于cypress在浏览器环境上运行,测试dom相关无需各种mock(如node-canvas等)
  • 统一测试代码风格、避免技术负担: 本身定位 e2e, 但是支持 unit test。
  • 支持CI环境

此外cypress还有很多非常棒的Features,感兴趣的朋友自行参考cypress官方文档。


二、环境搭建

1、安装依赖

npm i cypress @cypress/webpack-preprocessor start-server-and-test nyc babel-plugin-istanbul @vue/test-utils -D

note: 如果是使用vue cli3创建的项目,可以使用

# vue add @vue/cli-plugin-e2e-cypress
# npm i @cypress/webpack-preprocessor start-server-and-test nyc babel-plugin-istanbul @vue/test-utils -D

@cypress/webpack-preprocessor:引入webpack 预处理器

start-server-and-test:启动dev-server 监听端口启动成功,再执行测试命令。cypress 需要dev-server启动才能测试。

nyc babel-plugin-istanbul:覆盖率统计相关

2、添加/修改cypress.json文件

{"baseUrl": "http://localhost:9001","coverageFolder": "coverage","integrationFolder": "src","testFiles": "**/*.spec.js","video": false,"viewportHeight": 900,"viewportWidth": 1600,"chromeWebSecurity": false
}

3、修改package.json配置

"scripts": {"cy:run": "cypress run","cy:open": "cypress open","cy:dev": "start-server-and-test start :9001 cy:open","coverage": "nyc report -t=coverage","test": "rm -rf coverage && start-server-and-test start :9001 cy:run && nyc report -t=coverage"},

4、修改cypress/plugins/index.js(使用vue add @vue/cli-plugin-e2e-cypress的是tests/e2e//plugins/index.js)

// vue cli3 版本
const webpack = require('@cypress/webpack-preprocessor');
const webpackOptions = require('@vue/cli-service/webpack.config');webpackOptions.module.rules.forEach(rule => {if (!Array.isArray(rule.use)) return null;rule.use.forEach(opt => {if (opt.loader === 'babel-loader') {opt.options = {plugins: ['istanbul']};}});
});const options = {webpackOptions,watchOptions: {},
};module.exports = (on, config) => {on('file:preprocessor', webpack(options));return Object.assign({}, config, {integrationFolder: 'src',// screenshotsFolder: 'cypress/screenshots',// videosFolder: 'cypress/videos',// supportFile: 'cypress/support/index.js'})
};
// webpack4 版本const webpack = require('@cypress/webpack-preprocessor');
const config = require('../../webpack.base');
config.mode = 'development';
config.module.rules[0].use.options = {plugins: ['istanbul']
};module.exports = (on) => {const options = {// send in the options from your webpack.config.js, so it works the same// as your app's codewebpackOptions: config,watchOptions: {},};on('file:preprocessor', webpack(options));
};

5、修改cypress/support

// support/index.jsimport './commands';
import './istanbul';

在support目录里添加istanbul.js文件

// https://github.com/cypress-io/cypress/issues/346#issuecomment-365220178
// https://github.com/cypress-io/cypress/issues/346#issuecomment-368832585
/* eslint-disable */
const istanbul = require('istanbul-lib-coverage');const map = istanbul.createCoverageMap({});
const coverageFolder = Cypress.config('coverageFolder');
const coverageFile = `${ coverageFolder }/out-${Date.now()}.json`;Cypress.on('window:before:unload', e => {const coverage = e.currentTarget.__coverage__;if (coverage) {map.merge(coverage);}
});after(() => {cy.window().then(win => {const specWin = win.parent.document.querySelector('iframe[id~="Spec:"]').contentWindow;const unitCoverage = specWin.__coverage__;const coverage = win.__coverage__;if (unitCoverage) {map.merge(unitCoverage);}if (coverage) {map.merge(coverage);}cy.writeFile(coverageFile, JSON.stringify(map));cy.exec('npx nyc report --reporter=html -t=coverage')cy.exec('npm run coverage').then(coverage => {// output coverage reportconst out = coverage.stdout// 替换bash红色标识符.replace(/[31;1m/g, '').replace(/[0m/g, '')// 替换粗体标识符.replace(/[3[23];1m/g, '');console.log(out);}).then(() => {// output html file link to current test reportconst link = Cypress.spec.absolute.replace(Cypress.spec.relative, `${coverageFolder}/${Cypress.spec.relative}`).replace('cypress.spec.', '');console.log(`check coverage detail: file://${link}.html`);});});
});

6、修改package.json (推荐使用git push hooks 里跑test)

"gitHooks": {"pre-push": "npm run test"},"nyc": {"exclude": ["**/*.spec.js","cypress","example"]}

note: 如果项目使用了sass来写css,则必须指定node版本为v8.x.x,这个算是cypress的bug。Issuess

# npm install n -g
# sudo n v8.9.0
# npm rebuild node-sass

这样在git push之前会先跑单元测试,通过了才可以push成功。

0f87ffb9e957df0e000943e88a59d948.png

三、使用方法

  • 对于各个 utils 内的方法以及 vue组件,只需在其目录下补充同名的 xxx.spec.js,即可为其添加单元测试用例。
  • 断言语法采用 cypress 断言: https://docs.cypress.io/guides/references/assertions.html#Chai
  • vue组件测试使用官方推荐的test-utils: https://vue-test-utils.vuejs.org/
  • npm 命令测试:
  • npm run cy:run (终端测试,前置条件:必须启动本地服务)
  • npm run cy:open (GUI 测试,前置条件:必须启动本地服务)
  • npm run cy:dev (GUI测试, 自动启动本地服务,成功后打开GUI)
  • npm run test (终端测试, 自动启动本地服务,并且统计覆盖率,在终端运行,也是CI运行的测试命令)

四、测试原则

1、明白要测试的是什么

不推荐一味追求行级覆盖率,因为它会导致我们过分关注组件的内部实现细节,而只关注其输入和输出。一个简单的测试用例将会断言一些输入 (用户的交互或 prop 的改变) 提供给某组件之后是否导致预期结果 (渲染结果或触发自定义事件)。

2、测试公共接口

50b5b1510619082d3392e285c51c7bd8.png

a、如果模板有逻辑,我们应该测试它

// template
<button ref="logOutButton" v-if="loggedIn">Log out</button>
// Button.spec.jsconst PropsData = {loggedIn: true,
};it('hides the logOut button if user is not logged in', () => {const wrapper = mount(UserSettingsBar, { PropsData });const { vm } = wrapper;expect(vm.$refs.logOutButton).to.exist();wrapper.setProps({ loggedIn: false });expect(vm.$refs.logOutButton).not.to.exist();
});

原则:Props in Rendered Output

6189fddef2a344ca7e36bf8ad1c8b069.png

b、什么超出了我们组件的范围

  • 实现细节,过分关注组件的内部实现细节,从而导致琐碎的测试。
  • 测试框架本身, 这是vue应该去做的事情。
1、<p> {{ myProp }} </p>
expect(p.text()).to.be(/ prop value /);2、prop 校验   

c、权衡

c0d235a675ee8d57cff383b53b161402.png

Integration Test

2cd0d6eb17becacfdd4eff1102ac6306.png
// Count.spec.jsit('should display the updated count after button is clicked', () => {const wrapper = mount(Count, { count: 0});const ButtonInstance = wrapper.find(Button);const buttonEl = ButtonInstance.find('button')[0]; // find button clickbuttonE1.trigger('click');const CounterDisplayInstance = wrapper.find(CounterDisplay);const displayE1 = CounterDisplayInstance.find('.count-display')[0];expect(displayE1.text()).to.equal('1'); // find display, assert render
});

Shallow Test

9fc9c2a96cf23a2bb61737d4321e667f.png
// Count.spec.jsit('should pass the "count" prop to CounterDisplay', () => {const counterWrapper = shallow(Counter, {count: 10});const counterDisplayWrapper = counterWrapper.find(CounterDisplay);// we dont't care how this will be renderedexpect(counterDisplayWrapper.propsData().count).to.equal(10);
});it('should update the "count" prop by 1 on Button "increment" event', () => {const counterWrapper = shallow(Counter, {count: 10});const buttonWrapper = counterWrapper.find(Button);// we don't care how this was triggeredbuttonWrapper.vm.$emit('increment');expect(CounterDisplay.propsData().count).to.equal(11);
});

ebb95c8dbed002ebf42ef3b2cc93cd72.png

参考:

cypress-vue-unit-test

Vue Test Utils

Component Tests with Vue.js

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

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

相关文章

Hibernate如何存储二级缓存条目

介绍 使用数据库访问抽象层的好处是可以透明地实现缓存&#xff0c;而不会泄漏到业务逻辑代码中 。 Hibernate Persistence Context充当事务后写式高速缓存 &#xff0c;将实体状态转换转换为DML语句。 持久性上下文充当逻辑事务存储&#xff0c;并且每个Entity实例最多可以具…

file协议访问linux,Mozilla Firefox for Android 'file'协议未授权访问漏洞(CVE-2014-1501)

发布日期&#xff1a;2014-03-18更新日期&#xff1a;2014-04-02受影响系统&#xff1a;Mozilla Firefox < 28.0描述&#xff1a;--------------------------------------------------------------------------------BUGTRAQ ID: 66424CVE(CAN) ID: CVE-2014-1501Firefox是…

DrJava试用笔记

安装方便&#xff1a;只要配好JAVA_HOME&#xff0c;用java -jar drjava-stable-20120818-r5686.jar即可启动&#xff0c;算是绿色软件&#xff1b; 特色功能&#xff1a;交互式命令行&#xff0c;可以在调试程序时改变变量值&#xff0c;很方便&#xff1b; 编辑功能比较弱&…

dcdc芯片效率不高的原因_半导体厂商如何做芯片的出厂测试?

本文来源于知乎&#xff0c;已获作者授权&#xff0c;谢谢。作者&#xff1a;温戈链接&#xff1a;https://www.zhihu.com/question/20584576/answer/1538640891知乎网友提问&#xff1a;半导体厂商如何做芯片的出厂测试&#xff1f;例如 Intel 的 CPU、手机处理器&#xff0c;…

在运行时更新代码(已Spring解密)

当从编译到部署再到测试的开发周期花费太长时间时&#xff0c;人们希望能够及时替换正在运行的代码&#xff0c;而无需重新启动应用程序服务器并等待部署完成。 在这种情况下&#xff0c;像JRebel这样的商业解决方案或像Grails这样的开源框架就可以提供帮助。 JVM不支持在运行…

魅族android n内测报名,不再万年Android 5.0! Flyme安卓N内测招募开启

科客点评&#xff1a;恰逢Flyme五周年庆&#xff0c;这算的是给煤油们最大的礼物。近日&#xff0c;魅族Flyme系统非常活跃&#xff0c;为国内友商操碎了心&#xff0c;为此适配了一众友商热门机型&#xff0c;刷了不少存在感&#xff0c;但这显然不是魅族要搞的“大事情”。6月…

Android AudioTrack/AudioRecord -wav文件读取3

下面是一个网上一个大神写的,在公司测过了,还不错. 还可以写一个构造函数: initReader(InputStream is){ fis new FileInputStream(is); bis new BufferedInputStream(fis); }eg:call it as following : InputStream isActivity.getResource().openRawResource(); InitRe…

db2数据库连接数 linux_介绍一款数据库管理工具DBeaver

之前连接MySQL一直使用的是navicate&#xff0c;挺好用的&#xff0c;不过是个付费软件&#xff0c;一直想找一款免费开源的软件来替代。今天偶然间发现DBeaver&#xff0c;这是一款基于java开发的数据库工具&#xff0c;而且可以支持Windows、Linux、MacOS多个平台&#xff0c…

jqgrid mvc_jqGrid,REST,AJAX和Spring MVC集成

jqgrid mvc两年多以前&#xff0c;我写了一篇关于如何在Struts2中实现优雅的CRUD的文章。 实际上&#xff0c;我必须就该主题写两篇文章&#xff0c;因为该主题如此广泛。 今天&#xff0c;我采用了一套更为流行的&#xff0c;完善的框架和库&#xff0c;采用了更为轻量级的现代…

android view getwidth 0,Android中View.getWidth()和View.getMeasuredWidth()的区别

一。也許很多童鞋對getWidth()和getMeasuredWidth()的用法有很多的不解&#xff0c;這兩者之間有什麼樣的不同呢&#xff0c;網上也有各種不同的版本&#xff0c;但大多數都大同小異&#xff0c;從這個地方CtrlC,到另一個地方CtrlV,沒有把問題說透&#xff0c;也有一部分文章誤…

微信利用PHP创建自定义菜单的方法

在使用通用接口前&#xff0c;你需要做以下两步工作&#xff1a;1.拥有一个微信公众账号&#xff0c;并获取到appid和appsecret&#xff08;在公众平台申请内测资格&#xff0c;审核通过后可获得&#xff09;2.通过获取凭证接口获取到access_token注意&#xff1a;access_token…

ChronicleMap –具有堆外内存的Java体系结构

我的上一篇文章是在几周前写的&#xff0c;在收到一些有效的反馈后&#xff0c;我想澄清几点&#xff0c;作为本文的序言。 “ 使用零垃圾创建数百万个对象 ”的主要收获应该是&#xff0c;使用Chronicle&#xff0c;在编写Java程序时&#xff0c;您不会“局限于”使用jvm分配…

HTML滚动条S默认最小值,css修改滚动条默认样式

html代码:这是内容111这里是内容222这里是内容333css代码:.inner{width: 265px;height: 400px;position: absolute;top: 33px;left: 13px;/*cursor: pointer;*/overflow:hidden;}.innerbox{overflow-x: hidden;overflow-y: auto;color: #000;font-size: .7rem;font-family: &qu…

下列不属于html5语义元素,HTML5 新的语义元素

HTML5 提供了新的语义元素来明确一个Web页面的不同部分:HTML5中新的语义元素HTML5 元素标签定义文档中的节(section、区段)。比如章节、页眉、页脚或文档中的其他部分。根据W3C HTML5文档: section 包含了一组内容及其标题。WWFThe World Wide Fund for Nature (WWF) is....HTM…

【Android Developers Training】 81. 解析XML数据

注&#xff1a;本文翻译自Google官方的Android Developers Training文档&#xff0c;译者技术一般&#xff0c;由于喜爱安卓而产生了翻译的念头&#xff0c;纯属个人兴趣爱好。 原文链接&#xff1a;http://developer.android.com/training/basics/network-ops/xml.html 可扩展…

java核心面试_不正确的核心Java面试答案

java核心面试总览 在Internet上&#xff0c;Java面试问题和答案从一个网站复制到另一个网站。 这可能意味着错误或过时的答案可能永远不会得到纠正。 这是一些不太正确或已经过时的问题和答案。 即是Java 5.0之前的版本。 每个提供的问题后都有两个部分。 斜体的第一部分指示…

outlook自动保存html,当创建一个新的HTML电子邮件时保持默认的Outlook格式

我想创建一个简单的脚本来创建一个HTML消息&#xff0c;并且我想保留尽可能多的默认值。当创建一个新的HTML电子邮件时保持默认的Outlook格式在我的情况下&#xff0c;当我使用Home创建一个新邮件->新邮件时&#xff0c;它总是会创建一个默认字体[Calibri 11]&#xff0c;格…

干加个偏旁可以变成什么字_面试官:“干”字加一笔,变成什么字?回答王和午字不对...

随着大学生的增多&#xff0c;如今的求职者进入职场&#xff0c;想到一份心仪的工作&#xff0c;最让人头疼的就是面试&#xff0c;越来越多的企业都需要全能型的人才&#xff0c;从而在面试的时候不仅要考核专业知识&#xff0c;面试官还要费尽心思出各种各样的题来考验求职者…

Oracle研学-查询

学自B站黑马程序员 1.单表查询 //查询水表编号为 30408 的业主记录 select * from T_OWNERS where watermeter30408 //查询业主名称包含“刘”的业主记录 select * from t_owners where name like %刘% //查询业主名称包含“刘”的并且门牌号包含 5 的业主记录 select * from…

国际站html代码,国际站必须看得懂的HTML代码

国际站必须看得懂的HTML代码國産〇〇柒大家每天都忙着找关键词&#xff0c;忙着写标题&#xff0c;忙着做各种的优化。目的就是想把自己的产品排名到前面&#xff0c;获得更多的曝光&#xff0c;带来更多的询盘。在这个过程中我们是客服同事也是一名搜索优化人员&#xff0c;但…