先来几个专业词汇,这样显得高大上一点(不存在的=。=)

BDD: Behavior-Driven Development (行为驱动开发)TDD: Test-Driven Development (测试驱动开发)ATDD: Acceptance Test Driven Development(验收测试驱动开发)

好,说完了,然后我们废话不多说,直接进入正题。我会从多个测试框架入手,结合各种断言库,用代码方式说明。


单元测试(Unit Testing),是指对软件中的最小可测试单元进行检查和验证。

当今所有著名的框架都要进行单元测试,经过测试的框架,它的信任度显然高于未测试的框架。

这里,我们介绍一下karma这个前端的单元测试框架。

Spectacular Test Runner for Javascript​karma-runner.github.io

首先我们来安装一波:
新建一个文件夹,然后在空文件夹中打开终端输入

npm init -y
(sudo) npm install karma-cli -g
npm install karma karma-jasmine karma-chrome-launcher jasmine-core --save-dev
npm install karma-phantomjs-launcher --save-dev

你安装karma-cli这个倒是说得过去,可是这个jasmine是啥,这个chrome-launcherphantomjs-launcher又是啥?

没错,单说测试框架是不完整的,必须要有断言库与之相配合,这里的jasmine就是断言库。

啥是断言(assert)

根据概念:

断言是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。

一言以蔽之,老子/老娘说啥就是啥!听起来好像挺霸道的。那么具体呢?

顺着karma的正常流程向下走,我们来写一个简单的单元测试。在终端输入:

karma init

你会发现,需要做一个调查问卷了,问题如下:

> 请问你要用哪种测试框架呢?
> 按tab键选择,按回车键进入下一个问题。
> jasmine
(因为我们安装的是jasmine,选什么断言库都别忘了安装一下)> 您想要使用Require.js么?
> 选择yes的话,会安装Require.js插件。
> 按tab键选择,按回车键进入下一个问题。
> no
(这里我们选择no)> 你想要在什么浏览器中测试呢?
> 按tab键选择,输入空字符串进入下一个问题。
> Chrome
> PhantomJS
>注:上面的选择这两个浏览器的原因是我们之前安装了这两个浏览器的启动器(launcher)> 需要测试的源文件和测试命令文件放在哪呢?
你可以使用通配符(glob patterns)来匹配文件,比如:"js/*.js" 或 "test/**/*Spec.js"
输入空字符串进入下一个问题。
>
(这里先留空,可根据测试情况灵活配置)>在符合匹配的文件中有哪些文件可以排除在外呢?
你可以使用通配符来匹配文件,比如:"**/*.swp"
输入空字符串进入下一个问题。
> > 你想要Karma根据文件的变化立即做出响应么?
> yes

之后,你就会发现你的文件夹里多了一个文件:

打开这个文件,你会发现里面是一个配置项函数:

module.exports = function(config) {basePath: '', // 根路径将会同files和excluede项中的相对路径相关联frameworks: ['jasmine'], // 所使用的测试框架files: [], // 这里是需要测试的文件列表,有多种配置方式exclude: [], // 测试过程中排除在外的文件列表reporters: ['progress'], // 测试结果的汇报方式,port: 9876, // web服务器接口colors: true, // 是否使用彩色报告logLevel: config.LOG_INFO, // 日志级别,可配置的值有: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUGautoWatch: true, // 是否自动观测文档改变并执行测试命令browsers: ["Chrome", "PhantomJS"], // 用哪些浏览器测试呢singleRun: false, // 持续集成模式,如果设置成true,Karma将自行捕获浏览器,运行测试并根据结果退出,concurrency: Infinity // 并发数,同时跑多少个浏览器进行测试,默认无上限
}

默认会生成的配置项就是上面这些,更完整的配置请点我
这里稍微提一下browsers配置项,它可以配置高达8种浏览器:

每一种都需要安装对应的launcher。其中有两个需要注意chromeHeadlessPhantomJS。这两个是无头浏览器。所谓无头浏览器就是没有脑袋的浏览器

无头浏览器即headless browser,是一种没有界面的浏览器。既然是浏览器那么浏览器该有的东西它都应该有,只是看不到界面而已。因此这种浏览器没有渲染UI的过程,用于测试时的速度很快。

这就回答了上文launcher是啥的问题。毕竟,没有浏览器靠脑补可没法测试啊(真实)

言归正传。我们回到karma测试本身。接下来,我们修改一下配置:

files: ["src/srcTest/**/*.js", "test/unit/**/*.js"]

注意,上述写法只是配置写法中的一种, 配置的文件位置也是随您自己指定,更详细的配置请点我

采用上文写法的话,我们在files数组里面配置的第一项是需要测试的文件,第二项就是用什么方法去测试它的文件

因此,我们也在文件里创建对应的文件夹:

这里有一个要注意的点。我们的需要测试的文件和测试驱动文件的名字是一一对应的,区别就在于测试驱动文件的名字后要加上.spec

那么我们就在srcTest的文件里面写点什么吧....

newBee.js

// 减法函数
function minus(x) {return function(y) {return x - y;};
}

testKarma.js

// 加法函数
function add(x) {return function(y) {return x + y;};
}// 乘法函数
function multi(x) {return function(y) {return x * y;};
}//if函数测试
function ifTest(boolean) {if (boolean) {return "热热";} else {return "凉凉";}
}// 反转字符串
function reverseStr (string) {return string.split("").reverse().join("");
}

那么接下来,就在.spec文件里写入对应的测试断言。我滴个龟龟,终于说到断言了。

因为我们这里使用的是Jasmine,因此就先放一下它的官网。

Global​jasmine.github.io

我们结合实例来说文档

newBee.spec.js

describe("newBee单元测试", function() {it("减法函数测试", function() {var minus7 = minus(7);expect(minus7(6)).toBe(0);});
});

testKarma.spec.js

describe("testKarma单元测试", function() {it("如果函数测试", function() {expect(ifTest(true)).toBe(true);expect(ifTest(false)).toBe("凉凉");});it("回文函数测试", function() {expect(reverseStr('abc')).toEqual('cba');})});

基本的格式就是这样的,下面来解释一下

// 分组describe(), 这个是可以嵌套的,并且每个单独的测试都有beforeAll, afterAll, beforeEach和afterEach
describe("这里写测试群组的名称", function(){// 具体的测试,it(), 当其中所有的断言都为true时,则通过;否则失效。it('这里写具体测试的名称', function(){var a = true;// 期望, expect()。 匹配,to*()// 每个匹配方法在期望值和实际值之间执行逻辑比较// 它负责告诉jasmine断言的真假,从而决定测试的成功或失败// 木有错,老子/老娘说啥就是啥expect(a).toBe(true); // 这是肯定断言expect(!a).not.toBe(true); // 这是否定断言// jasmine内置的匹配方法有很多,亦可自定义匹配方法// toBe()// toEqual()// toMatch()// toBeUndefined()// toBeNull()// toBeTruthy()// toContain()// toBeLessThan()// toBeCloseTo()// toThrowError()// 等等等等})
})

那么,测试方法写完了,我们来实际运行一下测试吧。打开终端,输入:

karma start

就会在终端看到

可以看到,我们的测试在Chrome和PhantomJS浏览器中分别测试了的5个方法,都有2个没有通过测试,没错,我们当初在写测试的时候故意写错了(这是真的)。

那么我们把测试修改成真值。

newBee.spec.js

describe("newBee单元测试", function() {it("减法函数测试", function() {var minus7 = minus(7);expect(minus7(6)).toBe(1);});
});

testKarma.spec.js

it("如果函数测试", function() {expect(ifTest(true)).toBe("热热");expect(ifTest(false)).toBe("凉凉");
});

结果是:

全部SUCCESS, 撒花。

到这里,一个基本的测试流程就走完了。然而,这并非终点。

其实,还能更进一步的。我们打开终端:

npm install karma-coverage --save-dev

然后打开karma.conf.js, 添加一些配置项

// 这里配置哪些文件需要统计测试覆盖率,例如,如果你的所有代码文件都在src文件夹中,你就需要如下配置
preprocessors: {"src/srcTest/*.js": "coverage"
},
// 新增coverageReporter选项
// 配置覆盖率报告的查看方式,type查看类型,可以取值html、text等等,dir输出目录
coverageReporter: {dir: "docs/unit",reporters: [{type: "html",subdir: "report-html"}]
},
reporters: ['progress', "coverage"] // 没错,reporters里面新增了一个coverage

然后保存,再运行一次karma start

接着会发现你的项目里多了一个文件夹

用浏览器打开index.html。就会看到

这就是你所写的js的测试覆盖率。

这样看起来是不是高大上了一些呢?

这里就有一个问题了。普通的js可以测试,可是我是写Vue的啊,Vue组件怎么测试呢?很简单,Vue官网有非常详细的测试教程。甚至还有专用的测试工具和测试说明

彳亍口巴,你说的这些个单元测试看起来花里胡哨的,实际作用是什么呢?

单元测试的好处

  1. 单元测试不但会使你的工作完成得更轻松。而且会令你的设计会变得更好,甚至大大减少你花在调试上面的时间。
  2. 提高代码质量
  3. 减少bug, 快速定位bug
  4. 使修改和重构可以更放心
  5. 显得专业

单元测试的缺点

开发人员要花费时间在写测试代码上,然而又不会给你加工资...
小项目写测试只能单纯的增加开发时间和成本,然而又不会给你加工资...
我写了测试除了懂测试的人能看懂,别人又不知道,然而还不会给你加工资...

别别别,别打我...你先听我道(hu)理(jiao)讲(man)完(chan)。

  1. 对于所编写的代码,你在调试上面画了多少时间?
  2. 对于以前你自认为正确的代码,而实际上这些代码却存在重大的bug,你花了多少时间在重新确认这些代码上面?
  3. 对于一个别人报告的bug,你花了多少时间才找出导致这个bug的源码位置?

对于那些没有使用单元测试的程序员而言,上面这些问题所耗费的时间的是逐渐增加的,而且项目越深入,花费的时间越多;另一方面,适当的单元测试却可以很大程度地减少这些时间,从而为你腾出足够的时间来编写所有的单元测试——甚至可能还有剩余的空闲时间。

更加真实的是,主流的框架必须要写测试

不想当程序员的设计师不是好运维。 ----鲁迅

作为一个程序员,如果你想要让自己写的框架放到github和npm上能够为世界上的其他人所用。那么一个最基本的前提就是————代码没有BUG。可是,你的怎么向语言不通思维不同的人解释你的JavaScript库确实足够健壮呢。这个时候就需要单元测试出场了。

主流前端框架虽然在所使用的测试库(karma、jest、QUnit)和断言库(assert、jasmine、 chai)上略有差别,但Vue、React、Angular、Underscore甚至是jQuery都写了单元测试。

来个石锤

下面我们看一看Vue的测试是怎么写的:

git clone https://github.com/vuejs/vue.git
npm install
npm run test unit // 这里可以看到单元测试
npm run test // 这里就看全部的测试

Vue的测试覆盖率为

举例:v-show的测试

// import Vue from 'vue'describe('Directive v-show', () => {it('should check show value is truthy', () => {const vm = new Vue({template: '<div><span v-show="foo">hello</span></div>',data: { foo: true }}).$mount()expect(vm.$el.firstChild.style.display).toBe('')})it('should check show value is falsy', () => {const vm = new Vue({template: '<div><span v-show="foo">hello</span></div>',data: { foo: false }}).$mount()expect(vm.$el.firstChild.style.display).toBe('none')})it('should update show value changed', done => {const vm = new Vue({template: '<div><span v-show="foo">hello</span></div>',data: { foo: true }}).$mount()expect(vm.$el.firstChild.style.display).toBe('')vm.foo = falsewaitForUpdate(() => {expect(vm.$el.firstChild.style.display).toBe('none')vm.foo = {}}).then(() => {expect(vm.$el.firstChild.style.display).toBe('')vm.foo = 0}).then(() => {expect(vm.$el.firstChild.style.display).toBe('none')vm.foo = []}).then(() => {expect(vm.$el.firstChild.style.display).toBe('')vm.foo = null}).then(() => {expect(vm.$el.firstChild.style.display).toBe('none')vm.foo = '0'}).then(() => {expect(vm.$el.firstChild.style.display).toBe('')vm.foo = undefined}).then(() => {expect(vm.$el.firstChild.style.display).toBe('none')vm.foo = 1}).then(() => {expect(vm.$el.firstChild.style.display).toBe('')}).then(done)})it('should respect display value in style attribute', done => {const vm = new Vue({template: '<div><span v-show="foo" style="display:block">hello</span></div>',data: { foo: true }}).$mount()expect(vm.$el.firstChild.style.display).toBe('block')vm.foo = falsewaitForUpdate(() => {expect(vm.$el.firstChild.style.display).toBe('none')vm.foo = true}).then(() => {expect(vm.$el.firstChild.style.display).toBe('block')}).then(done)})it('should support unbind when reused', done => {const vm = new Vue({template:'<div v-if="tester"><span v-show="false"></span></div>' +'<div v-else><span @click="tester=!tester">show</span></div>',data: { tester: true }}).$mount()expect(vm.$el.firstChild.style.display).toBe('none')vm.tester = falsewaitForUpdate(() => {expect(vm.$el.firstChild.style.display).toBe('')vm.tester = true}).then(() => {expect(vm.$el.firstChild.style.display).toBe('none')}).then(done)})
})

只要你的测试覆盖率足够高,你就可以在著名的GitHub装逼网站Codecov搞一个覆盖率标签了。就像下面这个:

怎么样,这样你所写的框架,是不是就逼格满满?

所以你还在等什么,测不了吃亏,测不了上当,赶紧在自己的代码中加入测试吧,~~只要998~~,代码逼格带回家!

可以进行单元测试么_前端与单元测试相关推荐

  1. maven 单元测试并行_并行运行单元测试

    maven 单元测试并行 大约是时候单元测试的开发人员能够使用批注在Parallel中运行测试. 在今天的博客文章中,我们将介绍如何使用Easytest提供的注释使传统的Junit测试并行运行. 易测 ...

  2. 前端如何实现网络速度测试功能_前端组件单元测试

    啥?单元测试?我哪有时间写单元测试? 从软件质量说起 日常生活中,商品质量永远是我们进行选择时需要着重考虑的因素,计算机软件也不例外.优秀的软件应当如我们预期的一样工作,能够正确地处理所有功能性需求. ...

  3. java 单元测试技巧_其他一些单元测试技巧

    java 单元测试技巧 在我以前的文章中,我展示了有关JavaBeans单元测试的一些技巧. 在此博客文章中,我将针对单元测试一些相当常见的Java代码(即实用程序类和Log4J日志记录语句)提供另外 ...

  4. python123第三单元测试卷_第三单元测试卷(带答案)

    ) A. 诗人紧紧把握住立春这一节气 真实地描绘了春到人间的动人情景. B. 从"草木知"到"生意满" 诗人在作品中富有层次地再现了大自然的这一变化过程. C. ...

  5. 来,加入前端自动化单元测试

    最近闲来无事,开始摸索前端单元测试. 一是不备之需,二是确实在实际项目中能够用到单元测试.这样可以提高开发效率,提升代码质量,完全可以单独对 JS 进行测试,无需页面,不依赖其他第三方.   为什么需 ...

  6. pycharm appiunm 公众号测试_知道答案公众号_知到APP笔尖上的艺术——书法基础与赏析单元测试答案_知道答...

    知道答案公众号_知到APP笔尖上的艺术--书法基础与赏析单元测试答案_知道答案公众号 更多相关问题 按下图装置,持续通入X气体,可以看到a处有红色物质生成,b处变蓝,c处得到液体,则X气体可能是[ ] ...

  7. 大数据可视化陈为智慧树_智慧树知到_大数据可视化_答案章节单元测试答案

    智慧树知到_大数据可视化_答案章节单元测试答案 更多相关问题 (1)33+(-23)2-48-12×6:(2)当a=2时,计算21-a-a1+a的值. 计算(1-11-a)(1a2-1)的结果正确的是 ...

  8. 51、前端代码单元测试怎么做?

    我们专栏课程的知识部分已经告一段落了.今天,我来集中解决一下大家在学习中遇到的问题,我争取用快问快答的形式,咱们多回答一些问题. 1. 前端代码单元测试还是非常有必要的,不知道老师这一块是怎么做的,能 ...

  9. 单元测试编写_为什么要编写单元测试-测试技巧8

    单元测试编写 我对最近的博客"您应该测试什么"有很多React,有些人出于各种原因与我达成一致,另一些人则认为建议某些类可能不需要单元测试是完全危险的. 已经处理了什么测试,今天的 ...

最新文章

  1. mysql中groupby会用到索引吗_开发人员不得不知的MySQL索引和查询优化
  2. 正则表达式从基础到深入实战
  3. 谷歌官宣安卓改名!甜点不再
  4. React 组件的生命周期详解
  5. ScrollView嵌套StackView提示需要宽度和高度限制
  6. B站、字节跳动等上榜2021福布斯中国最佳雇主榜;中兴发布“5G 智能T恤”:可监测呼吸;Linux 5.13 发布|极客头条...
  7. Scrapy爬取多层级网页内容的方式
  8. 程序员的奋斗史(三十六)——人在囧途之应聘篇(六)——第一季终结篇
  9. 在Qt中配置海康工业相机SDK及遇到的问题(报错)
  10. 鲁迅生平及其作品简介
  11. 语音转换成文本 技术实现_职业转换者指南,帮助您实现梦想的技术工作
  12. fnd_global和fnd_profile 的区别
  13. 我看男人的眼光,是不行
  14. 《前端》localStorage 和 sessionStorage-将数据存入(取出)缓存的方法-2020年10月6日
  15. 经营性ICP与非经营性ICP有什么区别?
  16. 生物医学文献知识图创建的关系提取
  17. 雷军:小米如何成功逆转
  18. vivo冯宇飞:iQOO不请代言人 品牌更亲近互联网用户
  19. 计算机视觉中的变分方法-扩散(Diffusion)
  20. Could not extract response: no suitable HttpMessageConverter found for response type [class java.lan

热门文章

  1. 各种Exit退出函数用法
  2. java运算符 —(9)
  3. 操作系统原理之文件系统(第五章)
  4. ios怎样在一个UIImageButton的里面加一些自己定义的箭头
  5. 移动开发平台性能比較
  6. JMS-activMq与spring进行整合
  7. [Hadoop] - 自定义Mapreduce InputFormatOutputFormat
  8. net与树莓派的情缘-安装与卸载MySql(五)
  9. uva10617 - Again Palindrome(dp)
  10. hdu 2531 Catch him