11月阅读周·编写可测试的JavaScript代码:运行服务器端JavaScript单元测试篇
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十个月。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》、《JavaScript异步编程设计快速响应的网络应用》。
当前阅读周书籍:《编写可测试的JavaScript代码》。
运行服务器端JavaScript单元测试
服务器端JavaScript单元测试的过程,与其他服务器端代码的单元测试过程并没有太大的区别。测试和代码都驻留并运行在同一个主机上,这比客户端单元测试更简单。我将演示如何使用Jasmine进行服务器端JavaScript单元测试工作。
虽然可以编写自己的断言和测试框架,甚至也可以构建自己的飞机,但是不要白费力气做重复工作。如果不喜欢Jasmine,还有很多其他的测试框架可以使用,所以选择一个吧。
Jasmine
安装Jasmine非常简单:
npm install jasmine-node -g
Jasmine给我们提供了一个很好的方式,用命令行运行测试并以不同的方式输出测试结果:即人工或自动化方式。
其思想是,使用Jasmine语法和语义编写单元测试后,向Jasmine命令行指定这些测试所存放的根目录(或任意子目录)。Jasmine将遍历目录树,并运行所有找到的测试。
默认情况下,Jasmine期望测试文件中包含spec字符串,如sum-spec.js——该约定是BDD要件,可以使用--matchall选项进行重写。
如下是测试整数加法的Jasmine代码:
var mySum = require('./mySum');
describe('Sum', function () {
it('Adds Integers! ', function () {
expect(mySum.sum(7, 11)).toEqual(18);
});
});
最后是运行上述测试的代码:
jasmine-node
Finished in 0.009 seconds
1 test, 1 assertion, 0 failures
测试通过了!我们当然知道,Jasmine匹配器(上述示例中的toEqual)等价于YUI Test的断言。另外,我们可以编写自己的自定义匹配器。Jasmine的describe相当于YUI Test的套件(suite),sepsc(it函数)类似于YUI Test的测试用例。Jasmine还支持用于在每个spec之前或之后执行的beforeEach和afterEach函数,其类似于YUI Test的 setUp和tearDown函数的测试。
依赖
对单元测试下的Node.js代码来说,生产其依赖对象是非常苛刻的,因为其依赖对象是通过require函数引入的。要做测试,需要修改源代码(不太好),或者模拟require函数本身(很痛苦)。幸运的是,对于后一种方法,已经有人为我们做了一个非常好的npm包,名称为Mockery。Mockery拦截所有的require调用,可以很容易地插入模拟版本的依赖对象,而不是真实对象。
如下是我们修改后的sum函数,该函数从文件中读取JSON字符串,查找其中的a和b属性,并将其进行相加:
var fs = require('fs');
exports.sum = function (file) {
var data = json.parse(fs.readFileSync(file, 'utf8'));
return data.a + data.b;
};
如下是对sum函数的Jasmine测试代码:
var mySum = require('./mySumFS');
describe('Sum suite File', function () {
it('Adds Integers! ', function () {
expect(mySum.sum('numbers')).toEqual(12);
});
});
numbers文件的内容如下:
{'a':5, 'b':7}
很好——Jasmine运行该测试并通过,一切都很顺利。但我们同时测试的内容太多了——将fs依赖引入并利用,单元测试都是与依赖隔离有关。我们不希望给fs依赖引入任何东西,从而影响我们的测试。当然,这是一个极端的例子,但让我们有逻辑地遵循规则。
使用Mockery作为Jasmine测试的一部分,我们可以处理require调用,将fs模块替换为自己模拟的版本。
Mockery是一个非常好的工具。只是要记住,启用它之后,所有的require调用都会被路由到Mockery!
spy
spy在Jasmine中,对代码注入最有效。从本质上说,它们是一个stub和一个mock,两者之间糅合成一个对象。
所以,我们决定,将sum函数从文件中读取JSON和sum函数从参数列表中读取这两种方式像如下代码这样组合在一起:
exports.sum = function (func, data) {
var data = JSON.parse(func.apply(this, data));
return data.a + data.b;
};
exports.getByFile = function (file) {
var fs = require('fs');
return fs.readFileSync(file, 'utf8');
};
exports.getByParam = function (a, b) {
return json.stringify({ a: a, b: b });
};
我们现在有了广义数据的输入,并且只在一个地方有sum操作。这不仅能让我们添加新操作(减、乘等),而且还可以将数据从数据操作中分离出来。
对此,我们的Jasmine sepc文件(没有使用Mockery做澄清)如下所示:
var mySum = require('./mySumFunc');
describe('Sum suite Functions', function () {
it('Adds By Params! ', function () {
var sum = mySum.sum(mySum.getByParam, [6, 6]);
expect(sum).toEqual(12);
});
it('Adds By File! ', function () {
var sum = mySum.sum(mySum.getByFile, ['string']);
expect(sum).toEqual('testableJavaScript');
});
});
当然,strings文件包含了如下内容:
{ "a": "testable", "b": "JavaScript" }
Jasmine有很多使用技巧,包括可以很容易地禁用测试、模拟setTimer和setInterval,以方便测试、异步测试支持,请访问其主页检查所有的功能。此外,Jasmine不仅仅用于服务器端JavaScript测试!它可以运行在浏览器上用于客户端JavaScript单元测试;然而,在浏览器上运行Jasmine时,提取其测试结果输出不像用YUI Test进行自动化构建一样容易。Jasmine不支持自定义测试报告的导出,而YUI Test,则是开箱即用的支持。
输出
默认情况下,Jasmine将测试结果转储到屏幕上,对开发测试用例来说这很不错,但不适合自动化运行。不过,通过使用–junitreport,可以指示Jasmine将测试结果转存为有广泛支持的JUnit XML输出格式。默认情况下,所有的测试结果都保存在reports目录下,并以测试套件(describe方法的第一个参数)命名。
总结
对JavaScript进行单元测试并不繁琐。有了这些编写和运行单元测试的优秀工具,完成这项工作有很大的灵活性。我们调查了两个工具:YUI Test和Jasmine。对于JavaScript单元测试,两者都提供了全功能的环境,并且在撰写本文时都是活跃的项目,开发人员会定期添加新功能并修复bug。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)