代码无错是确保应用和业务流程顺利运行的关键。测试和静态分析(运行一个工具来识别代码中的潜在错误)只是为确保代码质量而可以采取的众多操作中的两种操作。测试和静态分析主要好在哪里?这两个过程都可以完全自动化。在我的 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 存储库中找到。
测试自动化概述
就本单元而言,测试是指根据程序要求检查代码以确保两者匹配的行为。自动化测试是指让计算机为您运行此检查而无需您手动操作的行为。
简言之,测试过程如下(须注意,我提倡测试驱动的开发):
编写测试用例,即受应用程序需求控制的代码。
编写用于实现应用程序需求的代码。这是受测试的代码。
根据受测试的代码运行测试用例,直到所有测试用例通过为止。
自动化测试有几个关键优势:
以代码形式编写测试用例可以提高代码质量,因为它减少了测试过程中出现人为错误的可能性。您可以预先编写测试用例,然后计算机可以根据您正在测试的代码反复运行这些用例。
您还可以根据需要随时向测试套件(测试用例集合)添加新的测试用例。如果您忘记编写测试用例,可稍后将它添加到集合中。
如果在编写测试用例时出错,可以调试测试用例本身,因为测试用例就是代码。从那时起,测试用例应该按照可预测的方式运行。
手动测试则与之相反:在手动测试中,每次更改内容时,都要记住运行所有的测试用例。这会导致更长的测试周期,容易发生人为错误,包括忘记运行测试。
在后面几节中,我将向您介绍本教程中用于自动化测试的工具。这些绝不是 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.true
或expect(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
生命周期内可用于自动执行测试的一些工具。
在本节中,我们设置一个示例项目,并编写一些测试。这些示例都需要亲自动手操作,因此您应该按照后续部分中的说明安装所有包。同样,您应该严格遵循有关编写测试的说明,以便学习和研究测试代码。
项目设置
要设置示例项目和测试,需要执行以下操作:
初始化项目
安装以下包:
Mocha
Chai
Sinon
Istanbul CLI - nyc
配置
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:recommended
和 google
配置。
首先,将下面的代码片段粘贴到 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 元素和值为 true
的 node
,便可以使用全局变量,如 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
的特殊脚本可运行解决方案代码。要运行此脚本:
将
solution/package.json
复制到 Unit-9 目录(这将覆盖您迄今为止对 Unit-9/package.json 所做的任何更改)。调用
npm
以运行脚本:npm run test-solution
。
您应会看到类似如下的输出: