内容简介:原文:作者:jstweetster
原文: Unit Testing Beginner's Guide - Part 1 - Testing Functions
作者:jstweetster
你已经决定开始对代码进行单元测试,但是不知道从哪里开始或者围绕该代码的最佳实践是什么。
在这个系列中,我计划在从基本原理开始,并使用您可能直到现在才知道的高级技术,在单元测试的领域中引导你。
开始之前,你需要安装如下依赖:
- NodeJS - 你也许已经安装啦
- Jest - 这是我们将会使用的单元测试库
请注意下面事项:
- 确保你已经安装了NodeJS:
node -v
。确保版本号 >= 6.x,如果不是请安装 - 创建名为
unit-testing-functions
目录 - 切换到该目录
cd unit-testing-functions
并且初始化该JavaScript项目:npm init--yes
- 现在在该目录你应该有一个
package.json
文件 - 安装Jest:
nom i jest --save-dev
- 你现在可以验证Jest是否安装成功通过运行:
./node_modules/.bin/jest -v
一切准备就绪,我们开始进入单元测试啦。
我们将从简单的函数开始,并且,随着我们进行一系列的单元测试,我们将会继续探索更复杂的数据结构和设置。
我们将会测试的代码
让我们开始定义一个简单的函数:
在 unit-testing-functions
项目中创建一个 sum.js
文件。定义如下函数:
module.export = function sum (a, b) { return a + b; }
这个函数我们将会进行测试。 单元测试背后的想法是提供尽可能多的输入类型,以涵盖所有条件分支。
现在,没有任何条件分支,但我们应该改变我们对函数的输入,以确保它继续正确运行,即使代码在将来被更改。
理解测试文件
每一个你写的代码文件都应该有一个对应的 Spec
文件,它通常在代码文件旁边。例如: touch sum.spec.js
或者手动创建。
在spec文件中,我们将会写测试方法
Jest和其他测试框架将测试组织到测试用例中,为了简单管理和记录,每个测试用例包含多个单独的测试。
让我们添加我们的第一个测试(在 sum.spec.js
中):
const sum = require('./sum.js'); describe('sum suite', function () { test('should add 2 positive numbers together and return the result', function () { expect(sum(1, 2)).toBe(3); }); });
如果这看起来令人生畏或不清楚,请不要担心,它会在少数情况下有意义。
那么,这里发生了什么?
const sum = require('./sum.js');
我们引入要测试的函数。我们使用 module.exports
从模块中暴露函数,并且使用 require
引入到要测试的文件中。这是因为Jest在能识别这些结构的NodeJS上运行我们的测试。
此代码不是在浏览器中运行,不使用像Webpack这样的模块打包器,但另一篇文章将会介绍。
接下来,我们定义了测试单元,它将包含所有有关 sum
函数的测试方法。
describe('sum suite', function () { // define here the individual tests })
最后我们添加我们第一个测试(我们将会在上面的测试单元中添加更多的测试):
test('should add 2 positive numbers together and return the result', function () { expect(sum(1, 2)).toBe(3); });
下面的代码可能也不是很清楚
expect(sum(1, 2)).toBe(3)
这是任何单元测试的构建块,被称作为“断言(assertion)”。断言基本上是一种表达对事物应该如何表现的期望的方式。在我们的示例中,我们期望调用 sum(1, 2)
应该返回的结果是 3
。
toBe
被称作为“匹配器(matcher)”。在 Jest 里有很多的匹配器,每一个匹配器都有助于验证一个特定的方面:比如测试对象是否相等等。
那么, expect
从哪里来的呢?我们也没有从任何地方导入进来。
事实证明,Jest作为全局变量,提供了 describe
、 test
、 expect
和一些其他函数,因此你无需导入他们。你可以到 官方文档 查看完整列表
该到我们运行我们的第一个单元测试啦
你可以在我们的项目根目录中直接调用Jest来运行单元测试 ./node_modules/.bin/jest
更好,更跨平台的方式是定义一个NPM脚本来运行这些测试。例如,打开 package.json
文件并且编辑下面的部分:
"scripts": { "test": "echo \"Error: no test specified\" && exit 1" }
改为:
"scripts": { "test": "jest" }
运行 npm run test
,你将会看到成功的输出:
➜ unit-testing-functions npm run test > unit-testing-functions@1.0.0 test /Users/zouyuwei/code/web/_SELF/unit-testing-functions > jest PASS __tests__/sum.spec.js sum suite ✓ should add 2 positive numbers together and return the result (8ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.74s Ran all test suites.
很好,你的第一个测试通过了!
现在,快进几周或者几个月,假设你的同事正在研究 sum
函数并且决定更改它的实现方法如下所示:
module.exports = function sum(a, b) { return a - b; }
请更改它(这仅仅只是为了演示)。现在你的同事在提交这些改变之前运行单元测试。输出将会如下所示:
➜ unit-testing-functions npm run test > unit-testing-functions@1.0.0 test /Users/zouyuwei/code/web/_SELF/unit-testing-functions > jest FAIL __tests__/sum.spec.js sum suite ✕ should add 2 positive numbers together and return the result (30ms) ● sum suite › should add 2 positive numbers together and return the result expect(received).toBe(expected) // Object.is equality Expected: 3 Received: -1 2 | describe('sum suite', function () { 3 | test('should add 2 positive numbers together and return the result', function () { > 4 | expect(sum(1, 2)).toBe(3); | ^ 5 | }); 6 | }); at Object.toBe (__tests__/sum.spec.js:4:23) Test Suites: 1 failed, 1 total Tests: 1 failed, 1 total Snapshots: 0 total Time: 3.303s Ran all test suites.
通过查看上面的输出信息,能够很容易的得出结论:在 __tests__/sum.spec.js
文件的第四行出错了,如跟踪堆栈所示 at Object.toBe (__tests__/sum.spec.js:4:23)
。因此可以得出结论 expect(sum(1,2)).toBe(3);
这一行出错了。通过检查控制台的输出,我们能够看到,期望的值是 ‘3’,而接收的值是 ‘-1’。
因此,单元测试既是防止回归的一种方式,也是一种活文档。
最后,请更改 a - b
为 a + b
。
扩大单元测试覆盖率
我们进行了第一次测试,并且它涵盖了sum函数中的所有分支,有许多场景我们还没有测试过。
考虑一下测试中的功能,不仅要考虑今天的实现,还要考虑它如何随着时间的推移而发展。我们希望捕获函数停止工作的情况,即使有人修改了它的实现,并添加了额外的检查和分支。
因此,让我们通过创建额外的单元测试来扩展测试范围。在 sum.spec.js
文件中添加如下代码:
const sum = require('../sum.js'); describe('sum suite', function () { test('should add 2 positive numbers together and return the result', function () { expect(sum(1, 2)).toBe(3); }); test('Should add 2 negative numbers together and return the result', function() { expect(sum(-1, -2)).toBe(-3); }); test('Should add 1 positive and 1 negative numbers together and return the result', function() { expect(sum(-1, 2)).toBe(1); }); test('Should add 1 positive and 0 together and return the result', function() { expect(sum(0, 2)).toBe(2); }); test('Should add 1 negative and 0 together and return the result', function() { expect(sum(0, -2)).toBe(-2); }); });
除了最初的测试用例之外,我们刚添加了4个测试用例。注意我们如何改变函数的输入以及我们如何尝试击中边缘情况(例如,通过添加0)。
运行单元测试,你将会看到:
➜ unit-testing-functions npm run test > unit-testing-functions@1.0.0 test /Users/zouyuwei/code/web/_SELF/unit-testing-functions > jest PASS __tests__/sum.spec.js sum suite ✓ should add 2 positive numbers together and return the result (7ms) ✓ Should add 2 negative numbers together and return the result (1ms) ✓ Should add 1 positive and 1 negative numbers together and return the result (1ms) ✓ Should add 1 positive and 0 together and return the result (1ms) ✓ Should add 1 negative and 0 together and return the result Test Suites: 1 passed, 1 total Tests: 5 passed, 5 total Snapshots: 0 total Time: 2.575s Ran all test suites.
处理单元测试功能中的异常
虽然我们在扩展单元测试覆盖率方面做得很好,但测试可以为我们做更多的事情。
如果我们认为我们还没有涉及的其他方案真的很好,你能想出一些目前代码处理不当的方法吗?
如何传递除数字以外的输入?
填写如下测试用例:
test('Should raise an error if one of the inputs is not a number', function() { expect(() => sum('0', -2)).toThrowError('Both arguments must be numbers'); });
首先,我们用一个匿名函数包装我们要测试的代码: () => sum('0', -2)
。
这是必需的,因为在测试一段代码时抛出的任何未捕获的异常都会触发测试失败。
在这个例子当中,当参数不是数据的时候,我们期望 sum
函数抛出异常,但是我们不希望这是被认为是测试失败的: 相反的,这是预期的行为,应该被认为是通过的测试用例。
因此,我们将其包装在一个匿名函数中,并引入一个新的匹配器: toThrowError
。
运行测试,观察如下:
FAIL __tests__/sum.spec.js sum suite ✕ Should raise an error if one of the inputs is not a number (18ms) ● sum suite › Should raise an error if one of the inputs is not a number expect(function).toThrowError(string) Expected the function to throw an error matching: "Both arguments must be numbers" But it didn't throw anything. 22 | 23 | test('Should raise an error if one of the inputs is not a number', function() { > 24 | expect(() => sum("0", -2)).toThrowError('Both arguments must be numbers'); | ^ 25 | }); 26 | }); at Object.toThrowError (__tests__/sum.spec.js:24:32)
此时要抵制修改测试代码的诱惑。
这个测试很清楚的说明了该函数实现上的问题:
- 它期望函数返回
to throw an error matching:“Both arguments must be numbers
。实际上它没有抛出任何异常。 - 要查看它正在讨论的函数以及用于调用它的参数,请遵循堆栈跟踪:
at Object.toThrowError (__tests__/sum.spec.js:24:32)
。在指定的行和列上,您可以看到对应的断言:expect(() => sum("0", -2)).toThrowError('Both arguments must be numbers');
。
所以我们的单元测试刚刚发现了一个bug,该是修复的时候了!
更改 sum.js
的代码,以考虑错误的输入类型,并在这种情况下抛出适当的异常:
module.exports = function sum (a, b) { if (typeof a !== 'number' || typeof b !== 'number') { throw new Error('Both arguments must be numbers'); } return a + b; }
再次运行测试脚本,观察所有的测试都通过了。好样的!
请注意:我们首先添加了一个单元测试,然后再进入并添加代码,这表明 sum
函数在某些条件下无法正常运行。
我们看到了测试FAILING,我们添加了修复bug的代码并观看了测试PASSING。
在开发新代码/修复现有代码时,您应始终遵循此过程。
提高生产力
到目前为止,您可能已经注意到,每次添加代码或更新单元测试时,我们都必须不断重新运行单元测试。
这很快就会变得烦人并妨碍实际的开发工作流程。幸运的是,大多数测试运行程序允许设置文件监视模式,当磁盘上的文件发生更改时,它会重新运行单元测试。
修改 package.json
文件,将 “script” 部分改为:
"script": { "test": "jest --watch" }
运行单元测试: npm run test
观察现在测试运行器没有退出而是等待命令。
改变 sum.js
或者 sum.spec.js
文件,会看到测试正在重新运行。
单元测试函数 - 最好实践总结
.spec test test
这就是我们对单元测试的介绍。
扫码关注w3ctech微信公众号
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- BabySploit:一个针对初学者的渗透测试框架
- 【译】Jest 初学者教程:JavaScript 测试入门
- [Jest]单元测试初学者指南 - 第三部分 - 模拟Http请求和访问文件
- [Jest]单元测试初学者指南 - 第二部分 - Spying and fake timers
- Pentest-Tools-Framework:一款专为渗透测试初学者设计的强大框架
- 区块链初学者指南
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
网络经济的十种策略
(美)凯文・凯利 / 肖华敬/任平 / 广州出版社 / 2000-06 / 26.00元
全书介绍网络经济的十个新游戏规则,分别是:蜜蜂比狮子重要;级数比加法重要;普及比稀有重要;免费比利润重要;网络比公司重要;造山比登山重要;空间比场所重要;流动比平衡重要;关系比产能重要;机会比效率重要!一起来看看 《网络经济的十种策略》 这本书的介绍吧!
URL 编码/解码
URL 编码/解码
HSV CMYK 转换工具
HSV CMYK互换工具