前端开发者丨前端开发
测试你的前端代码– part4( 集成测试) 上一篇文章《 测试你的前端代码– part3( 端到端测试)》 中, 我介绍了关于端到端测试的基本知识, 从本文介绍集成测试( Integration Testing)。
集成测试我们已经看过了“ 测试光谱” 中的两种测试: 单元测试和端到端测试。
实际工作中的测试经常是介于这两种测试之间的, 包括我在内的大多数人通常把这种测试叫做集成测试。
关于术语和许多 TDD 爱好者聊过以后, 我了解了他们对“ 集成测试” 这个词有一些不同的理解。
他们认为集成测试是测试代码边界, 即代码对外的接口部分。
比如他们代码中有 Ajax, localStorage 或者 IndexedDB 操作, 那其代码就不能做单元测试, 这时他们会把这些代码打包成接口, 然后在做单元测试的时候 mock 这些接口。
当真正测试这些接口的时候才称作“ 集成测试”。
从这个角度来说,“ 集成测试” 就是在纯的单元测试以外, 测试与外部“ 真实世界” 相关的代码。
而我和其他一些人则倾向于认为“ 集成测试” 是将两个或多个单元测试综合起来进行测试的一种方法。
通过接口把与外部相关的代码打包到一起, 再 mock, 只是其中的一种实现方式。
我的观点里, 决定是否使用真实场景的 Ajax 或者其他 I / O 操作进行集成测试( 即不使用 mock), 取决于是否能够保证测试速度足够快, 并且能够稳定测试( 不发生 flaky 的情况)。
如果可以确定这样的话, 那尽管用真实场景进行集成测试就好了。
不过如果很慢或者发生不稳定测试的情况, 那还是用 mock 会好一些。
在我们的例子中, 计算器应用唯一的真实 I / O 就是操作 DOM 了, 没有 Ajax 调用, 所以不存在上面的问题。
mock DOM这就引出了一个问题: 在集成测试中是否需要 mock DOM? 重新思考一下上面我说的标准, 使用真实 DOM 是否会使测试变慢呢, 答案是会的。
使用真实 DOM 意味着要用浏览器, 用浏览器意味着测试速度变慢, 测试变的不稳定。
那么是不是要么只能尽量把操作 DOM 的代码分离出来, 要么只能使用端到端测试了呢? 其实这两种方法都不好。
还有另一种解决方案: jsdom。
一个非常棒的包, 用它自己的话说: 这是在 NodeJS 中实现的 DOM。
它确实比较好用, 可以运行在 node 环境下。
使用 JSDom, 你可以不把 DOM 当做 I / O 操作。
这一点非常重要, 因为要把 DOM 操作从前端代码中分离出来非常困难( 实际工作中几乎不可能完全分离)。
我猜 JSDom 的诞生就是因为这个原因: 使得在 node 中也可以运行前端测试。
我们来看一下它的工作原理, 和往常一样, 需要有初始化代码和测试代码。
这次我们先看测试代码。
不过正式看代码之前请先接受我的歉意。
歉意这一部分是这个测试系列文章中唯一使用指定框架的部分, 这部分使用的框架是 React。
选择 React 并不是因为它是最好的框架, 我坚定地认为没有所谓最好的框架, 我甚至认为对于指定的场景也没有最好的框架。
我相信的是对于个人来讲, 只有最合适, 用着最顺手的框架。
而我使用着最顺手的框架就是 React, 所以接下来的代码都是 React 代码。
但是这里依然说明一下, 前端集成测试的 jsdom 解决方案可以适用于所有的主流框架。
ok, 现在回到正题。
使用 Jsdomconst React = require(‘react’) const e = React.createElement
const ReactDom = require(‘react-dom’) const CalculatorApp = require(‘../../lib/calculator-app’)…describe(‘calculator app component’, function () { …it(‘should work’, function () {
ReactDom.render(e(CalculatorApp),document.getElementById(‘container’)) constdisplayElement=document.querySelector(‘.display’) expect(displayElement.textContent).to.equal(‘0’) 1234567891011121314
constReact=require(‘react’) conste=React.createElement
constReactDom=require(‘react-dom’) constCalculatorApp=require(‘../../lib/calculator-app’)…describe(‘calculator app component’,function(){…it(‘should work’,function(){
ReactDom.render(e(CalculatorApp),document.getElementById(‘container’)) constdisplayElement=document.querySelector(‘.display’) expect(displayElement.textContent).to.equal(‘0’) 注意看第10– 14行, 首先render了CalculatorApp组件, 这个操作同时也render了Display和Keypad。
第12和14行测试了DOM中计算器的显示是否是0( 初始化状态下)。
上面的代码是可以运行在node下的, 注意到里面用的是 document。
我第一次使用它的时候特别惊讶。
全局变量 document 是一个浏览器变量, 竟然可以使用在NodeJS中。
在这简单的几行代码背后有着大量的代码支撑着, 这些jsdom代码几乎是完美地实现了浏览器的功能。
所以这里我要感谢DomenicDenicola,ElijahInsua和为这个工具包做过贡献的人们。
第10行中也使用了 document( 调用ReactDom来渲染组件), 在ReactDom经常会使用它。
那么在哪里创建的这些全局变量呢? 在测试中创建的, 见下面代码: before(function(){
global.document = jsdom(`
`) global.window = document.defaultView
}) after(function(){
delete global.windowdelete global.document
}) 123456789before(function(){
global.document = jsdom(` < ! doctype html > < html > < body > < div id = “container” / > < / div > < / body > < / html > `) global.window = document.defaultView
}) after(function(){
delete global.windowdelete global.document
}) 代码中创建了一个简单的 document, 把我们的组件挂在一个简易div上。
同时还创建了一个 window, 其实我们并不需要它, 但是React需要。
最后在after中清理全局变量。
document 和 window 一定要设置成全局的吗? 滥用全局变量不论理论和实践的角度都不是个好习惯。
如果它们是全局的, 那这个集成测试就不能和其他的集成测试并行运行( 这里对ava的用户表示抱歉), 因为它们会互相覆写全局变量, 导致结果错误。
然而, 它们必须要设置成全局的, React和ReactDOM要求 document 和 window 是全局的, 不接受把他们以参数的形式传递。
或许等Reactfiber出来就可以了? 也许吧, 不过现在我们还必须要把 document 和 window 设置成全局的。
事件处理剩下的测试代码怎么写呢, 看下面代码:
ReactDom.render(e(CalculatorApp),document.getElementById(‘container’)) constdisplayElement=document.querySelector(‘.display’) expect(displayElement.textContent).to.equal(‘0’) constdigit4Element=document.querySelector(‘.digit-4’) constdigit2Element=document.querySelector(‘.digit-2’) constoperatorMultiply=document.querySelector(‘.operator-multiply’) constoperatorEquals=document.querySelector(‘.operator-equals’) digit4Element.click() digit2Element.click() operatorMultiply.click() digit2Element.click() operatorEquals.click() expect(displayElement.textContent).to.equal(’84’) 123456789101112131415161718ReactDom.render(e(CalculatorApp),document.getElementById(‘container’)) constdisplayElement=document.querySelector(‘.display’) expect(displayElement.textContent).to.equal(‘0’) constdigit4Element=document.querySelector(‘.digit-4’) constdigit2Element=document.querySelector(‘.digit-2’) constoperatorMultiply=document.querySelector(‘.operator-multiply’) constoperatorEquals=document.querySelector(‘.operator-equals’) digit4Element.click() digit2Element.click() operatorMultiply.click() digit2Element.click() operatorEquals.click() expect(displayElement.textContent).to.equal(’84’) 测试中主要实现的是用户点击“ 42*2= ”,结果应该是输出“ 84”。
这里获取element使用的是广为人知的querySelector函数, 然后调用click点击。
还可以创建事件, 然后手动调度, 见下面代码:
varev=newEvent(“keyup”,…);
document.dispatchEvent(ev);
12
varev=newEvent(“keyup”,…);
document.dispatchEvent(ev);
这里有内置的click函数, 所以我们直接使用就好了。
就是这么简单! 机智的你可能已经发现了, 这个测试和前面的端到端测试其实是一样的。
但是注意这个测试要快10倍以上, 并且实际上它是同步的, 代码也更容易写, 可读性也更好。
但是如果都一样的话, 那需要继承测试干嘛? 因为这是个示例项目嘛, 并不是实际项目。
这个项目里面只有两个组件, 所以端到端测试和继承测试是一样的。
如果是在实际项目中, 端到端测试可能包含了上百个单元, 而继承测试只包含少量单元, 比如包含10个单元。
所以实际项目中只有几个端到端测试, 而可能包含了上百个继承测试。
评论前必须登录!
注册