一步一步实现现代前端单元测试
2年前写过一篇文章用Karma和QUnit做前端自动单元测试,只是大概讲解了 karma 如何使用,针对的测试情况是传统的页面模式。本文中题目中【现代】两字表明了这篇文章对比之前的最大不同,最近几年随着SPA
(Single Page Application) 技术和各种组件化解决方案(Vue/React/Angular)的普及,我们开发的应用的组织方式和复杂度已经发生了翻天覆地的变化,对应我们的测试方式也必须跟上时代的发展。现在我们一步一步把各种不同的技术结合一起来完成页面的单元测试和 e2e 测试。
1 karma + mocha + power assert
- karma - 是一款测试流程管理工具,包含在执行测试前进行一些动作,自动在指定的环境(可以是真实浏览器,也可以是 PhantamJS 等 headless browser)下运行测试代码等功能。
- mocha - 测试框架,类似的有 jasmine 和 jest 等。个人感觉 mocha 对异步的支持和反馈信息的显示都非常不错。
power assert - 断言库,特点是
No API is the best API
。错误显示异常清晰,自带完整的自描述性。1) Array #indexOf() should return index when the value is present:AssertionError: # path/to/test/mocha_node.js:10assert(ary.indexOf(zero) === two)| | | | || | | | 2| -1 0 false[1,2,3][number] two => 2 [number] ary.indexOf(zero) => -1
以下所有命令假设在 test-demo 项目下进行操作。
1.1 安装依赖及初始化
# 为了操作方便在全局安装命令行支持
~/test-demo $ npm install karma-cli -g# 安装 karma 包以及其他需要的插件和库,这里不一一阐述每个库的作用
~/test-demo $ npm install karma mocha power-assert karma-chrome-launcher karma-mocha karma-power-assert karma-spec-reporter karma-espower-preprocessor cross-env -D# 创建测试目录
~/test-demo $ mkdir test# 初始化 karma
~/test-demo $ karma init ./test/karma.conf.js
执行初始化过程按照提示进行选择和输入
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> mochaDo you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> noDo you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> no
生成的配置文件略作修改,如下(因篇幅原因,隐藏了注释):
module.exports = function(config) {config.set({basePath: '',// 表示可以在测试文件中不需引入即可使用两个库的全局方法frameworks: ['mocha', 'power-assert'],files: ['../src/utils.js','./specs/utils.spec.js.js'],exclude: [],preprocessors: {'./specs/utils.spec.js': ['espower']},reporters: ['spec'],port: 9876,colors: true,logLevel: config.LOG_INFO,autoWatch: false,browsers: ['Chrome'],singleRun: false,concurrency: Infinity})
}
1.2 待测试代码
我们把源文件放在src
目录下。
// src/utils.js
function reverseString(string) {return string.split('').reverse().join('');
}
1.3 测试代码
测试代码放在test/specs
目录下,每个测试文件以 .spec.js
作为后缀。
// test/spes/utils.spec.js
describe('first test', function() {it('test string reverse => true', function() {assert(reverseString('abc') === 'cba');});it('test string reverse => false', function() {assert(reverseString('abc') === 'cba1');});
});
1.4 运行测试
回到项目根目录,运行命令 npm run test
开始执行测试,然后看到浏览器会自动打开执行测试,命令行输出结果如下:
[karma]: Karma v2.0.0 server started at http://0.0.0.0:9876/
[launcher]: Launching browser Chrome with unlimited concurrency
[launcher]: Starting browser Chrome
[Chrome 63.0.3239 (Mac OS X 10.13.1)]: Connected on socket HEw50fXV-d24BZGBAAAA with id 24095855first test✓ test string reverse => true✗ test string reverse => falseAssertionError: # utils.spec.js:9assert(reverseString('abc') === 'cba1')| |"cba" false--- [string] 'cba1'+++ [string] reverseString('abc')@@ -1,4 +1,3 @@cba-1Chrome 63.0.3239 (Mac OS X 10.13.1): Executed 2 of 2 (1 FAILED) (0.022 secs / 0.014 secs)
TOTAL: 1 FAILED, 1 SUCCESS
可以看出一个测试成功一个测试失败。
2 测试覆盖率(test coverage)
测试覆盖率是衡量测试质量的主要标准之一,含义是当前测试对于源代码的执行覆盖程度。在 karma
中使用 karma-coverage
插件即可输出测试覆盖率,插件底层使用的是 istanbul。
~/test-demo $ npm i karma-coverage -D
修改 karma.conf.js
文件:
preprocessors: {'../src/utils.js': ['coverage'],'./specs/utils.spec.js': ['espower']
},reporters: ['spec', 'coverage'],
coverageReporter: {dir: './coverage', // 覆盖率结果文件放在 test/coverage 文件夹中reporters: [{ type: 'lcov', subdir: '.' },{ type: 'text-summary' }]
},
再次运行测试命令,在最后会输出测试覆盖率信息
=============================== Coverage summary ===============================
Statements : 100% ( 2/2 )
Branches : 100% ( 0/0 )
Functions : 100% ( 1/1 )
Lines : 100% ( 2/2 )
================================================================================
打开 test/coverage/lcov-report/index.html
网页可以看到详细数据
3 webpack + babel
上面的例子,只能用于测试使用传统方式编写的 js 文件。为了模块化和组件化,我们可能会使用ES6
、commonjs
、AMD
等模块化方案,然后使用 webpack 的 umd 打包方式输出模块以兼容不同的使用方式。一般我们还需要使用ES6+
的新语法,需要在 webpack 中加入babel
作为转译插件。
webpack 和 babel 的使用以及需要的依赖和配置,这里不做详细说明,因为主要是按照项目需要走,本文仅指出为了测试而需要修改的地方。
3.1 安装依赖
~/test-demo $ npm i babel-plugin-istanbul babel-preset-power-assert karma-sourcemap-loader karma-webpack -D
3.2 修改配置
.babelrc
把power-assert
以及coverage
的代码注入修改为在babel
编译阶段进行,在.babelrc
文件中加入以下配置:
{"env": {"test": {"presets": ["env", "babel-preset-power-assert"],"plugins": ["istanbul"]}}
}
test/index.js
在测试文件以及源码文件都非常多的情况下,或者我们想让我们的测试代码也使用上ES6+
的语法和功能,我们可以建立一个入口来统一引入这些文件,然后使用 webpack 处理整个入口,在test
目录下新建index.js
:
// require all test files (files that ends with .spec.js)
const testsContext = require.context('./specs', true, /\.spec$/)
testsContext.keys().forEach(testsContext)// require all src files except main.js for coverage.
// you can also change this to match only the subset of files that
// you want coverage for.
const srcContext = require.context('../src', true, /^\.\/(?!main(\.js)?$)/)
srcContext.keys().forEach(srcContext)
karma.conf.js
修改已经增加对应的配置
{files: ['./index.js'],preprocessors: {'./index.js': ['webpack', 'sourcemap'],},webpack: webpackConfig,webpackMiddleware: {noInfo: false},
}
utils.spec.js
import reverseString from '../../src/utils';describe('first test', function() {it('test string reverse => true', function() {assert(reverseString('abc') === 'cba');});it('test string reverse => false', function() {assert(reverseString('abc') === 'cba1');});
});
3.3 运行测试
运行测试,能得到和第二步相同的结果。
4 vue
如果项目中使用了 vue,我们想对封装的组件进行测试,也非常简单。
首先 webpack 配置中添加处理 vue 的逻辑,安装需要的依赖,这里不再赘述。
在src
目录下添加HelloWorld.vue
:
<template><div class="hello"><h1>{{ msg }}</h1><h2>Essential Links</h2></div>
</template><script>
export default {name: 'HelloWorld',data () {return {msg: 'Welcome to Your Vue.js App'}}
}
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>
h1, h2 {font-weight: normal;
}
ul {list-style-type: none;padding: 0;
}
li {display: inline-block;margin: 0 10px;
}
a {color: #42b983;
}
</style>
然后添加测试代码:
// test/specs/vue.spec.js
import Vue from 'vue';
import HelloWorld from '@/HelloWorld';describe('HelloWorld.vue', () => {it('should render correct contents', () => {const Constructor = Vue.extend(HelloWorld)const vm = new Constructor().$mount()assert(vm.$el.querySelector('.hello h1').textContent === 'Welcome to Your Vue.js App')})})
运行测试,可以看到命令行输出:
first test✓ test string reverse => true✗ test string reverse => falseAssertionError: # test/specs/utils.spec.js:9assert(reverseString('abc') === 'cba1')| |"cba" false--- [string] 'cba1'+++ [string] reverseString('abc')@@ -1,4 +1,3 @@cba-1HelloWorld.vue✓ should render correct contents
这里 Vue 能替换为其他任意的前端框架,只需要按照对应框架的配置能正确打包即可。
结语
上面所有代码都放在了这个项目,可以把项目下载下来手动执行查看结果。
以上大概讲解了现代前端测试的方法和过程,但是有人会问,我们为什么需要搞那么多事情,写那么多代码甚至测试代码比真实代码还要多呢?这里引用 Egg 官方一段话回答这个问题:
先问我们自己以下几个问题:- 你的代码质量如何度量?
- 你是如何保证代码质量?
- 你敢随时重构代码吗?
- 你是如何确保重构的代码依然保持正确性?
- 你是否有足够信心在没有测试的情况下随时发布你的代码?
如果答案都比较犹豫,那么就证明我们非常需要单元测试。
它能带给我们很多保障: - 代码质量持续有保障
- 重构正确性保障
- 增强自信心
- 自动化运行
Web 应用中的单元测试更加重要,在 Web 产品快速迭代的时期,每个测试用例都给应用的稳定性提供了一层保障。 API 升级,测试用例可以很好地检查代码是否向下兼容。 对于各种可能的输入,一旦测试覆盖,都能明确它的输出。 代码改动后,可以通过测试结果判断代码的改动是否影响已确定的结果。
是不是消除了很多心中的疑惑?
以上内容如有错漏,或者有其他看法,请留言共同探讨。
版权声明:原创文章,如需转载,请注明出处“本文首发于xlaoyu.info“。否则将追究法律责任。
一步一步实现现代前端单元测试相关推荐
- 前端单元测试---孤勇者级教程
<孤勇者>最近火爆的一塌糊涂,占领了小学生.甚至幼儿园,连我家2岁多的儿子尽然也会哼几句.虽然他以为这首歌是奥特曼的主题曲. 回到正题,现代前端项目的工程化.模板化日益壮大,各种类库框架层 ...
- button active 跳转到另一个页面_一步一步实现一个古诗词网站(四)——首页
汪小黑:一步一步实现一个古诗词网站(三)--首页zhuanlan.zhihu.com 在上篇文章中,我们一步一步的实现了我们的静态首页,从中学习到了页面布局方面的知识. 在这篇文章中,我们将使用 J ...
- 【Linux】一步一步学Linux——dpkg-preconfigure命令(275)
00. 目录 文章目录 00. 目录 01. 命令概述 02. 命令格式 03. 常用选项 04. 参考示例 05. 附录 01. 命令概述 dpkg-preconfigure命令用于在Debian ...
- 【Linux】一步一步学Linux——dpkg-reconfigure命令(272)
00. 目录 文章目录 00. 目录 01. 命令概述 02. 命令格式 03. 常用选项 04. 参考示例 05. 附录 01. 命令概述 dpkg-reconfigure命令是Debian lin ...
- 【Linux】一步一步学Linux——indent命令(262)
00. 目录 文章目录 00. 目录 01. 命令概述 02. 命令格式 03. 常用选项 04. 参考示例 05. 附录 01. 命令概述 indent命令可识别C语言代码文件,并加以格式化,以方便 ...
- 【Linux】一步一步学Linux——gcc命令(249)
00. 目录 文章目录 00. 目录 01. 命令概述 02. 命令格式 03. 常用选项 04. 参考示例 05. 附录 01. 命令概述 gcc命令使用GNU推出的基于C/C++的编译器,是开放源 ...
- 【Linux】一步一步学Linux——iptables命令(186)
00. 目录 文章目录 00. 目录 01. 命令概述 02. 命令格式 03. 常用选项 04. 参考示例 05. 附录 01. 命令概述 iptables命令是Linux上常用的防火墙软件,是ne ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权...
Oauth2授权,熟悉微信开发的同学对这个东西应该不陌生吧.当我们的应用系统需要集成第三方授权时一般都会做oauth集成,今天就来看看在Dapr的语境下我们如何仅通过配置无需修改应用程序的方式让第三方 ...
- (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性
转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)...
阅读目录 一.NumberBoxExtensions 二.DateTimeBoxExtensions 1.初始方案 2.改进方案 三.TextareExtensions 四.SelectExtensi ...
最新文章
- TypeError(“cannot concatenate ‘str‘ and ‘instancemethod‘ objects“,)
- 对人脑而言,阅读计算机代码和阅读语言有何不同?
- 政府免费WiFi遭吐槽:近七成网友表示从未用过
- 20.二叉树怎么存储
- ftruncate函数的功能及使用
- centos下cmake安装
- Arraylist理解(3)删除元素
- 第十四节(接口(行为))
- Python基础(函数)
- C++栈与队列基本操作
- 干货!一文带您读懂区块链溯源!
- Android设置Textview字体样式
- mysql 联合查询去重复_两个表联合查询去重复GROUP_CONCAT
- android 手机 平板同屏,多屏互动手机与平板互相投屏
- 计算机组成原理选择题题库
- mysql safe file priv_解决MySQL导入数据时遇到secure-file-priv的问题
- python 3d绘图立方体_用python绘制三维立方体的二维投影
- 大多数人不知道淘宝天猫有内部优惠卷,能省钱的公众号,购物省钱妙招
- linux下配置mysql_linux下安装mysql
- 快学Scala 学习笔记-1: (第一章到第三章)