Node.js 单元测试

Xebcnor     最近更新时间:2019-10-10 11:27:15

667

代码无错是确保应用和业务流程顺利运行的关键。测试和静态分析(运行一个工具来识别代码中的潜在错误)只是为确保代码质量而可以采取的众多操作中的两种操作。测试和静态分析主要好在哪里?这两个过程都可以完全自动化。在我的   Node.js 学习路径的第 9 单元中,学习如何自动执行测试和静态分析,以便构建无错的 Node.js 应用程序。同时还将向您介绍 Node 中一些最流行的测试自动化工具:

使用 SDK for Node.js? 创建一个 Cloud Foundry 应用

轻松开发、部署、扩展服务器端 JavaScript? 应用。IBM SDK for Node.js?   提供增强的性能、安全和服务。

  • Mocha:一种测试框架

  • Chai:一种断言库

  • Sinon:一种测试替身库

  • Istanbul:一种代码覆盖率库

  • ESLint:一种静态分析实用程序

所有这些工具都纳入 NPM 生态系统,可以接入到您的 npm 生命周期中。

获取代码

您在此学习路径中跟随其中示例一起进行操作所需的代码可在我的 GitHub 存储库中找到。

测试自动化概述

就本单元而言,测试是指根据程序要求检查代码以确保两者匹配的行为。自动化测试是指让计算机为您运行此检查而无需您手动操作的行为。

简言之,测试过程如下(须注意,我提倡测试驱动的开发):

  1. 编写测试用例,即受应用程序需求控制的代码。

  2. 编写用于实现应用程序需求的代码。这是受测试的代码

  3. 根据受测试的代码运行测试用例,直到所有测试用例通过为止。

自动化测试有几个关键优势:

  • 以代码形式编写测试用例可以提高代码质量,因为它减少了测试过程中出现人为错误的可能性。您可以预先编写测试用例,然后计算机可以根据您正在测试的代码反复运行这些用例。

  • 您还可以根据需要随时向测试套件(测试用例集合)添加新的测试用例。如果您忘记编写测试用例,可稍后将它添加到集合中。

  • 如果在编写测试用例时出错,可以调试测试用例本身,因为测试用例就是代码。从那时起,测试用例应该按照可预测的方式运行。

手动测试则与之相反:在手动测试中,每次更改内容时,都要记住运行所有的测试用例。这会导致更长的测试周期,容易发生人为错误,包括忘记运行测试。

在后面几节中,我将向您介绍本教程中用于自动化测试的工具。这些绝不是 Node 生态系统中唯一可用的测试工具。它们只是一些比较流行的工具,恰好也能很好协作。

Mocha:一种测试框架

测试框架是用于定义并提供以下几项内容的软件:

  • 测试 API,用于指定如何编写测试代码。

  • 测试发现,用于确定哪些 JavaScript 文件是测试代码。

  • 测试生命周期,用于定义在运行测试之前、期间和之后发生的事件。

  • 测试报告,用于记录运行测试时发生的情况。

Mocha 是最流行的 JavaScript 测试框架之一,您在开发期间很可能会遇到它。Jest 是另一个流行的 Node 测试框架。 对于本教程,我没有时间同时介绍这两个框架,所以我选择了 Mocha。

为了告知 Mocha 您的 JavaScript 代码是一个测试,可以使用 Mocha 测试 API 中提供的特殊关键字:

  • describe() 表示一个任意嵌套的测试用例分组(一个 Description() 可以包含其他     Description())。

  • it() 表示单个测试用例。

这两个函数都有两个参数:

  • 在测试报告中显示的描述。

  • 回调函数

我们将在本教程的后面再讨论这些内容。

用 Mocha 编写测试套件

最简单的测试套件只包含一个测试:

清单 1. 仅含一个测试的 Mocha 测试套件

1
2
3
4
5
6
7
8
9
const {describe} = require('mocha');
 
const assert = require('assert');
 
describe('Simple test suite:', function() {
    it('1 === 1 should be true', function() {
        assert(1 === 1);
    });
});

在上面的清单中,describe 已经在全局上下文中;因此,require('mocha') 是不必要的,但为了说明起见,我还是想把它包含在内。

以下是此测试的输出:

1
2
3
4
5
6
7
8
9
$ cd src/projects/IBM-Developer/Node.js/Course/Unit-9
$ ./node_modules/.bin/mocha test/example1.js
 
 
  Simple test suite:
    ? 1 === 1 should be true
 
 
  1 passing (5ms)

在本例中,我使用了 Node 的 assert 模块,但这不是最具表达性的断言库。幸运的是,Mocha 并不关心您使用哪个库,所以您可以自由选择最喜欢的库。

挑战问题 1 如何运行清单 1 中的测试?看看您能否明白我是如何实现的。本单元的结尾给出了答案。

Chai:一种断言库

Chai 是最流行的 JavaScript 测试断言库之一。它易于使用,适用于 Mocha,并提供了两种风格的断言:

  • Assert:assertEqual(1, 1)

  • BDD(行为驱动开发):expect(1 === 1).to.be.trueexpect(1).to.equal(1)

Chai 还允许您插入自己的断言库,但在本教程中不会讨论这个问题。

我喜欢 BDD 风格的断言,因为它们比 assert 风格的断言表达性更强。表达性断言使测试代码更易读,更易于维护。在本教程中,我们将使用 Chai 的 BDD 风格断言。

使用 Chai 的测试套件

让我们修改清单 1 中的测试套件以使用 Chai。

清单 2. 使用 Chai 的 BDD 风格断言库的 Mocha 测试套件

1
2
3
4
5
6
7
8
9
const {describe} = require('mocha');
 
const {expect} = require('chai');
 
describe('Simple test suite (with chai):', function() {
    it('1 === 1 should be true', function() {
        expect(1).to.equal(1);
    });
});

输出如下:

1
2
3
4
5
6
7
8
9
$ cd ~/src/projects/IBM-Developer/Node.js/Course/Unit-9
$ ./node_modules/.bin/mocha test/example2.js
 
 
  Simple test suite (with chai):
    ? 1 === 1 should be true
 
 
  1 passing (6ms)

清单 2 中的示例可能与第一个测试套件没有太大不同,但是使用 Chai 的 BDD 风格断言语法可以提高测试的可读性。

Sinon:一种测试替身库

测试替身是一个代码块,用于替换部分生产代码以便进行测试。如果不方便甚至不可能对生产代码运行测试用例,那么测试替身就非常有用。当生产代码需要连接到数据库时,或者您需要获得精确的系统时间时(您很快就会看到这一点),测试替身便能派上用场。

自行编写这样的代码可能面临出现技术问题的风险。幸运的是,Node 社区已经开发了几个用于测试替身的包。其中一个名为 Sinon 的库非常流行。您可能会碰到这个库,所以我们在这里了解一下。

使用间谍、存根和模拟对象进行测试

测试替身有几种类型:

  • 间谍 (spy)   用于包装一个真实函数,以便记录其相关信息,比如它被调用了多少次以及使用哪些参数来调用。

  • 伪造对象 (fake)   是一个间谍对象,它只会假装是一个真实函数,这样它就可以记录有关该函数的信息。

  • 模拟对象 (mock)   类似于一个伪造函数,只是使用了您所指定的期望值,例如函数被调用多少次以及使用哪些参数来调用。

  • 存根 (stub) 类似于间谍,只是用您所指定的行为替换了真实函数。

清单 3 展示了如何创建一个间谍。

清单 3. 窥探 console.log() 的 Sinon 间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const {describe, it} = require('mocha');
 
const {expect} = require('chai');
 
const sinon = require('sinon');
 
describe('When spying on console.log()', function() {
    it('console.log() should still be called', function() {
        let consoleLogSpy = sinon.spy(console, 'log');
        let message = 'You will see this line of output in the test report';
        console.log(message);
        expect(consoleLogSpy.calledWith(message)).to.be.true;
        consoleLogSpy.restore();
    });
});

输出如下所示:

1
2
3
4
5
6
7
8
9
10
$ cd ~/src/projects/IBM-Developer/Node.js/Course/Unit-9
$ ./node_modules/.bin/mocha test/example3.js
 
 
  When spying on console.log()
You will see this line of output in the test report
    ? console.log() should still be called
 
 
  1 passing (7ms)

须注意,测试报告中显示了 console.log 输出。这是因为间谍不会替换函数,而只是窥探函数。如果您希望调用真实函数,但需要进行相关断言,就可以使用间谍。您可以在清单 3 中看到这一点,其中的测试规定必须使用精确的 message 来调用 console.log()

如何编写存根

要用提供实现的函数替换 console.log(),可以使用存根或模拟对象。清单 4 显示了如何编写一个非常简单的存根。

清单 4. 用一个不执行任何操作的函数来替换 console.log() 的 Sinon 存根

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const {describe, it} = require('mocha');
 
const {expect} = require('chai');
 
const sinon = require('sinon');
 
describe('When stubbing console.log()', function() {
    it('console.log() is replaced', function() {
        let consoleLogStub = sinon.stub(console, 'log');
        let message = 'You will NOT see this line of output in the test report';
        console.log(message);
        consoleLogStub.restore();
        expect(consoleLogStub.calledWith(message)).to.be.true;
    });
});

以下是这个示例的输出:

1
2
3
4
5
6
7
8
9
$ cd ~/src/projects/IBM-Developer/Node.js/Course/Unit-9
$ ./node_modules/.bin/mocha test/example4.js
 
 
  When stubbing console.log()
    ? console.log() is replaced and the stub is called instead
 
 
  1 passing (8ms)

须注意,在此示例中,该消息没有显示在测试报告中。这是因为用存根替换了真正的 console.log()。另须注意,必须在   expect() 断言调用之前调用 consoleLogStub.restore(),否则测试报告看上去将不正确。

挑战问题 2 为什么在调用任何断言逻辑之前必须在 Sinon 存根上调用 restore() 查看 Sinon 存根文档, 看看您是否可以找到原因。

模拟对象与存根非常相似,所以在这里,我就不再演示如何编写模拟对象了。我们将在本单元后面的内容中再次涉及到模拟对象和存根。

Istanbul:一种测试代码覆盖率库

代码覆盖率是一个代码质量度量,用于衡量在运行测试时(即,在 npm test 的单次调用期间,您很快就会看到),实际执行了多少受测试的潜在可执行代码。

在自动化测试中,您会希望尽可能多地运行可执行代码。运行的代码越多,发现的缺陷也就越多。因此,对于代码覆盖率,您想要争取达到 100%,这意味着在自动化测试时,所有可能的可执行代码行都在运行。

Istanbul 是用于在 JavaScript 中测试代码覆盖率的一个很受欢迎的库,并且有一个用于此库的 Node 模块。Istanbul 可在运行测试之前动态检测您的代码,然后在测试运行期间跟踪执行了多少代码。

在本单元后面的内容中,您将使用 Istanbul 的命令行界面 nyc。

ESlint:一种插件式静态分析实用程序

linter 是一个用来分析代码中潜在错误的工具,有时称为静态代码分析

在代码上运行 linter 称为静态分析,这一技术可以非常方便地发现以下问题:

  • 未声明的变量

  • 未使用的变量或函数

  • 过长的源代码行

  • 格式错误的注释

  • 缺少的文档注释

  • 其他众多内容

您可能还记得单元 7 中的内容:npm 注册表包含许多用于静态分析的工具。对于本教程,我选择的工具是 ESLint。ESLint 会根据配置插件(配置)报告不同的问题,有些插件会比其他插件更宽容。除了   eslint:recommended 配置之外,我们还使用了可共享的eslint-config-google。可供选择的可共享配置有很多:搜索 npm 注册表,亲眼看一看。

在本单元的后面部分中,我将向您展示如何在 npm test 生命周期内,将 linter 配置为两个单独的 npm 脚本并运行。

设置示例项目

到目前为止,您已经大致了解了测试技术,以及在 npm 生命周期内可用于自动执行测试的一些工具。 在本节中,我们设置一个示例项目,并编写一些测试。这些示例都需要亲自动手操作,因此您应该按照后续部分中的说明安装所有包。同样,您应该严格遵循有关编写测试的说明,以便学习和研究测试代码。

项目设置

要设置示例项目和测试,需要执行以下操作:

  1. 初始化项目

  2. 安装以下包:

    • Mocha

    • Chai

    • Sinon

    • Istanbul CLI - nyc

  3. 配置 package.json

我们将一起执行这些步骤。一旦设置了示例项目,便可以开始编写测试代码。

第 1 步. 初始化项目

最简单的着手方法是打开终端窗口或命令提示符,导航到 Unit-9 目录,并输入以下命令:touch package.json。这将创建一个空白的 package.json 文件。

将空白的 package.json 替换为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "name": "logger",
  "version": "1.0.0",
  "main": "logger.js",
  "license": "Apache-2.0",
  "scripts": {
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/jstevenperry/node-modules"
  }
}

第 2 步. 安装测试包

接下来,您将安装每个测试包。

安装 Mocha

在终端窗口中,输入以下命令:npm i --save-dev mocha

输出如下所示:

1
2
3
4
5
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ npm i --save-dev mocha
npm notice created a lockfile as package-lock.json. You should commit this file.
+ mocha@5.2.0
added 24 packages from 436 contributors and audited 31 packages in 1.739s
found 0 vulnerabilities

这将安装 Mocha 的最新版本,并将 mocha 保存在 package.json 的 devDependencies 节中。

安装 Chai

在终端窗口中,输入以下命令:npm i --save-dev chai

输出如下所示:

1
2
3
4
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ npm i --save-dev chai
+ chai@4.1.2
added 7 packages from 20 contributors and audited 39 packages in 1.157s
found 0 vulnerabilities

这将安装 Chai 的最新版本,并将 chai 保存在 package.json 的 devDependencies 节中。

安装 Sinon

在终端窗口中,输入以下命令:npm i sinon

输出如下所示:

1
2
3
4
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ npm i --save-dev sinon
+ sinon@6.1.3
added 11 packages from 317 contributors and audited 57 packages in 1.687s
found 0 vulnerabilities

这将安装 Sinon 的最新版本,并将 sinon 保存在 package.json 的 devDependencies 节中。

安装 Istanbul CLI

在终端窗口中,输入以下命令:npm i --save-dev nyc

输出如下所示:

1
2
3
4
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ npm i --save-dev nyc
+ nyc@12.0.2
added 284 packages from 148 contributors and audited 2246 packages in 5.744s
found 0 vulnerabilities

这将安装 Istanbul 命令行接口(称为 nyc)的最新版本,并将 nyc 保存到 package.json 的 devDependencies 节中。

第 3 步. 配置 package.json

现在打开 package.json,并将以下内容添加到 scripts 元素中:

1
"test": "nyc mocha ./test"

这个脚本告诉 npm 调用 Istanbul CLI (nyc) 和 Mocha,后者将发现和运行位于 ./test 目录中的测试。

在执行进一步操作之前,确保您的 package.json 如下面的清单 5 所示。

清单 5. 安装了 Mocha、Chai、Sinon 和 Istanbul 的 package.json 以及 test 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "name": "logger",
  "version": "1.0.0",
  "main": "logger.js",
  "license": "Apache-2.0",
  "scripts": {
    "test": "nyc mocha ./test"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/jstevenperry/node-modules"
  },
  "devDependencies": {
    "chai": "^4.1.2",
    "mocha": "^5.2.0",
    "nyc": "^12.0.2",
    "sinon": "^6.1.3"
  }
}

如果您安装的包版本与清单 5 中的包版本不同,也不用担心。在撰写本文时,我已经安装了最新版本,但这些版本将会随着时间而更新。

编写测试代码

本部分将为本单元中的最后练习做好准备,您在此将完成 logger.js 的测试和实现代码。在实际编写代码之前,我建议您通读整个部分。这将有助于您较好地了解在进入 logger.js 和   test-logger.js 模块之前需要做些什么。开始编写代码时(很快就要开始了),您也可以回过头来参考本部分内容。

使用 Mocha 和 Chai 编写测试

在本例中,我们使用 Mocha 作为测试框架,Chai 作为支持 BDD 风格断言的断言库。

在清单 2 中,您已经发现用 Mocha 和 Chai 编写测试非常容易。您了解了如何通过在一个 describe() 函数调用中嵌套另一个   describe() 函数调用(并在其中再嵌套一个,以此类推),创建所需的测试套件分组。现在,您将使用 it() 函数编写一个测试用例,如下所示:

清单 6. test-logger.js 中的测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
.
describe('Module-level features:', function() {
    // TRACE
    describe('when log level isLevel.TRACE', function() {
        it('should have a priority order lower than Level.DEBUG', function() {
            expect(Level.TRACE.priority).to.be.lessThan(Level.DEBUG.priority);
        });
        it('should have outputString value of TRACE', function() {
            expect(Level.TRACE.outputString).to.equal('TRACE');
        });
    });
.
.

清单 6 中的嵌套测试套件包含两个测试用例:

  • 第一个测试用例可确保 Level.TRACE 优先级属性值低于 Level.DEBUG 的值。

  • 第二个测试用例可确保 outputString 属性是 TRACE

须注意,在断言 expect().to.be.lessThan()expect().to.be.equal() 中使用了函数链。函数链在 BDD 风格的断言中很常见,这可提高它们的可读性。只需查看函数链,就可以清楚地看到每个断言的作用。

运行清单 6 中的测试用例时,输出将如下所示(先不要尝试,否则您会遇到很多错误):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ npm test
.
.
> logger@1.0.0 test /Users/sperry/home/development/projects/IBM-Developer/Node.js/Course/Unit-9
> nyc mocha ./test
 
.
.
  Module-level features:
    when log level isLevel.TRACE
      ? should have a priority order lower than Level.DEBUG
      ? should have outputString value of TRACE
.
.

我从整个测试套件中删除了一些输出,这只是为了显示清单 2 中所示部分的输出。

使用 Mocha 和 Sinon 编写测试

Sinon 是一个测试库,它允许您在测试中使用测试替身。在本部分,您将详细了解如何在 Mocha 测试中使用存根和模拟对象,以及这两者的相关示例。

编写存根

存根 函数是一个测试替身,它用您自己编写的自定义行为来替换某些函数的行为。当您想要用所选的值来替换对 Date.now() 的调用时,这特别方便。

存根是间谍的一种类型,但是存根会用您的函数来替换真实函数,而间谍则不会。间谍只是观察和返回报告,就像现实世界中的间谍一样。

打开 logger.js 模块,查看 log() 函数。您将看到以下内容:

1
2
3
4
5
6
7
8
9
10
11
01 function log(messageLogLevel, message, source, logFunction) {
 02   let computedMessage = null;
 03   if (messageLogLevel.priority >= logLevel.priority) {
 04     let now = Date.now();
 05     let outputString = now.toString() + ':' + messageLogLevel.outputString;
 06     computedMessage = outputString + ': ' + ((source) ? source + ': ' : '') +
 07       message;
 08     (logFunction) ? logFunction(computedMessage) : logMessage(computedMessage);
 09   }
 10   return computedMessage;
 11 }

测试用例全都关乎可预测性,记录器使用 Date.now()(第 4 行)作为 computedMessage(第 6 行)的一部分,这是实际记录的字符串(第 8 行)。

将存根用于时间戳

您可能已经知道,Date.now() 会返回自 Unix   纪元 以来的毫秒数,因此,它始终在变化。如何正确断言预期消息的值?

解决方案是去除真实的 Date.now() 函数,将其替换为返回所选值的函数,然后在断言中使用该值。

使用 Sinon 创建存根非常容易:

1
2
3
4
5
let dateStub = sinon.stub(Date, 'now').returns(1111111111);
.
// Use the stubbed version
.
dateStub.restore();

首先,调用 sinon.stub() 函数,传递要存根的对象(Date 类)和要存根的函数 (now())。然后,您需要向返回的存根(Sinon API 是流畅的)添加一个对 returns() 的调用,指示存根在被调用时返回 1111111111

此后(直到调用了 dateStub.restore()),每次调用 Date.Now() 时,都将替换为您的存根。将返回   1111111111 值,而不是 Date.now() 通常返回的运行毫秒值。

切记,存根将替换真实函数,因此,如果您希望 Date.now() 在测试运行后正确运行,就需要在存根上调用 restore() 函数。

编写模拟对象

模拟对象 函数是一个测试替身,它使用一组期望值替换某个真实函数。与存根不同,模拟对象不提供实现。

如果检查 logger.js 模块的 log() 函数,您会注意到最后一个参数是执行消息实际日志记录的函数。看一下帮助函数,您会注意到,它们也全都使用这个参数。

如果省略了这个参数,log() 将调用 logMessage() ,这是一个内部函数,委托给 sole   e.log() 。对于测试,我们不希望将测试报告输出(默认情况下显示在控制台中)与程序输出混为一谈。我们会将每个 logger 帮助方法的最后一个参数替换为一个模拟对象。然后,我们可以验证它的调用次数是否与预期一致。

使用 Sinon 创建模拟对象非常容易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
01      describe('Default log level of INFO', function() {
 02          let mockLogFunction;
 03
 04          before(function() {
 05              // Make sure the default is correct
 06              logger.setLogLevel(logger.DEFAULT_LOG_LEVEL);
 07              mockLogFunction = sinon.mock().exactly(4);
 08          });
 09          after(function() {
 10              mockLogFunction.verify();
 11          });
 12          // TRACE
 13          it('should not log a TRACE level message and return null', function() {
 14              expect(logger.trace('BAR', 'foo()', mockLogFunction)).to.be.null;
 15          });

记住,测试框架的功能之一就是定义测试生命周期。您可以在上面的示例中看到这一点,其中 Mocha 的 before() 函数在组内的任何测试用例之前运行一次,其 after() 函数在所有测试用例运行之后运行一次。

模拟对象是在第 7 行中设置的,这个特定测试组中的函数期望被调用四次。

一旦测试用例都在 after() 中运行,就会调用 verify() 函数来验证是否满足了期望。如果未满足期望,将会抛出异常。

对于 Mocha ,这些测试示例已经足够。在开始编写代码之前,让我们来安装、配置和运行 linter。

安装 ESLint

在终端窗口中,输入以下命令:npm i --save-dev eslint eslint-config-google

输出如下所示:

1
2
3
4
5
Ix:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ npm i --save-dev eslint eslint-config-google
+ eslint-config-google@0.9.1
+ eslint@5.2.0
added 119 packages from 146 contributors and audited 2491 packages in 7.696s
found 0 vulnerabilities

这将安装 ESLint 的最新版本和 Google 共享的 ESLint 配置。npm 还会将 eslint 和   eslint-config-google 保存到 package.json 的 devDependations 节中,现在应该如下所示:

1
2
3
4
5
6
7
8
9
"devDependencies": {
    "babel-eslint": "^8.2.6",
    "chai": "^4.1.2",
    "eslint": "^5.2.0",
    "eslint-config-google": "^0.9.1",
    "mocha": "^5.2.0",
    "nyc": "^12.0.2",
    "sinon": "^6.1.4"
  }

配置并运行 linter

ESLint 的配置方法有许多,但我建议使用 package.json ,以便尽量减少必须在项目中来回拖动的元数据文件的数量。在这个例子中,我们对 ESLint 同时使用了   eslint:recommendedgoogle 配置。

首先,将下面的代码片段粘贴到 package.json 中的 devDependents 节后面。您需要在此节末尾添加一个逗号,以便使 JSON 格式正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"eslintConfig": {
    "extends": ["eslint:recommended", "google"],
    "env": {
        "node" : true
    },
    "parserOptions": {
      "ecmaVersion": 6
    },
    "rules" : {
      "max-len": [2, 120, 4, {"ignoreUrls": true}],
      "no-console": 0
    }
  },
  "eslintIgnore": [
    "node_modules"
  ]

extends 元素表示要使用的配置数组,在此情况下,是 eslint:recommended 和   google

添加 env 元素和值为 truenode,便可以使用全局变量,如 require 和   module

我们将使用 ES6 语法 (parserOptions) 。还须注意,在 rules 下面,我让   max-len 更符合我的喜好(默认为 80 )。

我们当然也不希望对 node_modules 中的所有 JavaScript 代码进行静态分析,因为这会产生可怕的输出量。

其他脚本

接下来,将以下代码片段添加到 package.json 中的 scripts 元素:

1
2
3
"lint": "eslint .",
    "lint-fix": "npm run lint -- --fix",
    "pretest": "npm run lint"

第一个脚本 lint 实际上运行 linter (eslint) ,并告知其对当前目录中的所有内容进行静态分析。ESLint 还将对每个下级目录进行静态分析,您明确告诉它要忽略的目录除外(如上面所示,通过 eslintIgnore 元素)。

我创建了第二个脚本 lint-fix,允许 linter 自动修正常见代码 陷阱,比如,行中的多余空格,或者在单行注释后无空格。安装了这个脚本后,您只需要运行 npm run lint-fix,linter 将为您清理代码。(也许将来总有一天,当我注释掉一行代码时,我会记得在 // 之后添加一个空格,但这看起来并不好。)

最后,npm 内置的 pretest 脚本将确保您的代码在每次运行 npm test 时都会被执行静态分析。如果   pretest 脚本太烦人,可以将其删除,但是我喜欢将它放在那里,这样我就不会忘记每次更改代码时都对代码进行静态分析

关于目录结构的说明(保持目录整洁)

如果您询问 10 位开发人员他们将单元测试放在 Node 项目中的哪个目录,他们的回答可能不尽相同。就我个人而言,我喜欢使源代码尽可能接近 package.json ,且使用   最小 目录结构。我想要一个整洁且可维护的布局,这样就可以很容易地找到所需内容。

接下来的练习使用以下目录布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
x:~/src/projects/IBM-Developer/Node.js/Course/Unit-9 sperry$ tree .
.
├── logger.js
├── solution
│   ├── exercise-1
│   │   ├── logger.js
│   │   └── test
│   │       └── test-logger.js
│   ├── logger.js
│   ├── package.json
│   └── test
│       └── test-logger.js
└── test
    ├── example1.js
    ├── example2.js
    ├── example3.js
    ├── example4.js
    └── test-logger.js
 
5 directories, 11 files

最低原则:如果贵公司已有标准,应遵循该标准。否则,只需采用一个可以使代码保持整洁的最小目录结构。

练习:单元测试 logger.js

您已经了解了如何用 Chai 和 Sinon 编写 Mocha 测试,以及如何运行 linter 。现在是时候编写一些代码了,您将自己编写代码。

为帮助您着手,我创建了以下框架版本:

  • 您首先要编写的测试模块,位于 ./test/test-logger.js

  • 实现代码,位于 logger.js

我推荐使用测试驱动开发 (TDD) ,所以务必首先编写所有测试代码,然后再运行 npm test

因为 linter 首先在 pretest 中运行,所以您先要解决所有静态分析错误, 然后测试用例才会运行 。如果一开始看到很多错误,不要灰心。这个练习的两个配置都相当挑剔,但最终结果会是一个超级整洁的代码。

一旦测试用例最终开始运行,第一个任务就是观察它们全部失败。 然后 (只能在此之后),应开始编写 logger.js 实现,直到测试通过为止。

在需要编写代码的框架中有一些 TODO: 注释。在看到 TODO: 注释的地方,应遵循相关说明。我还提供了一些参考代码块来帮助您。

祝您好运!

如果其他一切方法都不幸失败

如果您似乎无法让代码正常工作,并且您确实希望它能够工作,那么在 solution/package.json 中有一个名为   test-solution 的特殊脚本可运行解决方案代码。要运行此脚本:

  1. solution/package.json 复制到 Unit-9 目录(这将覆盖您迄今为止对 Unit-9/package.json   所做的任何更改)。

  2. 调用 npm 以运行脚本:npm run test-solution

您应会看到类似如下的输出:

展开阅读全文