使用Mocha和Chai对JavaScript进行单元测试

这篇文章由Panayiotis«pvgr»VelisarakosMark BrownTom Greco进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!

您是否曾经对代码进行过更改,后来发现它导致了其他问题?

我敢肯定我们大多数人都有。 这几乎是不可避免的,尤其是当您拥有大量代码时。 一件事依赖于另一件事,然后更改它会破坏其他东西。

但是,如果那没有发生怎么办? 如果您有办法知道何时由于某些更改而发生故障,该怎么办? 那就太好了。 您可以修改代码而不必担心会破坏任何东西,错误更少,调试时间也更少。

这就是单元测试的亮点。 他们将自动为您检测代码中的任何问题。 进行更改,运行测试,如果有任何问题,您将立即知道发生了什么,问题在哪里以及正确的行为是什么。 这完全消除了任何猜测!

在本文中,我将向您展示如何开始对JavaScript代码进行单元测试。 本文中显示的示例和技术可以应用于基于浏览器的代码和Node.js代码。

可从我们的GitHub存储库中获得本教程的代码。

什么是单元测试

在测试代​​码库时,您需要一段代码(通常是一个函数),并验证它在特定情况下的行为是否正确。 单元测试是一种结构化且自动化的方式。 结果,编写的测试越多,获得的好处就越大。 随着您继续开发代码库,您还将对代码库有更大的信心。

单元测试的核心思想是在给函数一组特定输入时测试其行为。 您调用带有某些参数的函数,并检查是否获得正确的结果。

// Given 1 and 10 as inputs...
var result = Math.max(1, 10);

// ...we should receive 10 as the output
if(result !== 10) {
  throw new Error('Failed');
}

实际上,测试有时会更复杂。 例如,如果您的函数发出Ajax请求,则测试需要进行更多设置,但是“给定某些输入,我们期望一个特定的结果”这一原则仍然适用。

设置工具

对于本文,我们将使用Mocha。 它很容易上手,可用于基于浏览器的测试和Node.js测试,并且可以与其他测试工具很好地配合使用。

安装Mocha的最简单方法是通过npm(为此,我们还需要安装Node.js )。 如果不确定如何在系统上安装npm或Node,请查阅我们的教程: npm入门指南-Node Package Manager

安装Node后,在项目目录中打开终端或命令行。

  • 如果要在浏览器中测试代码,请运行npm install mocha chai --save-dev
  • 如果要测试Node.js代码,除上述内容外,请运行npm install -g mocha

这将安装软件包mochachaiMocha是允许我们运行测试的库, Chai包含一些有用的函数,我们将使用它们来验证测试结果。

在Node.js上进行测试与在浏览器中进行测试

下面的示例旨在在浏览器中运行测试时起作用。 如果要对Node.js应用程序进行单元测试,请遵循以下步骤。

  • 对于Node,您不需要测试运行器文件。
  • 要包含Chai,请添加var chai = require('chai'); 在测试文件的顶部。
  • 使用mocha命令运行测试,而不是打开浏览器。

设置目录结构

您应该将测试与主代码文件放在单独的目录中。 例如,如果您将来想添加其他类型的测试(例如集成测试功能测试 ),这将使它们的结构更加容易。

JavaScript代码最流行的做法是在项目的根目录中有一个名为test/的目录。 然后,将每个测试文件放置在test/someModuleTest.js 。 (可选)您也可以使用test/内的目录,但是我建议保持简单-以后随时可以更改它。

设置测试运行器

为了在浏览器中运行测试,我们需要设置一个简单的HTML页面作为测试运行器页面。 该页面将加载Mocha,测试库和我们的实际测试文件。 要运行测试,我们只需在浏览器中打开运行器即可。

如果您使用的是Node.js,则可以跳过此步骤。 假设您遵循建议的目录结构,则可以使用命令mocha来运行Node.js单元测试。

以下是我们将用于测试运行器的代码。 我将此文件另存为testrunner.html

<!DOCTYPE html>
<html>
  <head>
    <title>Mocha Tests</title>
    <link rel="stylesheet" href="node_modules/mocha/mocha.css">
  </head>
  <body>
    <div id="mocha"></div>
    <script src="node_modules/mocha/mocha.js"></script>
    <script src="node_modules/chai/chai.js"></script>
    <script>mocha.setup('bdd')</script>

    <!-- load code you want to test here -->

    <!-- load your test files here -->

    <script>
      mocha.run();
    </script>
  </body>
</html>

测试运行程序中的重要位是:

  • 我们加载Mocha的CSS样式以使测试结果具有良好的格式。
  • 我们创建一个ID为mocha的div。 这是插入测试结果的位置。
  • 我们加载摩卡和柴。 由于它们是通过npm安装的,因此它们位于node_modules文件夹的子文件夹中。
  • 通过调用mocha.setup ,我们可以使用Mocha的测试助手。
  • 然后,我们加载要测试的代码和测试文件。 我们现在还没有任何东西。
  • 最后,我们调用mocha.run来运行测试。 确保加载源文件和测试文件调用此函数。

基本测试构建块

现在我们可以运行测试,让我们开始编写一些测试。

我们将从创建一个新文件test/arrayTest.js 。 这样的单个测试文件称为测试用例 。 我将其arrayTest.js因为在此示例中,我们将测试一些基本的数组功能。

每个测试用例文件都遵循相同的基本模式。 首先,您有一个describe块:

describe('Array', function() {
  // Further code for tests goes here
});

describe用于对单个测试进行分组。 第一个参数应指示我们要测试的内容–在这种情况下,由于我们要测试数组函数,因此我传入了字符串'Array'

其次,在describe ,我们将it块:

describe('Array', function() {
  it('should start empty', function() {
    // Test implementation goes here
  });

  // We can have more its here
});

it用于创建实际测试。 于第一参数it应该提供试验的人类可读的描述。 例如,我们可以将以上内容理解为“它应该开始为空”,这很好地描述了数组的行为。 然后,将实现测试的代码写入传递给it的函数中。

所有Mocha测试都是从这些相同的构建块构建的,并且遵循相同的基本模式。

  • 首先,我们使用describe来说明要测试的内容,例如,“描述数组应该如何工作”。
  • 然后,我们使用了一些it的功能来创建单独的测试-每次it应该解释一个特定行为,如“应启动空”为我们的数组的情况下上面。

编写测试代码

现在我们知道如何构造测试用例,让我们跳到有趣的部分-实现测试。

由于我们正在测试一个数组应该以空开始,因此我们需要创建一个数组,然后确保它为空。 此测试的实现非常简单:

var assert = chai.assert;

describe('Array', function() {
  it('should start empty', function() {
    var arr = [];

    assert.equal(arr.length, 0);
  });
});

注意在第一行,我们设置了assert变量。 只是这样,我们不需要在所有位置继续输入chai.assert

it函数中,我们创建一个数组并检查其长度。 尽管很简单,但这是测试如何工作的一个很好的例子。

首先,您要进行测试-这就是被测 系统SUT 。 然后,如有必要,您可以对SUT进行操作。 在此测试中,我们没有做任何事情,因为我们正在检查数组开头是否为空。

测试中的最后一件事应该是验证-检查结果的断言 。 在这里,我们使用assert.equal做到这一点。 大多数断言函数采用相同顺序的参数:首先是“实际”值,然后是“期望”值。

  • 实际值是测试代码的结果,因此在这种情况下为arr.length
  • 期望值是结果应该是什么。 由于数组应开始为空,因此此测试中的期望值为0

Chai还提供了两种不同的写断言样式,但是我们现在使用断言使事情保持简单。 当您对编写测试有更多的经验时,您可能希望改用Expected断言 ,因为它们提供了更多的灵活性。

运行测试

为了运行此测试,我们需要将其添加到我们先前创建的测试运行器文件中。

如果使用的是Node.js,则可以跳过此步骤,并使用命令mocha运行测试。 您将在终端中看到测试结果。

否则,要将此测试添加到跑步者,只需添加:

<script src="test/arrayTest.js"></script>

下面:

<!-- load your test files here -->

添加脚本后,您便可以在所选浏览器中加载测试运行器页面。

测试结果

运行测试时,测试结果将如下所示:

Mocha test results - 1 test passing

请注意,我们进入了describeit功能,在输出显示-测试都说明下分组。 请注意,也可以嵌套describe块以创建更多子分组。

让我们看一下失败的测试是什么样的。

在测试的行上说:

assert.equal(arr.length, 0);

将数字0替换为1 。 这会使测试失败,因为数组的长度不再与预期值匹配。

如果再次运行测试,则会以红色显示失败的测试,并说明发生了什么问题。

Mocha test error - one test failing

测试的好处之一是它们可以帮助您更快地发现错误,但是该错误在这方面不是很有帮助。 我们可以修复它。

大多数断言函数也可以采用可选的message参数。 这是断言失败时显示的消息。 使用此参数使错误消息更易于理解是个好主意。

我们可以像这样在声明中添加一条消息:

assert.equal(arr.length, 1, 'Array length was not 0');

如果您重新运行测试,则将显示自定义消息,而不是默认消息。

让我们将断言切换回原来的状态-将1替换为0 ,然后再次运行测试以确保它们通过。

把它放在一起

到目前为止,我们已经看了相当简单的示例。 让我们将学到的知识付诸实践,看看如何测试更现实的代码。

这是一个向元素添加CSS类的函数。 这应该放在新文件js/className.js

function addClass(el, newClass) {
  if(el.className.indexOf(newClass) === -1) {
    el.className += newClass;
  }
}

为了使它更有趣,我仅在元素的className属性中不存在该类的情况下才添加一个新类-毕竟谁想看到<div class="hello hello hello hello">

最好的情况是, 编写代码之前 ,我们将为此功能编写测试。 但是测试驱动的开发是一个复杂的话题,现在我们只想专注于编写测试。

首先,让我们回顾一下单元测试背后的基本思想:我们为函数提供某些输入,然后验证函数的行为是否符合预期。 那么此功能的输入和行为是什么?

给定一个元素和一个类名:

  • 如果元素的className属性不包含类名,则应添加它。
  • 如果元素的className属性确实包含类名,则不应添加它。

让我们将这些案例转换为两个测试。 在test目录中,创建一个新文件classNameTest.js并添加以下内容:

describe('addClass', function() {
  it('should add class to element');
  it('should not add a class which already exists');
});

我们将措词略微更改为测试使用的“它应该X”形式。 这意味着它读起来更好一些,但本质上仍与我们上面列出的人类可读形式相同。 从构思到测试通常并不比这困难得多。

但是等等,测试功能在哪里? 好吧,当我们省略第二个参数it ,Mocha it这些测试标记为测试结果。 这是设置许多测试的便捷方法,有点像您打算编写的待办事项清单。

让我们继续执行第一个测试。

describe('addClass', function() {
  it('should add class to element', function() {
    var element = { className: '' };

    addClass(element, 'test-class');

    assert.equal(element.className, 'test-class');
  });

  it('should not add a class which already exists');
});

在此测试中,我们创建一个element变量,并将其与字符串test-class (要添加的新类)一起作为参数传递给addClass函数。 然后,我们使用断言检查类是否包含在值中。

再次,我们从最初的想法出发-给定一个元素和一个类名,应将其添加到类列表中-并以相当简单的方式将其转换为代码。

尽管此函数旨在与DOM元素一起使用,但我们在这里使用的是普通的JS对象。 有时,我们可以通过这种方式利用JavaScript的动态特性来简化测试。 如果我们不这样做,则需要创建一个实际元素,这会使我们的测试代码复杂化。 另外一个好处是,由于我们不使用DOM,因此我们也可以在Node.js中运行此测试。

在浏览器中运行测试

要在浏览器中运行测试,您需要将className.jsclassNameTest.js添加到运行器:

<!-- load code you want to test here -->
<script src="js/className.js"></script>

<!-- load your test files here -->
<script src="test/classNameTest.js"></script>

现在,您应该看到一个测试通过,另一个测试显示为未决,如以下CodePen所示。 请注意,该代码与示例稍有不同,以使代码在CodePen环境中工作。

请参阅CodePen上的SitePoint@SitePoint使用Mocha(1)进行的笔单元测试

接下来,让我们实施第二项测试…

it('should not add a class which already exists', function() {
  var element = { className: 'exists' };

  addClass(element, 'exists');

  var numClasses = element.className.split(' ').length;
  assert.equal(numClasses, 1);
});

经常运行测试是一个好习惯,所以让我们检查一下如果现在运行测试会发生什么。 正如预期的那样,它们应该通过。

这是实现第二个测试的另一个CodePen。

请参阅CodePen上的SitePoint@SitePoint使用Mocha(2)进行的笔单元测试

但是等一下! 我实际上欺骗了你。 我们没有考虑该功能的第三种行为。 该函数中还有一个错误-一个相当严重的错误。 它只是一个三行功能,但是您注意到了吗?

让我们为第三种行为编写另一个测试,该行为将Bug作为奖励暴露出来。

it('should append new class after existing one', function() {
  var element = { className: 'exists' };

  addClass(element, 'new-class');

  var classes = element.className.split(' ');
  assert.equal(classes[1], 'new-class');
});

这次测试失败。 您可以在下面的CodePen中看到它的运行。 这里的问题很简单:元素中的CSS类名称应以空格分隔。 但是,我们当前的addClass实现不会添加空格!

请参阅CodePen上的SitePoint@SitePoint使用Mocha(3)进行的笔单元测试

让我们修复函数并通过测试。

function addClass(el, newClass) {
  if(el.className.indexOf(newClass) !== -1) {
    return;
  }

  if(el.className !== '') {
    //ensure class names are separated by a space
    newClass = ' ' + newClass;
  }

  el.className += newClass;
}

这是具有固定功能和通过测试的最终CodePen。

请参阅CodePen上的SitePoint@SitePoint使用Mocha(4)进行的笔单元测试

在节点上运行测试

在Node中,事物仅对同一文件中的其他事物可见。 由于className.jsclassNameTest.js位于不同的文件中,因此我们需要找到一种将彼此公开的方法。 执行此操作的标准方法是使用module.exports 。 如果您需要复习,则可以在这里阅读全部内容: 了解Node.js中的module.exports和export

代码本质上保持不变,但结构略有不同:

// className.js

module.exports = {
  addClass: function(el, newClass) {
    if(el.className.indexOf(newClass) !== -1) {
      return;
    }

    if(el.className !== '') {
      //ensure class names are separated by a space
      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;

// The rest of the file remains the same

describe('addClass', function() {
  ...
});

如您所见,测试通过了。

Mocha terminal output - 4 tests passing

下一步是什么?

如您所见,测试不必复杂或困难。 就像编写JavaScript应用程序的其他方面一样,您有一些重复的基本模式。 一旦熟悉了这些内容,就可以一次又一次地继续使用它们。

但这只是表面。 关于单元测试,还有更多的知识要学习。

  • 测试更复杂的系统
  • 如何处理Ajax,数据库和其他“外部”事物?
  • 测试驱动开发

如果您想继续学习以及更多,我创建了一个免费的JavaScript单元测试快速入门系列 。 如果您发现本文有用,则绝对应该在此处查看

另外,如果视频更适合您的风格,您可能会对SitePoint Premium的课程感兴趣: Node.js中的测试驱动开发

From: https://www.sitepoint.com/unit-test-javascript-mocha-chai/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值