摘要:单元测试上一节有讨论过,单元测试就是以代码单元为单位进行测试,代码单元可以是一个函数,一个模块,或者一个类。单元测试是最容易理解也最容易实现的测试方式。在写单元测试的时候,尽量将你的单元测试独立出来,不要几个单元互相引用。
本文作者:Gil Tayar
编译:胡子大哈翻译原文:http://huziketang.com/blog/posts/detail?postId=58d3de1e7413fc2e8240855b
英文连接:Testing Your Frontend Code: Part II (Unit Testing)
转载请注明出处,保留原文链接以及作者信息
上一篇文章《测试你的前端代码 - part1(介绍)》中,我介绍了关于前端测试的基本知识,从本文开始将具体介绍测试技术。
单元测试上一节有讨论过,单元测试就是以代码单元为单位进行测试,代码单元可以是一个函数,一个模块,或者一个类。很多人认为大多数测试都应该叫单元测试,其实我的观点还是那句话,无所谓怎么叫,名字叫什么都行。只要你做了足够多的测试,能够保证你部署到线上的生产代码没有问题就可以了。
单元测试是最容易理解、也最容易实现的测试方式。给单元测试一个输入,让它自动执行,将输出结果和预期结果做对比看其是否正确(输入可以是一个函数参数,输出就是函数的返回值)。
在写单元测试的时候,尽量将你的单元测试独立出来,不要几个单元互相引用。养成这样良好的测试习惯。
测试 Calculator 应用第一节中提到过,为了这系列博文,我写了一个计算器应用,后面都会拿它进行测试。理论就讲到这里,一起来看一下 Calculator 应用吧,源代码在这里。主要有两个组件: keypad 和 display ,它们自身都是 React 单元,也都没有引用其他单元,后面会介绍如何对它们进行测试。
(如果你已经看了代码可能已经发现了我没有使用 JSX。因为我不想进行转译。现在 Node 和所有流行的浏览器都已经完全支持 ES6 了,那么作为一个例子来讲,让它直接运行会更好一些。虽然它不能运行在 IE 上,不过也没关系,如果是一个真实的线上项目,我会进行转译的。)
还有一个问题是按键和展示的逻辑问题,必须要有代码来控制当点击按键的时候发生什么。这里的按键包括数字键(如“1”,“5”)和操作键(如“+”,“=”)。按通常的做法,我把组件设计成了展示型组件(键盘)和容器型组件。容器型组件在我的 App 中是唯一包含 state 的组件,它要考虑当发生按键行为的时候 App 内在逻辑的问题。
“calculator”模块处理逻辑问题的代码是一个多带带的模块——calculator。这个模块对于单元测试是很完美的例子。因为它没有对 I/O 和 UI 的依赖。你也应该尽量使你的应用逻辑上保持独立——模块不依赖于 I/O 和 UI。
对于 Web 应用来讲,I/O 是什么?没有文件和数据库的操作?其实不仅仅是这样,还有 Ajax 调用,本地存储,DOM 操作等,对我而言,任何和浏览器 API 有关的都是 I/O 操作。
我是怎么把计算逻辑从 React 组件中分离出来的呢?其实很简单,其内在逻辑是计算,我把他封装到一个模块中就可以了。
这个模块的实现也很容易——它接收一个计算器 state(一个对象)和一个字符(数字或者操作符),返回一个新的计算器 state。如果你用过 Redux,它很像 Redux 的 reducer 模式(如果你没用过 Redux 也没关系)。但是如果一直由上一个 state 获取下一个 state,怎么能回到初始状态呢?这里还有一个模块叫做 initialState,通过它可以初始化计算器。计算器的 state 并不是完全黑盒的,它包含了一个字段叫做 display,可以把你想要展示的 state 显示在计算器应用上。
如果你没有耐心看源代码的话,我们一起来看下这里面最重要的部分,应用中算法的细节其实不重要。
module.exports.initialState = { display: "0", initial: true } module.exports.nextState = (calculatorState, character) => { if (isDigit(character)) { return addDigit(calculatorState, character) } else if (isOperator(character)) { return addOperator(calculatorState, character) } else if (isEqualSign(character)) { return compute(calculatorState) } else { return calculatorState } } //....
再次强调这里的实现细节并不重要,重要的是模块的设计,它暴露出来的函数非常简单——给一个 state,得到下一个 state。
这就是我们在 test-calculator 中所做的事情。那么接下来怎么进行测试呢?使用测试框架,目前比较流行的框架是 Mocha ,我们就用它。不过像 Jest,Jasmine,Tape等框架也都行,随意使用你喜欢的测试框架。
用 Mocha 进行单元测试所有的测试框架都类似,写测试代码调用被测函数,通过测试框架运行他们,其中运行它们的代码通常叫做“runner”。
Mocha runner 叫做 “mocha”,如果你看测试脚本的 package.json,可以看到:
"scripts": { ... "test": "mocha "test/**/test-*.js" && eslint test lib", ... },
它会运行 test 文件夹中所有以 test- 开头的文件,你可以复制我的 repo,npm install 后,运行 npm test 自己试试。
(顺便提一句,把所有测试都放在测试目录,并且测试目录放在 package 的根目录是一个公认的 npm package 约定,如果你不想让人觉得你不专业的话,最好还是遵守这一约定。)
运行它,会得到如下输出:
这里有 14 个测试通过的提示信息,如果没通过,就会有红色提示出现。
我们看下面代码:
const {describe, it} = require("mocha") const {expect} = require("chai") const calculator = require("../../lib/calculator") describe("calculator", function () { const stream = (characters, calculatorState = calculator.initialState) => !characters ? calculatorState : stream(characters.slice(1), calculator.nextState(calculatorState, characters[0])) it("should show initial display correctly", () => { expect(calculator.initialState.display).to.equal("0") }) it("should replace 0 in initialState", () => { expect(stream("4").display).to.equal("4") }) //...
首先引入 mocha 和断言常量 expect,这里只引入我们需要的函数:describe,it 和 expect。接下来引入我们要测试的模块 calculator。
准备开始测试,用 it 函数来表达:
it("should show initial display correctly", () => { expect(calculator.initialState.display).to.equal("0") })
it 函数接收一个字符串(用来表示测试结果)和一个函数(待测函数)。it 测试不能多带带运行,它们必须组成一个测试组。所以如代码中所示,用 describe 函数定义测试组,里面包含了若干个 it 函数。
测试函数中写什么呢?可以写任何想写的东西,在这个例子中我们测试了初始状态所显示的是不是 0。如果我们自己来实现怎么实现呢,比如可以像如下代码:
if (calculator.initialState.display !== "0") throw "failed"
对于这个问题,上面代码也是可以测出来的。但是 expect 包含了很多特性可以使测试变得更简单,比如可以测试数组或者对象是否和一个给定的值相等。这就是单元测试的要点,即运行一个函数,或一组函数,检查其 运行结果 是否和 预期结果 一致。
编写单元可测的代码上面的很简单对吧!其实对于单元测试来讲,难的并不是单元测试本身,而是分离代码的艺术,把代码尽量分离成单元可测的模块。单元可测的代码一般都是不依赖于其他模块、不依赖于 I/O 的代码。这是比较困难的,大多数人都倾向于把逻辑代码、I/O 代码和 UI 代码写到一起。困难是困难,但不是说做不到,有很多技巧可以使用,比如你的代码中有一些验证字段,那么你就可以把验证代码组织到一起形成函数,再对这个验证函数进行测试。
测试代码是运行在 NodeJS 下的!?注意一个重要的事情——单元测试是在 NodeJS 下运行的!而计算器应用是运行在浏览器端的,上面的生产代码都是在 NodeJS 下进行测试的,这也可以吗?
当然可以。因为我们的代码是同构的,它可以运行在浏览器端和 NodeJS 上。如果你的代码没有使用任何 I/O,就是说没有对浏览器做任何的特化处理,那么它就没有理由不能运行在 NodeJS 上。另外,如果你使用了 require,它既可以被本地的 NodeJS 识别,也可以被像 Webpack 一样的打包器识别。你看代码中的 package.json,就可以看到我们就是使用了 Webpack,用 require 进行代码打包:
"scripts": { "build": "webpack && cp public/* dist", ... }
代码中使用 require 来引入 React 或者其他模块,这不论是在 NodeJS 中还是浏览器中都是通用的。
在浏览器中运行单元测试我们还可以使用另一个测试框架,Karma 。使用它可以在浏览器中运行 Mocha 代码,但是这里表达一下我的浅见:单元测试能在 Node 下运行就在 Node 下运行,因为很容易执行和 debug(当然现在在浏览器中执行也很方便)。并且如果代码不需要转译的话,执行的也非常快。
但是我们的代码没有在浏览器中测试确实是个问题,因为我们并不真正地知道代码在浏览器中运行会是什么样子。浏览器中的 JS 执行环境和 NodeJS 环境可能会有微妙的差别。
总结本文中主要介绍了什么:
介绍了如何使用 Mocha (和 Chai)创建单元测试;
介绍了单元测试就是以代码单元为单位进行测试,这个代码单元是独立于其他模块的。
介绍了设计模块时应该独立于其他模块。如果一定要有依赖,那么可以 mock 一个其他模块对本模块进行单元测试,或者进行集成测试。
介绍了我们测试的代码单元应该是同构的,这样就可以在 NodeJS 环境下进行测试了。
介绍了如何写同构代码——没有 I/O操作、使用 require 引入模块、使用 Webpack 来打包模块以使其符合浏览器运行环境。
下文简介下篇文章我们介绍端到端测试,把我们的代码在真实环境(浏览器)中测试。请看下一篇文章《测试你的前端代码 - part3(端到端测试)》。
我最近正在写一本《React.js 小书》,对 React.js 感兴趣的童鞋,欢迎指点。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/8754.html
摘要:单元测试上一节有讨论过,单元测试就是以代码单元为单位进行测试,代码单元可以是一个函数,一个模块,或者一个类。单元测试是最容易理解也最容易实现的测试方式。在写单元测试的时候,尽量将你的单元测试独立出来,不要几个单元互相引用。 showImg(https://segmentfault.com/img/remote/1460000008823416?w=997&h=350); 本文作者:G...
摘要:测试光谱光谱的一端单元测试顾名思义,代码以单元为单位进行测试。这个系列文章整体如下测试你的前端代码单元测试测试你的前端代码端到端测试测试你的前端代码集成测试。 showImg(https://segmentfault.com/img/remote/1460000008812278?w=998&h=354); 本文作者:Gil Tayar 编译:胡子大哈 翻译原文:http://hu...
摘要:测试光谱光谱的一端单元测试顾名思义,代码以单元为单位进行测试。这个系列文章整体如下测试你的前端代码单元测试测试你的前端代码端到端测试测试你的前端代码集成测试。 showImg(https://segmentfault.com/img/remote/1460000008812278?w=998&h=354); 本文作者:Gil Tayar 编译:胡子大哈 翻译原文:http://hu...
摘要:单元测试几乎不会出现不稳定的情况,因为单元测试通常是简单输入,简单输出。链接直达测试你的前端代码集成测试。 本文作者:Gil Tayar 编译:胡子大哈 翻译原文:http://huziketang.com/blog/posts/detail?postId=58d50da37413fc2e8240855c 英文连接:Testing Your Frontend Code: Part ...
摘要:单元测试几乎不会出现不稳定的情况,因为单元测试通常是简单输入,简单输出。链接直达测试你的前端代码集成测试。 本文作者:Gil Tayar 编译:胡子大哈 翻译原文:http://huziketang.com/blog/posts/detail?postId=58d50da37413fc2e8240855c 英文连接:Testing Your Frontend Code: Part ...
阅读 1080·2021-10-14 09:43
阅读 1105·2021-10-11 11:07
阅读 3096·2021-08-18 10:23
阅读 1457·2019-08-29 16:18
阅读 982·2019-08-28 18:21
阅读 1451·2019-08-26 12:12
阅读 3725·2019-08-26 10:11
阅读 2481·2019-08-23 18:04