在上文最后一步中,我们手动测试了我们的应用程序。这将适用于个别用例,但随着我们的模块扩展,这种方法变得不那么可行。当我们测试新功能时,我们必须确定添加的功能没有在旧功能中产生问题。我们希望针对代码中的每次更改重新测试每个功能,但是手动执行此操作会花费大量精力并且容易出错。
更有效的做法是设置自动化测试。这些是像任何其他代码块一样编写的脚本测试。我们使用定义的输入运行我们的函数并检查它们的效果以确保它们的行为符合我们的预期。随着我们的代码库增长,我们的自动化测试也会增长。当我们在功能旁边编写新测试时,我们可以验证整个模块是否仍然有效——无需每次都记住如何使用每个功能。
在本教程中,我们将 Mocha 测试框架与 Node.jsassert模块一起使用。让我们获得一些实践经验,看看它们是如何协同工作的。
首先,创建一个新文件来存储我们的测试代码:
touch index.test.js
现在使用您喜欢的文本编辑器打开测试文件。您可以nano像以前一样使用:
nano index.test.js
在文本文件的第一行,我们将像在 Node.js shell 中一样加载 TODOs 模块。然后,我们将在编写测试时加载assert模块。添加以下行:
const Todos = require('./index');
const assert = require('assert').strict;
该模块的strict属性assert将允许我们使用 Node.js 推荐的特殊相等测试,并且有利于未来的验证,因为它们考虑了更多的用例。
在我们开始编写测试之前,让我们讨论一下 Mocha 如何组织我们的代码。在 Mocha 中构建的测试通常遵循以下模板:
describe([String with Test Group Name], function() {
it([String with Test Name], function() {
[Test Code]
});
});
注意两个关键功能:describe()和it()。该describe()函数用于对类似的测试进行分组。Mocha 不需要运行测试,但是分组测试使我们的测试代码更易于维护。建议您以一种便于您将相似的测试一起更新的方式对测试进行分组。
包含我们的it()测试代码。这是我们将与模块的功能交互并使用assert库的地方。许多it()函数可以在一个describe()函数中定义。
我们在本节中的目标是使用 Mocha 并assert自动化我们的手动测试。我们将从我们的描述块开始逐步执行此操作。在模块行之后将以下内容添加到您的文件中:
...
describe("integration test", function() {
});
使用这个代码块,我们为我们的集成测试创建了一个分组。单元测试将一次测试一个功能。集成测试验证模块内或模块之间的功能如何协同工作。当 Mocha 运行我们的测试时,该描述块中的所有测试都将在该"integration test"组下运行。
让我们添加一个it()函数,以便我们可以开始测试我们模块的代码:
...
describe("integration test", function() {
it("should be able to add and complete TODOs", function() {
});
});
请注意我们为测试命名的描述性。如果有人运行我们的测试,将立即清楚什么是通过或失败。一个经过良好测试的应用程序通常是一个文档齐全的应用程序,并且测试有时可以成为一种有效的文档。
对于我们的第一个测试,我们将创建一个新Todos对象并验证其中没有项目:
...
describe("integration test", function() {
it("should be able to add and complete TODOs", function() {
let todos = new Todos();
assert.notStrictEqual(todos.list().length, 1);
});
});
第一行新代码实例化了一个新Todos对象,就像我们在 Node.js REPL 或其他模块中所做的那样。在第二个新行中,我们使用assert模块。
从assert模块中我们使用notStrictEqual()方法。这个函数有两个参数:我们想要测试的值(称为actual值)和我们期望获得的值(称为expected值)。如果两个参数相同,则notStrictEqual()抛出错误以使测试失败。
保存并退出index.test.js。
基本情况将是真的,因为长度应该是0,而不是1。让我们通过运行 Mocha 来确认这一点。为此,我们需要修改我们的package.json文件。package.json使用文本编辑器打开文件:
nano package.json
现在,在您的scripts属性中,将其更改为如下所示:
...
"scripts": {
"test": "mocha index.test.js"
},
...
我们刚刚改变了 npm 的 CLItest命令的行为。当我们运行时npm test,npm 会检查我们刚刚输入的命令package.json。它将在我们的文件夹中查找 Mocha 库并使用我们的测试文件node_modules运行命令。
保存并退出package.json。
让我们看看运行测试时会发生什么。在您的终端中,输入:
npm test
该命令将产生以下输出:
Output
> todos@1.0.0 test your_file_path/todos
> mocha index.test.js
integrated test
? should be able to add and complete TODOs
1 passing (16ms)
这个输出首先向我们展示了它将运行哪组测试。对于组中的每个单独测试,测试用例都会缩进。我们看到我们在it()函数中描述的测试名称。测试用例左侧的勾号表示测试通过。
在底部,我们得到了所有测试的摘要。在我们的例子中,我们的一项测试通过并在 16 毫秒内完成(时间因计算机而异)。
我们的测试已经开始成功。但是,当前的测试用例可能会出现误报。误报是当它应该失败时通过的测试用例。
我们目前检查数组的长度是否不等于1。让我们修改测试,使这个条件在不应该成立时成立。将以下行添加到index.test.js:
...
describe("integration test", function() {
it("should be able to add and complete TODOs", function() {
let todos = new Todos();
todos.add("get up from bed");
todos.add("make up bed");
assert.notStrictEqual(todos.list().length, 1);
});
});
保存并退出文件。
我们添加了两个 TODO 项目。让我们运行测试看看会发生什么:
npm test
这将给出以下内容:
Output
...
integrated test
? should be able to add and complete TODOs
1 passing (8ms)
按预期通过,因为长度大于 1。但是,它违背了进行第一次测试的最初目的。第一个测试旨在确认我们从空白状态开始。更好的测试将在所有情况下证实这一点。
让我们更改测试,使其只有在我们绝对没有 TODO 存储时才能通过。对进行以下更改index.test.js:
...
describe("integration test", function() {
it("should be able to add and complete TODOs", function() {
let todos = new Todos();
todos.add("get up from bed");
todos.add("make up bed");
assert.strictEqual(todos.list().length, 0);
});
});
您更改notStrictEqual()为strictEqual(),一个检查其实际参数和预期参数是否相等的函数。如果我们的论点不完全相同,严格相等将失败。
保存并退出,然后运行测试,这样我们就可以看到会发生什么:
npm test
这一次,输出将显示错误:
Output
...
integration test
1) should be able to add and complete TODOs
0 passing (16ms)
1 failing
1) integration test
should be able to add and complete TODOs:
AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+ expected - actual
- 2
+ 0
+ expected - actual
-2
+0
at Context.<anonymous> (index.test.js:9:10)
npm ERR! Test failed. See above for more details.
我们的测试摘要不再位于输出的底部,而是显示在我们的测试用例列表之后:
...
0 passing (29ms)
1 failing
...
剩余的输出为我们提供了有关我们失败的测试的数据。首先,我们看看哪个测试用例失败了:
...
1) integrated test
should be able to add and complete TODOs:
...
然后,我们看到了为什么我们的测试失败了:
...
AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+ expected - actual
- 2
+ 0
+ expected - actual
-2
+0
at Context.<anonymous> (index.test.js:9:10)
...
失败AssertionError时抛出一个。strictEqual()我们看到expected值 0 与actual值 2 不同。
然后我们在测试文件中看到代码失败的行。在这种情况下,它是第 10 行。
现在,我们已经亲眼看到,如果我们预期不正确的值,我们的测试将会失败。让我们将测试用例改回正确的值。首先,打开文件:
nano index.test.js
然后取出这些todos.add行,使您的代码如下所示:
...
describe("integration test", function () {
it("should be able to add and complete TODOs", function () {
let todos = new Todos();
assert.strictEqual(todos.list().length, 0);
});
});
保存并退出文件。
再次运行它以确认它通过而没有任何潜在的误报:
npm test
输出如下:
Output
...
integration test
? should be able to add and complete TODOs
1 passing (15ms)
我们现在已经大大提高了测试的弹性。让我们继续我们的集成测试。下一步是将新的 TODO 项添加到index.test.js:
...
describe("integration test", function() {
it("should be able to add and complete TODOs", function() {
let todos = new Todos();
assert.strictEqual(todos.list().length, 0);
todos.add("run code");
assert.strictEqual(todos.list().length, 1);
assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);
});
});
使用该add()函数后,我们确认我们现在有一个 TODO 由我们的todos对象管理,带有strictEqual(). 我们的下一个测试证实了todoswith中的数据deepStrictEqual()。该deepStrictEqual()函数递归地测试我们的预期对象和实际对象是否具有相同的属性。在这种情况下,它会测试我们期望的数组中是否都有一个 JavaScript 对象。然后它检查它们的 JavaScript 对象是否具有相同的属性,即它们的title属性都是"run code"并且它们的completed属性都是false.
然后,我们通过添加以下突出显示的行,根据需要使用这两个相等检查来完成剩余的测试:
...
describe("integration test", function() {
it("should be able to add and complete TODOs", function() {
let todos = new Todos();
assert.strictEqual(todos.list().length, 0);
todos.add("run code");
assert.strictEqual(todos.list().length, 1);
assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);
todos.add("test everything");
assert.strictEqual(todos.list().length, 2);
assert.deepStrictEqual(todos.list(),
[
{ title: "run code", completed: false },
{ title: "test everything", completed: false }
]
);
todos.complete("run code");
assert.deepStrictEqual(todos.list(),
[
{ title: "run code", completed: true },
{ title: "test everything", completed: false }
]
);
});
});
保存并退出文件。
我们的测试现在模仿我们的手动测试。使用这些程序化测试,如果我们的测试在运行时通过,我们就不需要连续检查输出。您通常希望测试使用的各个方面,以确保正确测试代码。
npm test让我们再次运行我们的测试以获得这个熟悉的输出:
Output
...
integrated test
? should be able to add and complete TODOs
1 passing (9ms)
您现在已经使用 Mocha 框架和assert库设置了一个集成测试。
让我们考虑一下我们与其他一些开发人员共享我们的模块并且他们现在正在给我们反馈的情况。如果到目前为止还没有添加 TODO ,我们的很大一部分用户希望该complete()函数返回错误。让我们在我们的函数中添加这个complete()功能。
index.js在文本编辑器中打开:
nano index.js
将以下内容添加到函数中:
...
complete(title) {
if (this.todos.length === 0) {
throw new Error("You have no TODOs stored. Why don't you add one first?");
}
let todoFound = false
this.todos.forEach((todo) => {
if (todo.title === title) {
todo.completed = true;
todoFound = true;
return;
}
});
if (!todoFound) {
throw new Error(`No TODO was found with the title: "${title}"`);
}
}
...
保存并退出文件。
现在让我们为这个新特性添加一个新的测试。我们想验证如果我们对一个Todos没有项目的对象调用 complete ,它将返回我们的特殊错误。
回到index.test.js:
nano index.test.js
在文件末尾,添加以下代码:
...
describe("complete()", function() {
it("should fail if there are no TODOs", function() {
let todos = new Todos();
const expectedError = new Error("You have no TODOs stored. Why don't you add one first?");
assert.throws(() => {
todos.complete("doesn't exist");
}, expectedError);
});
});
我们的测试从创建一个新todos对象开始。然后我们定义我们在调用complete()函数时期望收到的错误。
接下来,我们使用模块的throws()功能assert。创建了这个函数,以便我们可以验证代码中抛出的错误。它的第一个参数是一个包含引发错误的代码的函数。第二个参数是我们期望收到的错误。
在您的终端中,npm test再次运行测试,您现在将看到以下输出:
Output
...
integrated test
? should be able to add and complete TODOs
complete()
? should fail if there are no TODOs
2 passing (25ms)
至此,我们的测试已经验证了同步代码的结果。让我们看看我们需要如何验证异步代码。
本文暂时没有评论,来添加一个吧(●'◡'●)