在Node.js中使用Mocha和Chai进行测试 [1/2]
测试是软件开发生命周期中的一个基本部分。当你刚刚开始的时候,测试可能看起来很无聊,甚至是多余的。然而,如果你想开发的软件能够100%地按照你的期望工作,并为用户提供良好的用户体验,正确地测试你的程序是必须的。
因此,我们决定在这篇文章中对Node.js的测试进行简化介绍。我们打算在即将到来的几周内创建另一个帖子,以涵盖与测试有关的更复杂的主题;在阅读本帖后,请密切关注。(更新,新的帖子已经准备好了,在Node.js中使用Mocha和Chai进行测试 - 第二部分)
什么是测试?
当我们谈论测试时,我们谈论的是一个评估软件质量的过程。测试验证了我们的程序是否按照我们的意图工作,并满足它应该满足的技术、功能、法规和用户要求。
开发人员用来测试应用程序的方法根据他们测试的内容和水平而不同。通常情况下,程序是用几种方法的组合来测试的,以确保它们准备好向世界发布。单元测试、集成测试、系统测试、负载和压力测试以及可用性测试是目前常用的一些方法。
在这篇文章中,我们将重点讨论单元测试以及如何在Node.js中编写单元测试。
单元测试
单元测试是对软件的最小单元或组件进行测试的做法。这些最小的单元以其独立性来识别;它们不依赖于其他软件组件或部件。在Node.js中,一个程序的最小单位是一个函数。因此,当在Node应用程序上运行单元测试时,我们正在测试单个功能是否按照预期的那样工作。
单元测试是在任何其他类型的测试之前进行的第一层软件测试。通常情况下,编写单元测试是开发人员的责任。
单元测试的好处
开发人员必须付出额外的努力来编写单元测试和可测试的代码。那么,为什么有经验的开发人员坚持做单元测试这项繁琐的工作?原因很简单:其明显的好处超过了测试的成本和额外的努力。
单元测试使软件易于维护。如果有人对代码库做了修改,你可以运行单元测试,以弄清该修改是否在系统中造成了缺陷。如果有缺陷,你可以通过分析失败的测试用例,轻松找到缺陷所在。
单元测试迫使开发人员编写松散耦合和可重用的代码。为重度耦合的代码编写测试是一项不可能的任务。因此,无论你喜欢与否,你都必须遵循这些最佳编程实践来成功完成单元测试。由于这些原因,可测试的代码总是更容易理解。
在Node.js中测试--我们使用的是哪些包?
Node.js在npm上有许多包,使编写测试的过程更容易。在本教程中,我们使用两个最流行的Node模块进行测试。Mocha和Chai。
Mocha是这个测试套件的主要测试框架。它提供了执行测试和处理记录测试结果到终端的功能。Chai是一个常用于Mocha的断言库。我们使用断言来验证被测试的组件在特定的测试案例中返回它所期望的值。
正如你在本教程中写的单元测试中看到的,像it和describe这样的函数是Mocha的一部分。我们在这些函数中的断言,如expect(isSuccess).to.be.true来自于Chai库。
设置测试环境
在继续之前,如果你还没有一个新的Node.js项目,请建立一个新的项目。然后,安装Mocha和Chai库,用这个命令把它们作为dev-dependencies保存到package.json文件中:
npm install mocha chai --save-dev
然后,将package.json文件中的测试脚本改为这样:
"scripts": {
"test": "mocha"
},
这样我们就可以在命令行上使用npm test 命令运行测试。
你还应该创建一个名为test的新目录。这是我们所有测试文件要保存的地方。注意,你必须使用确切的名称 "test "来命名这个目录,因为Mocha在运行测试时要寻找一个有这个名称的目录。
在这一点上,我们的项目目录有一个像这样的文件夹结构:
|test
|src
|node_modules
package.json
摩卡的基本测试格式
在Mocha中,一个典型的测试文件有如下形式:
//a collection of test cases that test a specific component
describe("validator isValid()", () => {
//test a function for a specific case
it("should return true for a number in between 10 and 70", ()=> {
})
it("should return false when the number is less than or equal to 10", () => {
})
it("should return false when the number is greater than or equal to 70", () => {
})
})
使用Mocha钩子的测试格式
Mocha提供了一些钩子,我们可以在每个测试用例或所有测试用例前后运行,它们是
- before():这个钩子里面的逻辑在测试集合中的所有测试用例之前运行。
- after():这里面的逻辑在所有测试用例之后运行。
- beforeEach()。这里面的逻辑是在测试集合中的每个测试案例之前运行。
- afterEach():这里面的逻辑在每个测试用例之后运行。
describe("validator isValid()", () => {
before(()=> {
})
after(()=> {
})
beforeEach(()=> {
})
afterEach(()=> {
})
it("should return true for a number in between 10 and 70", ()=> {
})
it("should return false when the number is less than or equal to 10", () => {
})
it("should return false when the number is greater than or equal to 70", () => {
})
})
编写你的第一个单元测试
要使用Mocha和Chai编写第一个单元测试,首先让我们创建一个简单的函数,名为isNumValid,它对10和70之间的值返回真,对大于或等于70和小于或等于10的值返回假:
exports.isNumValid = function(num) {
if (num >= 70){
return false
} else if (num <= 10) {
eturn false
} else{
return true
}
}
现在我们可以编写测试来验证这个函数的行为。我们在测试目录下创建一个名为validator.js的新文件来编写这个测试:
const chai = require('chai')
const expect = chai.expect
const validator = require('../app/validator')
describe("validator isNumValid()", () => {
it("should return true for a number in between 10 and 70", ()=> {
expect(validator.isNumValid(39)).to.be.true
})
it("should return false when the number is less than or equal to 10", () => {
expect(validator.isNumValid(10)).to.be.false
})
it("should return false when the number is greater than or equal to 70", () => {
expect(validator.isNumValid(79)).to.be.false
})
})
你必须将validator.js文件导入我们的测试文件,以便访问被测试的函数。在我们的例子中,该函数被写在src目录下的validator.js文件中。
除了我们在这里使用的断言样式expect 外,Chai 还提供了另外两种断言样式,should和assert。你可以在Chai断言风格指南中阅读更多关于这些风格的区别和使用情况。
运行测试
使用npm test命令来运行这些测试。Mocha输出测试的结果,并在命令行上显示。
测试通过 成功输出
失败的测试案例
假设我们在编写isNumValid函数时犯了一个错误。我们忘记了添加大于(>)和小于(<)运算符的相等条件:
exports.isNumValid = function(num) {
if (num > 70) {
return false
} else if (num < 10) {
return false
} else {
return true
}
}
当我们再次运行测试时,一个测试案例由于这个错误而失败了:

测试失败的输出
由于单元测试的存在,我们能够很容易地抓住并纠正这样的错误,而不需要倾注数小时去寻找错误所在。
然而,在上述情况下,只有一个测试用例失败,尽管我们在两个布尔运算符上都犯了同样的错误。这是一个问题。但这是单元测试的缺点吗?
不,这是我们测试用例的缺陷。我们没有考虑到所有的输入场景,也没有为每个场景写单独的测试用例。我们将边界条件(当数字等于10和70时)与其他失败的测试条件相结合,而不是为每个条件写单独的测试用例。
因此,当遵循正确的单元测试实践时,我们的测试套件应该总共包含五个测试用例:
const chai = require('chai')
const expect = chai.expect
const validator = require('../app/validator')
describe("validator isNumValid()", () => {
it("should return true for a number in between 10 and 70", ()=> {
expect(validator.isNumValid(39)).to.be.true
})
it("should return false when the number is less 10", () => {
expect(validator.isNumValid(9)).to.be.false
})
it("should return false when the number is equal to 10", () => {
expect(validator.isNumValid(10)).to.be.false
})
it("should return false when the number is equal to 70", () => {
expect(validator.isNumValid(70)).to.be.false
})
it("should return false when the number is greater than 70", () => {
expect(validator.isNumValid(71)).to.be.false
})
})
总结
在这篇文章中,我们对测试、单元测试和Node.js中的测试进行了快速和简化的介绍。然而,这个教程只包含了与测试有关的基本概念。在下一篇文章中,我们将进一步探讨Node.js的测试主题。我们将讨论编写复杂的测试用例、集成测试和其他测试实践,如使用mocks和stubs。
谢谢你的阅读!