译文
当我们写单元测试时一个最大的绊脚石是当你面对的代码过于复杂。
在真实的项目中,我们的代码经常要做各种导致我们测试很难进行的事情。Ajax请求,timer,日期,跨浏览器特性…或者如果你使用Nodejs,则面对数据库,网络,文件操作等。
所有这些事情之所以不容易测试是因为你无法轻易用代码控制它们。如果你使用Ajax,你需要一个服务端来响应请求,这样才能让你的测试项通过。如果你使用setTimeout
,你的测试项不得不等待它。如果是数据库或网络,也类似–你需要一个包含正确数据的数据库,或一个网络服务。
真实世界不像那些测试教程里看起来的那样简单。但你知道有一个解决方案么?
By using Sinon, we can make testing non-trivial code trivial! (译者:这个口号不太好翻译,non-trivial)
让我们看看该怎么做。
是什么让Sinon如此重要?
简单的说,Sinon允许你去替换代码中复杂的部分,以此来简化你的测试代码。
当我们测试某部分代码时,你不希望受到其它部分的影响。如果有外部因素影响测试,那么测试项将变得非常复杂且不稳定。
如果你想测试一个使用了ajax的代码,你该怎么做?你需要跑一个服务端,并保证该服务端返回指定的响应数据来支撑你的测试项。这很难完成也让运行测试很麻烦。
那如果你的代码依赖时间呢?假如它需要等待一秒钟才执行。怎么办?你需要在你的测试项中使用setTimeout
,但这会让测试变得缓慢。想像一下,如果间隔时间很久,例如五分钟。我想你不会希望每次跑测试项都等待五分钟吧。
如果使用Sinon,我们可以搞定这些问题(甚至更多),并减少复杂度。
Sinon是怎么工作的?
Sinon通过允许我们简单的创建test-doubles
从而帮助我们减少测试项编写的复杂度。
正如它名字一样,Test-doubles作用是在测试中替换某部分代码。上面提到的ajax的例子中,不需要创建服务端,我们可以使用test-doubles
替换掉Ajax调用。在timer例子中,我们可以使用test-doubles
来控制时间。
听起来可能很复杂,但基本思想很简单。基于javascript的动态性,我们可以替换任何函数。Test-doubles只是在这个思想的基础上走的更远了一些。使用Sinon,我们可以使用test-doubles替换任何javascript函数,并提供很多方便测试的配置。
Sinon中test-doubles
分三类:
- Spies,提供了函数调用的信息,但不会改变其行为(译者注:类似动态代理)
- Stubs,类似Spies,但是是完全替换目标函数。这可以让你随心所欲的控制函数–抛异常,返回指定结果等
- Mocks,提供了替换整个对象的能力
此外,Sinon还提供了其他的辅助功能,本文不包含下面的范围:
- Fake timers,用来控制时间,例如触发一个
setTimeout
- Fake XMLHttpResquest and server,可以用来伪造Ajax请求和响应
基于这些功能,Sinon可以让你解决测试中遇到的由外部依赖带来的所有复杂问题。如果你学会了Sinon提供的这些技巧,你几乎不需要其它别的工具了。
安装Sinon
开始之前,我们需要安装Sinon
Nodejs
- 使用
npm install sinon
安装sinon - 在测试项中引入sinon:
var sinon = require('sinon');
浏览器
入门指南
sinon包含许多功能,但它们多数都存在关系。你只需要掌握一部分,就会了解剩余部分。这让sinon很容易使用,只需要你了解了基本用法并知道它们之间的差别。
只要我们的代码调用了一个不容易控制的函数,我们通常就需要sinon。
对于Ajax,它可能是$.get
或者XMLHttpResquest
。对于timer,它可能是setTimeout
。对于数据库,它可能是mongodb.findOne
。
为了方便我们讨论,后面我将成这类函数为依赖方。我们测试的目标函数依赖其它函数的返回结果。
最常见的使用sinon方式是使用test-doubles替换掉问题依赖方。
- 当测试Ajax时,我们使用
test-doubles
替换XMLHttpResquest
来伪造ajax请求 - 当测试timer时,我们伪造替换
setTimeout
- 当测试数据库时,我们使用
test-doubles
来替换mongodb.findOne
来直接返回伪造数据
让我们写点代码吧。
Spies
Spies很简单,但其它很多功能依赖它。
spies的主要用法是收集函数的调用信息。你可以用来验证一些事儿,例如函数是否被调用。
var spy = sinon.spy();
//我们可以像调用函数一样调用spy
spy('Hello', 'World');
//我们可以得到调用信息
console.log(spy.firstCall.args); //output: ['Hello', 'World']
</