使用Mocha和Chai对JavaScript进行单元测试
本文由Panayiotis«pvgr»Velisarakos,马克。布朗而且汤姆·格列柯.感谢所有SitePoint的同行审必威西盟体育网页登录稿人,让SitePoint的内容成为最好的!
你是否曾经对你的代码做过一些更改,后来发现它导致了其他东西的破坏?
我相信我们大多数人都经历过。这几乎是不可避免的,特别是当您有大量的代码时。一件事依赖于另一件事,然后改变它会破坏另一件事。
但如果这没有发生呢?如果您有一种方法可以知道什么时候由于某些更改而导致某些东西损坏了呢?那太好了。你可以修改你的代码,而不必担心破坏任何东西,你会有更少的错误,你会花更少的时间调试。
这就是单元测试发挥作用的地方。他们将自动为您检测代码中的任何问题。进行更改,运行测试,如果有任何故障,您将立即知道发生了什么,问题在哪里而且正确的行为应该是什么。这完全消除了任何猜测!
在本文中,我将向您展示如何开始对JavaScript代码进行单元测试。本文中展示的示例和技术既可以应用于基于浏览器的代码,也可以应用于Node.js代码。
本教程的代码可从我们的GitHub回购.
什么是单元测试
在测试代码库时,需要取一段代码(通常是一个函数),并验证它在特定情况下的行为是否正确。单元测试是一种结构化的自动化方法。因此,您编写的测试越多,您得到的好处就越大。当您继续开发代码库时,您还将对它有更大的信心。
单元测试的核心思想是在给一个函数给定一组输入时测试它的行为。调用带有特定参数的函数,并检查得到的结果是否正确。
//输入1和10…var result =数学。马克斯(10);/ /……我们应该接收10作为输出if(result !== 10) {throw new Error('Failed');}
实际上,测试有时会更复杂。例如,如果函数发出Ajax请求,则测试需要进行更多设置,但是“给定特定输入,我们期望特定结果”的相同原则仍然适用。
安装工具
在本文中,我们将使用摩卡。它很容易上手,既可以用于基于浏览器的测试,也可以用于Node.js测试,而且它可以很好地与其他测试工具一起使用。
安装Mocha最简单的方法是通过npm(我们也需要安装npm)node . js).如果你不确定如何在你的系统上安装npm或Node,请参考我们的教程:npm -节点包管理器的初学者指南
安装Node后,在项目目录中打开终端或命令行。
- 如果您想在浏览器中测试代码,请运行
NPM安装摩卡茶
- 如果你想测试Node.js代码,除了上面的,运行
NPM安装-g mocha
这将安装包摩卡
而且柴
.摩卡是允许我们运行测试的库吗柴包含一些有用的函数,我们将使用它们来验证测试结果。
在Node.js上测试vs在浏览器中测试
下面的示例设计用于在浏览器中运行测试。如果你想对Node.js应用程序进行单元测试,请遵循以下步骤。
- 对于Node,您不需要测试运行程序文件。
- 要包括Chai,请添加
Var chai = require('chai');
在测试文件的顶部。 - 运行测试
摩卡
命令,而不是打开浏览器。
设置目录结构
您应该将测试放在与主代码文件分开的目录中。这使得构建它们更加容易,例如,如果您想在将来添加其他类型的测试(例如集成测试或功能测试).
JavaScript代码最流行的做法是有一个名为测试/
在项目的根目录中。然后,将每个测试文件放在下面测试/ someModuleTest.js
.您还可以选择在其中使用目录测试/
,但我建议保持简单-如果有必要,你可以随时更改。
设置测试运行程序
为了在浏览器中运行测试,我们需要设置一个简单的HTML页面作为我们的测试页面测试运行器页面。该页面加载Mocha、测试库和我们实际的测试文件。要运行测试,我们只需在浏览器中打开运行程序。
如果你使用的是Node.js,你可以跳过这一步。Node.js单元测试可以使用该命令运行摩卡
,假设您遵循了推荐的目录结构。
下面是我们将用于测试运行程序的代码。我将这个文件保存为testrunner.html
.
<!文档类型超文本标记语言><超文本标记语言><头><标题>摩卡测试标题><链接rel="样式表"href="node_modules /摩卡/ mocha.css">头><身体><divid="摩卡">div><脚本src="node_modules /摩卡/ mocha.js">脚本><脚本src="node_modules /茶/ chai.js">脚本><脚本>摩卡.设置(“bdd”)脚本><!——加载你想测试的代码——><!——在这里加载测试文件——><脚本>摩卡.运行();脚本>身体>超文本标记语言>
测试运行程序中的重要部分是:
- 我们加载Mocha的CSS样式来给我们的测试结果提供漂亮的格式。
- 我们用ID创建一个div
摩卡
.这是插入测试结果的地方。 - 我们装上摩卡和柴。的子文件夹中
node_modules
文件夹,因为我们安装他们通过npm。 - 通过调用
mocha.setup
,我们提供Mocha的测试助手。 - 然后,加载要测试的代码和测试文件。我们现在还什么都没有。
- 最后,我们调用
mocha.run
进行测试确保你调用了这个后加载源文件和测试文件。
基本测试构建块
现在我们可以运行测试了,让我们开始编写一些测试。
我们首先创建一个新文件测试/ arrayTest.js
.这样一个单独的测试文件被称为测试用例.我把它叫做arrayTest.js
因为在这个例子中,我们将测试一些基本的数组功能。
每个测试用例文件都遵循相同的基本模式。首先,你有一个描述
布洛克:
description ('Array', function(){//进一步的测试代码在这里});
描述
用于对单个测试进行分组。第一个参数应该表明我们要测试什么——在本例中,因为我们要测试数组函数,所以我传入了字符串“数组”
.
其次,在描述
,我们会有它
块:
description ('Array', function() {it('应该从空开始',function(){//测试实现在这里});//我们可以在这里有更多的its});
它
用于创建实际测试。的第一个参数它
应该提供一个人类可读的测试描述。例如,我们可以将上面的语句理解为“它应该从空开始”,这是对数组应该如何行为的一个很好的描述。然后将实现测试的代码写入传递给的函数中它
.
所有的Mocha测试都是从这些相同的构建块构建的,它们遵循相同的基本模式。
- 首先,我们使用
描述
要说明我们正在测试什么——例如,“描述数组应该如何工作”。 - 然后,我们用了一些
它
函数来创建各个测试(每个测试)它
应该解释一个特定的行为,例如上面的数组情况中的“it Should start empty”。
编写测试代码
现在我们知道了如何构造测试用例,让我们进入有趣的部分——实现测试。
由于我们正在测试一个数组是否应该以空开始,所以我们需要创建一个数组,然后确保它为空。这个测试的实现非常简单:
Var assert = chai.assert;description ('Array', function() {it('应该从空开始',function() {var arr = [];assert.equal(加勒比海盗。长度,0);});});
注意在第一行,我们设置了断言
变量。这样我们就不用一直打字了chai.assert
无处不在。
在它
函数中,我们创建一个数组并检查它的长度。虽然很简单,但这是测试如何工作的一个很好的例子。
首先,你有一些你要测试的东西,这叫做测试中的系统或SUT.然后,如果有必要,您可以使用SUT做一些事情。在这个测试中,我们什么也没做,因为我们检查的数组开头是空的。
测试中的最后一件事应该是验证断言检查结果。这里,我们用assert.equal
这样做。大多数断言函数以相同的顺序接受参数:首先是“实际”值,然后是“预期”值。
- 的实际值是测试代码的结果,因此在本例中
arr.length
- 的预期价值就是结果应该是。由于数组开始时应该为空,因此该测试中的期望值为
0
Chai还提供了两种不同的断言写作风格,但我们使用的是断言为了让事情变得简单。当您在编写测试方面变得更有经验时,您可能希望使用预计断言相反,他们提供了更多的灵活性。
运行测试
为了运行这个测试,我们需要将它添加到我们之前创建的测试运行器文件中。
如果你正在使用Node.js,你可以跳过这一步,使用命令摩卡
运行测试。您将在终端中看到测试结果。
否则,要将这个测试添加到运行器中,只需添加:
< script src = "测试/ arrayTest.js " > < /脚本>
下图:
<!——在这里加载测试文件——>
一旦您添加了脚本,您就可以在您选择的浏览器中加载测试运行程序页面。
测试结果
运行测试时,测试结果将如下所示:
注意我们输入的描述
而且它
函数显示在输出中-测试在描述下分组。注意,也可以嵌套描述
块,以创建进一步的子分组。
让我们来看看失败的测试是什么样的。
在测试的那一行写着:
assert.equal(加勒比海盗。长度,0);
更换号码0
与1
.这将导致测试失败,因为数组的长度不再匹配期望的值。
如果再次运行测试,您将看到失败的测试显示为红色,并显示出错误所在的描述。
测试的好处之一是,它们可以帮助您更快地找到错误,但是这个错误在这方面并不是很有帮助。不过我们可以解决这个问题。
大多数断言函数也可以接受一个可选参数消息
参数。这是断言失败时显示的消息。使用这个参数可以使错误消息更容易理解,这是个好主意。
我们可以像这样在断言中添加一条消息:
assert.equal(加勒比海盗。length, 1, '数组长度不是0');
如果重新运行测试,将显示自定义消息而不是默认消息。
让我们把断言切换回原来的方式——替换1
与0
,并再次运行测试以确保它们通过。
把它放在一起
到目前为止,我们已经看了相当简单的例子。让我们把学到的知识应用到实践中,看看如何测试一段更现实的代码。
下面是一个函数,它将CSS类添加到元素中。这应该放到一个新文件里js / className.js
.
函数addClass(el, newClass) {if(el. classname . indexof (newClass) === -1) {el。className += newClass;}}
为了让它更有趣一点,我让它只在一个元素的类中不存在时才添加一个新类 在最好的情况下,我们会为这个函数编写测试之前我们编写代码。但测试驱动开发是一个复杂的主题,现在我们只想专注于编写测试。 首先,让我们回顾一下单元测试背后的基本思想:我们为函数提供某些输入,然后验证函数的行为符合预期。那么这个函数的输入和行为是什么呢? 给定一个元素和一个类名: 让我们把这些用例转换成两个测试。在 我们稍微改变了措辞,变成了测试中使用的“它应该做X”形式。这意味着它读起来更好一些,但本质上仍然是我们上面列出的人类可读的形式。从想法到测试通常并不比这困难多少。 但是等等,测试函数在哪里?当我们省略第二个参数时 让我们继续实现第一个测试。 在这个测试中,我们创建了一个 同样,我们从最初的想法出发——给定一个元素和一个类名,应该将其添加到类列表中——并以相当直接的方式将其转换为代码。 虽然这个函数设计用于处理DOM元素,但这里我们使用的是纯JS对象。有时,我们可以以这种方式利用JavaScript的动态特性来简化测试。如果我们不这样做,我们将需要创建一个实际的元素,这将使我们的测试代码复杂化。作为一个额外的好处,因为我们不使用DOM,如果我们愿意,我们也可以在Node.js中运行这个测试。 要在浏览器中运行测试,您需要添加 您现在应该看到一个测试通过,另一个测试显示为挂起,如下所示的CodePen。注意,为了使代码在CodePen环境中工作,代码与示例略有不同。 看钢笔使用Mocha进行单元测试(1)由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)CodePen. 接下来,让我们实现第二个测试… 经常运行测试是一个好习惯,所以让我们检查一下如果现在运行测试会发生什么。不出所料,他们应该会通过。 这是实现了第二个测试的另一个CodePen。 看钢笔使用Mocha进行单元测试(2)由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)CodePen. 但是等一下!其实我骗了你一点。这个函数还有第三种性质,我们还没有考虑。该函数中也有一个错误——相当严重的错误。这只是一个三条线的函数,但是你们注意到了吗? 让我们再为第三个行为编写一个测试,作为奖励暴露bug。 这次测试失败了。您可以在下面的CodePen中看到它的实际操作。这里的问题很简单:元素中的CSS类名应该用空格分隔。但是,我们目前的实现 看钢笔使用Mocha进行单元测试(3)由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)CodePen. 让我们修复函数并使测试通过。 这是一个带有固定函数和通过测试的最终CodePen。 看钢笔使用Mocha进行单元测试(4)由Si必威西盟体育网页登录tePoint (@必威西盟体育网页登录SitePoint)CodePen. 在Node中,内容只对同一文件中的其他内容可见。作为 代码本质上保持不变,但结构略有不同: 如你所见,测试通过了。 如您所见,测试并不复杂或困难。就像编写JavaScript应用程序的其他方面一样,您有一些重复的基本模式。一旦你熟悉了它们,你就可以一次又一次地使用它们。 但这只是皮毛。关于单元测试还有很多需要学习的地方。 如果你想继续学习,我创建了一个免费JavaScript单元测试快速入门系列.如果你觉得这篇文章很有用,你一定要看点击这里查看. 或者,如果视频更适合你的风格,你可能会对SitePoint Premium的课程感兴趣:必威西盟体育网页登录Node.js中的测试驱动开发.类名称
财产——谁想看
类名称
属性不包含类名,则应将其添加。类名称
属性不包含类名,则不应添加。测验
目录,创建一个新文件classNameTest.js
并添加以下内容:description ('addClass', function() {it('应该向元素添加类');它('不应该添加一个已经存在的类');});
它
,摩卡将这些测试标记为等待在测试结果中。这是一种设置大量测试的方便方式——有点像你打算写的待办事项列表。description ('addClass', function() {it('应该向元素中添加类',function() {var element = {className: "};addClass(元素,测试类);assert.equal(元素。类名称,'test-class'); }); it('should not add a class which already exists'); });
元素
变量,并将其作为参数传递给addClass
函数,以及一个字符串测试类
(添加的新类)。然后,使用断言检查类是否包含在值中。在浏览器中运行测试
className.js
而且classNameTest.js
致跑步者:<!——加载你想测试的代码——>< script src="js/className.js"> < script src="test/classNameTest.js"> .js
它('不应该添加一个已经存在的类',function() {var element = {className: 'exists'};addClass(元素,“存在”);var numClasses = element.className。分割(' '). length;assert.equal(numClasses, 1); });
它('应该在现有类之后追加新类',function() {var element = {className: 'exists'};addClass(元素,“新阶层”);var classes = element.className。分割(' ');assert.equal(classes[1], 'new-class'); });
addClass
不加空格!函数addClass(el, newClass) {if(el. classname . indexof (newClass) !== -1){返回;如果(el}。== "){//确保类名之间用空格分隔newClass = '' + newClass;} el。className += newClass;}
在节点上运行测试
className.js
而且classNameTest.js
都在不同的文件里,我们得想办法把其中一个暴露给另一个。做到这一点的标准方法是使用module.exports
.如果你需要复习,你可以在这里阅读所有相关内容:理解模块。在Node.js中exports和exports// className.js模块。exports = {addClass: function(el, newClass) {if(el. classname . indexof (newClass) !== -1){返回;如果(el}。== "){//确保类名之间用空格分隔newClass = '' + newClass;} el。className += newClass;}}
// classNameTest.js var chai = require('chai');Var assert = chai.assert;var className = require('../js/className.js');var addClass = className.addClass;//文件的其余部分保持不变describe('addClass', function(){…});
接下来是什么?