node mocha

The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

作者选择了“ 开放互联网/言论自由基金会”作为“ Write for DOnations”计划的一部分来接受捐赠。

介绍 (Introduction)

Testing is an integral part of software development. It’s common for programmers to run code that tests their application as they make changes in order to confirm it’s behaving as they’d like. With the right test setup, this process can even be automated, saving a lot of time. Running tests consistently after writing new code ensures that new changes don’t break pre-existing features. This gives the developer confidence in their code base, especially when it gets deployed to production so users can interact with it.

测试是软件开发不可或缺的一部分。 程序员在更改代码时运行测试其应用程序的代码,以确认其行为是否正常,这很常见。 有了正确的测试设置,该过程甚至可以实现自动化,从而节省大量时间。 在编写新代码后一致地运行测试可确保新的更改不会破坏现有功能。 这使开发人员对他们的代码库充满信心,尤其是在将代码部署到生产环境中以便用户可以与其交互时。

A test framework structures the way we create test cases. Mocha is a popular JavaScript test framework that organizes our test cases and runs them for us. However, Mocha does not verify our code’s behavior. To compare values in a test, we can use the Node.js assert module.

测试框架构建了我们创建测试用例的方式。 Mocha是一种流行JavaScript测试框架,可以组织我们的测试用例并为我们运行它们。 但是,Mocha不会验证我们代码的行为。 为了比较测试中的值,我们可以使用Node.js assert模块。

In this article, you’ll write tests for a Node.js TODO list module. You will set up and use the Mocha test framework to structure your tests. Then you’ll use the Node.js assert module to create the tests themselves. In this sense, you will be using Mocha as a plan builder, and assert to implement the plan.

在本文中,您将为Node.js TODO列表模块编写测试。 您将建立并使用Mocha测试框架来构建测试。 然后,您将使用Node.js assert模块自己创建测试。 从这个意义上讲,您将使用Mocha作为计划构建器,并assert要实施计划。

先决条件 (Prerequisites)

  • Node.js installed on your development machine. This tutorial uses Node.js version 10.16.0. To install this on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.

    在您的开发机器上安装了Node.js。 本教程使用Node.js版本10.16.0。 要将其安装在macOS或Ubuntu 18.04上,请遵循如何在macOS上安装Node.js并创建本地开发环境中的步骤,或如何在Ubuntu 18.04上安装Node.js的 使用PPA安装部分中的步骤 。

  • A basic knowledge of JavaScript, which you can find in our How To Code in JavaScript series.

    JavaScript的基础知识,您可以在我们的“ 如何编写JavaScript代码”系列中找到 。

步骤1 —编写节点模块 (Step 1 — Writing a Node Module)

Let’s begin this article by writing the Node.js module we’d like to test. This module will manage a list of TODO items. Using this module, we will be able to list all the TODOs that we are keeping track of, add new items, and mark some as complete. Additionally, we’ll be able to export a list of TODO items to a CSV file. If you’d like a refresher on writing Node.js modules, you can read our article on How To Create a Node.js Module.

让我们从编写我们要测试的Node.js模块开始。 该模块将管理TODO项目列表。 使用此模块,我们将能够列出所有要跟踪的待办事项,添加新项目并将其标记为已完成。 此外,我们将能够将TODO项目列表导出到CSV文件。 如果您想重新编写Node.js模块,可以阅读有关如何创建Node.js模块的文章 。

First, we need to set up the coding environment. Create a folder with the name of your project in your terminal. This tutorial will use the name todos:

首先,我们需要设置编码环境。 在终端中创建一个带有项目名称的文件夹。 本教程将使用名称todos

  • mkdir todos

    麦克迪尔· 托多斯

Then enter that folder:

然后输入该文件夹:

  • cd todos cd待办事项

Now initialize npm, since we’ll be using its CLI functionality to run the tests later:

现在初始化npm ,因为我们稍后将使用其CLI功能来运行测试:

  • npm init -y npm初始化-y

We only have one dependency, Mocha, which we will use to organize and run our tests. To download and install Mocha, use the following:

我们只有一个依赖项Mocha,我们将使用它来组织和运行测试。 要下载并安装Mocha,请使用以下命令:

  • npm i request --save-dev mocha npm我要求--save-dev摩卡

We install Mocha as a dev dependency, as it’s not required by the module in a production setting. If you would like to learn more about Node.js packages or npm, check out our guide on How To Use Node.js Modules with npm and package.json.

我们将Mocha安装为dev依赖项,因为生产环境中的模块并不需要它。 如果您想了解有关Node.js软件包或npm的更多信息,请查看我们的有关如何将Node.js模块与npm和package.json一起使用的指南。

Finally, let’s create our file that will contain our module’s code:

最后,让我们创建一个包含模块代码的文件:

  • touch index.js 触摸index.js

With that, we’re ready to create our module. Open index.js in a text editor like nano:

这样,我们就可以创建模块了。 在类似于nano的文本编辑器中打开index.js

  • nano index.js 纳米index.js

Let’s begin by defining the Todos class. This class contains all the functions that we need to manage our TODO list. Add the following lines of code to index.js:

让我们从定义Todos 类开始。 此类包含管理TODO列表所需的所有功能 。 将以下代码行添加到index.js

todos/index.js
待办事项/ index.js
class Todos {constructor() {this.todos = [];}
}module.exports = Todos;

We begin the file by creating a Todos class. Its constructor() function takes no arguments, therefore we don’t need to provide any values to instantiate an object for this class. All we do when we initialize a Todos object is create a todos property that’s an empty array.

我们通过创建Todos类开始文件。 它的constructor()函数不带参数,因此我们不需要提供任何值来实例化此类的对象。 初始化Todos对象时,我们要做的就是创建一个todos属性,该属性是一个空数组 。

The modules line allows other Node.js modules to require our Todos class. Without explicitly exporting the class, the test file that we will create later would not be able to use it.

modules行允许其他Node.js模块需要我们的Todos类。 如果没有显式导出类,我们稍后将创建的测试文件将无法使用它。

Let’s add a function to return the array of todos we have stored. Write in the following highlighted lines:

让我们添加一个函数来返回已存储的todos数组。 写下以下突出显示的行:

todos/index.js
待办事项/ index.js
class Todos {constructor() {this.todos = [];}list() {return [...this.todos];}
}module.exports = Todos;

Our list() function returns a copy of the array that’s used by the class. It makes a copy of the array by using JavaScript’s destructuring syntax. We make a copy of the array so that changes the user makes to the array returned by list() does not affect the array used by the Todos object.

我们的list()函数返回该类使用的数组的副本。 它使用JavaScript的解构语法制作数组的副本。 我们复制数组,以便用户对list()返回的数组所做的更改不会影响Todos对象使用的数组。

Note: JavaScript arrays are reference types. This means that for any variable assignment to an array or function invocation with an array as a parameter, JavaScript refers to the original array that was created. For example, if we have an array with three items called x, and create a new variable y such that y = x, y and x both refer to the same thing. Any changes we make to the array with y impacts variable x and vice versa.

注意: JavaScript数组是引用类型 。 这意味着对于任何分配给数组的变量或以数组为参数的函数调用,JavaScript均指创建的原始数组。 例如,如果我们有一个包含三个称为x项的数组,并创建一个新变量y ,使得y = x ,则yx都引用同一件事。 我们对带有y的数组所做的任何更改都会影响变量x ,反之亦然。

Now let’s write the add() function, which adds a new TODO item:

现在让我们编写add()函数,该函数添加一个新的TODO项:

todos/index.js
待办事项/ index.js
class Todos {constructor() {this.todos = [];}list() {return [...this.todos];}add(title) {let todo = {title: title,completed: false,}this.todos.push(todo);}
}module.exports = Todos;

Our add() function takes a string, and places it in a new JavaScript object’s title property. The new object also has a completed property, which is set to false by default. We then add this new object to our array of TODOs.

我们的add()函数接受一个字符串,并将其放在新JavaScript 对象的title属性中。 新对象还具有completed属性,默认情况下将其设置为false 。 然后,我们将此新对象添加到我们的TODO数组中。

Important functionality in a TODO manager is to mark items as completed. For this implementation, we will loop through our todos array to find the TODO item the user is searching for. If one is found, we’ll mark it as completed. If none is found, we’ll throw an error.

TODO管理器中的重要功能是将项目标记为已完成。 对于此实现,我们将遍历todos数组以查找用户正在搜索的TODO项。 如果找到一个,我们会将其标记为已完成。 如果没有找到,我们将抛出错误。

Add the complete() function like this:

像这样添加complete()函数:

todos/index.js
待办事项/ index.js
class Todos {constructor() {this.todos = [];}list() {return [...this.todos];}add(title) {let todo = {title: title,completed: false,}this.todos.push(todo);}complete(title) {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}"`);}}
}module.exports = Todos;

Save the file and exit from the text editor.

保存文件并退出文本编辑器。

We now have a basic TODO manager that we can experiment with. Next, let’s manually test our code to see if the application is working.

现在,我们有了一个可以试用的基本TODO管理器。 接下来,让我们手动测试代码以查看应用程序是否正常运行。

第2步-手动测试代码 (Step 2 — Manually Testing the Code)

In this step, we will run our code’s functions and observe the output to ensure it matches our expectations. This is called manual testing. It’s likely the most common testing methodology programmers apply. Although we will automate our testing later with Mocha, we will first manually test our code to give a better sense of how manual testing differs from testing frameworks.

在这一步中,我们将运行代码的功能并观察输出,以确保其符合我们的期望。 这称为手动测试 。 程序员可能会采用最常见的测试方法。 尽管稍后我们将使用Mocha使测试自动化,但我们将首先手动测试代码,以更好地了解手动测试与测试框架的不同之处。

Let’s add two TODO items to our app and mark one as complete. Start the Node.js REPL in the same folder as the index.js file:

让我们将两个待办事项添加到我们的应用程序中,然后将其中一个标记为完成。 在与index.js文件相同的文件夹中启动Node.js REPL :

  • node 节点

You will see the > prompt in the REPL that tells us we can enter JavaScript code. Type the following at the prompt:

您将在REPL中看到>提示,告诉我们我们可以输入JavaScript代码。 在提示符下键入以下内容:

  • const Todos = require('./index'); const Todos = require('./ index');

With require(), we load the TODOs module into a Todos variable. Recall that our module returns the Todos class by default.

使用require() ,我们将TODOs模块加载到Todos变量中。 回想一下,我们的模块默认情况下返回Todos类。

Now, let’s instantiate an object for that class. In the REPL, add this line of code:

现在,让我们为该类实例化一个对象。 在REPL中,添加以下代码行:

  • const todos = new Todos(); const todos = new Todos();

We can use the todos object to verify our implementation works. Let’s add our first TODO item:

我们可以使用todos对象来验证我们的实现工作。 让我们添加第一个TODO项:

  • todos.add("run code"); todos.add(“运行代码”);

So far we have not seen any output in our terminal. Let’s verify that we’ve stored our "run code" TODO item by getting a list of all our TODOs:

到目前为止,我们还没有在终端中看到任何输出。 让我们通过获取所有待办事项列表来验证是否已存储"run code"待办事项:

  • todos.list(); todos.list();

You will see this output in your REPL:

您将在REPL中看到以下输出:

Output
[ { title: 'run code', completed: false } ]

This is the expected result: We have one TODO item in our array of TODOs, and it’s not completed by default.

这是预期的结果:TODO数组中有一个TODO项,并且默认情况下未完成。

Let’s add another TODO item:

让我们添加另一个待办事项:

  • todos.add("test everything");todos.add(“测试所有内容”);

Mark the first TODO item as completed:

将第一个待办事项标记为已完成:

  • todos.complete("run code");todos.complete(“运行代码”);

Our todos object will now be managing two items: "run code" and "test everything". The "run code" TODO will be completed as well. Let’s confirm this by calling list() once again:

现在,我们的todos对象将管理两个项目: "run code""test everything""run code" TODO也将完成。 让我们再次调用list()确认这一点:

  • todos.list(); todos.list();

The REPL will output:

REPL将输出:

Output
[{ title: 'run code', completed: true },{ title: 'test everything', completed: false }
]

Now, exit the REPL with the following:

现在,使用以下命令退出REPL:

  • .exit。出口

We’ve confirmed that our module behaves as we expect it to. While we didn’t put our code in a test file or use a testing library, we did test our code manually. Unfortunately, this form of testing becomes time consuming to do every time we make a change. Next, let’s use automated testing in Node.js and see if we can solve this problem with the Mocha testing framework.

我们已经确认我们的模块的行为符合我们的预期。 虽然我们没有将代码放入测试文件或使用测试库,但我们确实手动测试了代码。 不幸的是,每次进行更改时,这种测试形式都变得很耗时。 接下来,让我们在Node.js中使用自动化测试,看看是否可以通过Mocha测试框架解决此问题。

第3步-使用Mocha和Assert编写您的第一个测试 (Step 3 — Writing Your First Test with Mocha and Assert)

In the last step, we manually tested our application. This will work for individual use cases, but as our module scales, this method becomes less viable. As we test new features, we must be certain that the added functionality has not created problems in the old functionality. We would like to test each feature over again for every change in the code, but doing this by hand would take a lot of effort and would be prone to error.

在最后一步,我们手动测试了我们的应用程序。 这将适用于个别用例,但是随着我们模块的扩展,此方法变得不可行。 在测试新功能时,必须确定添加的功能没有在旧功能中造成问题。 我们希望针对代码中的每个更改重新测试每个功能,但是手动执行此操作会很费力,并且容易出错。

A more efficient practice would be to set up automated tests. These are scripted tests written like any other code block. We run our functions with defined inputs and inspect their effects to ensure they behave as we expect. As our codebase grows, so will our automated tests. When we write new tests alongside the features, we can verify the entire module still works—all without having to remember how to use each function every time.

一种更有效的做法是设置自动化测试 。 这些是脚本测试,就像其他任何代码块一样编写。 我们使用定义的输入来运行我们的函数,并检查它们的效果以确保它们的行为符合我们的预期。 随着我们代码库的增长,我们的自动化测试也将随之增长。 当我们在功能旁边编写新的测试时,我们可以验证整个模块是否仍在工作-所有这些都无需记住每次如何使用每个功能。

In this tutorial, we’re using the Mocha testing framework with the Node.js assert module. Let’s get some hands-on experience to see how they work together.

在本教程中,我们将Mocha测试框架与Node.js assert模块一起使用。 让我们获得一些实践经验,以了解它们如何协同工作。

To begin, create a new file to store our test code:

首先,创建一个新文件来存储我们的测试代码:

  • touch index.test.js 触摸index.test.js

Now use your preferred text editor to open the test file. You can use nano like before:

现在,使用首选的文本编辑器打开测试文件。 您可以像以前一样使用nano

  • nano index.test.js 纳米index.test.js

In the first line of the text file, we will load the TODOs module like we did in the Node.js shell. We will then load the assert module for when we write our tests. Add the following lines:

在文本文件的第一行中,我们将像在Node.js shell中一样加载TODOs模块。 然后,我们将在编写测试时加载assert模块。 添加以下行:

todos/index.test.js
待办事项/ index.test.js
const Todos = require('./index');
const assert = require('assert').strict;

The strict property of the assert module will allow us to use special equality tests that are recommended by Node.js and are good for future-proofing, since they account for more use cases.

assert模块的strict属性将使我们能够使用Node.js建议的特殊相等性测试,并且这些标准对将来很有用,因为它们占了更多的用例。

Before we go into writing tests, let’s discuss how Mocha organizes our code. Tests structured in Mocha usually follow this template:

在开始编写测试之前,让我们讨论一下Mocha如何组织代码。 在Mocha中进行结构化的测试通常遵循以下模板:

describe([String with Test Group Name], function() {it([String with Test Name], function() {[Test Code]});
});

Notice two key functions: describe() and it(). The describe() function is used to group similar tests. It’s not required for Mocha to run tests, but grouping tests make our test code easier to maintain. It’s recommended that you group your tests in a way that’s easy for you to update similar ones together.

注意两个关键功能: describe()it()describe()函数用于对相似的测试进行分组。 Mocha不需要运行测试,但是对测试进行分组可以使我们的测试代码更易于维护。 建议您以一种易于组合在一起更新相似测试的方式对测试进行分组。

The it() contains our test code. This is where we would interact with our module’s functions and use the assert library. Many it() functions can be defined in a describe() function.

it()包含我们的测试代码。 在这里我们可以与模块的功能进行交互并使用assert库。 可以在describe()函数中定义许多it() describe()函数。

Our goal in this section is to use Mocha and assert to automate our manual test. We’ll do this step-by-step, beginning with our describe block. Add the following to your file after the module lines:

我们在本节中的目标是使用Mocha和assert来自动化我们的手动测试。 我们将从我们的describe块开始逐步进行此操作。 在模块行之后将以下内容添加到您的文件中:

todos/index.test.js
待办事项/ index.test.js
...
describe("integration test", function() {
});

With this code block, we’ve created a grouping for our integrated tests. Unit tests would test one function at a time. Integration tests verify how well functions within or across modules work together. When Mocha runs our test, all the tests within that describe block will run under the "integration test" group.

通过此代码块,我们为集成测试创建了分组。 单元测试可以一次测试一个功能。 集成测试可验证模块内部或模块之间的功能如何协同工作。 当Mocha运行我们的测试时,该describe块中的所有测试将在"integration test"组下运行。

Let’s add an it() function so we can begin testing our module’s code:

让我们添加一个it()函数,以便我们可以开始测试模块的代码:

todos/index.test.js
待办事项/ index.test.js
...
describe("integration test", function() {it("should be able to add and complete TODOs", function() {});
});

Notice how descriptive we made the test’s name. If anyone runs our test, it will be immediately clear what’s passing or failing. A well-tested application is typically a well-documented application, and tests can sometimes be an effective kind of documentation.

请注意,我们为测试的名称做了描述。 如果有人运行我们的测试,则将立即清除通过或未通过的内容。 一个经过良好测试的应用程序通常是一个文档齐全的应用程序,而测试有时可以是一种有效的文档。

For our first test, we will create a new Todos object and verify it has no items in it:

对于我们的第一个测试,我们将创建一个新的Todos对象并验证其中是否没有项目:

todos/index.test.js
待办事项/ index.test.js
...
describe("integration test", function() {it("should be able to add and complete TODOs", function() {let todos = new Todos();assert.notStrictEqual(todos.list().length, 1);});
});

The first new line of code instantiated a new Todos object as we would do in the Node.js REPL or another module. In the second new line, we use the assert module.

新的第一行代码实例化了一个新的Todos对象,就像在Node.js REPL或其他模块中所做的那样。 在第二行中,我们使用assert模块。

From the assert module we use the notStrictEqual() method. This function takes two parameters: the value that we want to test (called the actual value) and the value we expect to get (called the expected value). If both arguments are the same, notStrictEqual() throws an error to fail the test.

assert模块中,我们使用notStrictEqual()方法。 该函数有两个参数:我们要测试的值(称为actual值)和我们期望获得的值(称为expected )。 如果两个参数相同,则notStrictEqual()会引发错误以使测试失败。

Save and exit from index.test.js.

保存并退出index.test.js

The base case will be true as the length should be 0, which isn’t 1. Let’s confirm this by running Mocha. To do this, we need to modify our package.json file. Open your package.json file with your text editor:

基本情况将是正确的,因为长度应为0 ,而不是1 。 让我们通过运行Mocha确认这一点。 为此,我们需要修改package.json文件。 使用文本编辑器打开package.json文件:

  • nano package.json 纳米package.json

Now, in your scripts property, change it so it looks like this:

现在,在您的scripts属性中,对其进行更改,使其如下所示:

todos/package.json
todos / package.json
...
"scripts": {"test": "mocha index.test.js"
},
...

We have just changed the behavior of npm’s CLI test command. When we run npm test, npm will review the command we just entered in package.json. It will look for the Mocha library in our node_modules folder and run the mocha command with our test file.

我们刚刚更改了npm的CLI test命令的行为。 当我们运行npm test ,npm将查看我们刚刚在package.json输入的命令。 它将在我们的node_modules文件夹中查找Mocha库,并使用我们的测试文件运行mocha命令。

Save and exit package.json.

保存并退出package.json

Let’s see what happens when we run our test. In your terminal, enter:

让我们看看运行测试时会发生什么。 在您的终端中,输入:

  • npm test npm测试

The command will produce the following output:

该命令将产生以下输出:

Output
> todos@1.0.0 test your_file_path/todos
> mocha index.test.jsintegrated test✓ should be able to add and complete TODOs1 passing (16ms)

This output first shows us which group of tests it is about to run. For every individual test within a group, the test case is indented. We see our test name as we described it in the it() function. The tick at the left side of the test case indicates that the test passed.

此输出首先向我们显示它将要运行哪组测试。 对于组内的每个测试,测试用例都会缩进。 我们看到了我们在it()函数中描述的测试名称。 测试用例左侧的对勾表示测试已通过。

At the bottom, we get a summary of all our tests. In our case, our one test is passing and was completed in 16ms (the time varies from computer to computer).

在底部,我们获得了所有测试的摘要。 在我们的案例中,我们的一项测试通过并在16毫秒内完成(时间因计算机而异)。

Our testing has started with success. However, this current test case can allow for false-positives. A false-positive is a test case that passes when it should fail.

我们的测试从成功开始。 但是,当前的测试用例可能会出现假阳性。 假阳性是应该失败的测试案例。

We currently check that the length of the array is not equal to 1. Let’s modify the test so that this condition holds true when it should not. Add the following lines to index.test.js:

当前,我们检查数组的长度不等于1 。 让我们修改测试,以使此条件在不应该成立时成立。 index.test.js添加到index.test.js

todos/index.test.js
待办事项/ 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);});
});

Save and exit the file.

保存并退出文件。

We added two TODO items. Let’s run the test to see what happens:

我们添加了两个待办事项。 让我们运行测试看看会发生什么:

  • npm test npm测试

This will give the following:

这将给出以下内容:

Output
...
integrated test✓ should be able to add and complete TODOs1 passing (8ms)

This passes as expected, as the length is greater than 1. However, it defeats the original purpose of having that first test. The first test is meant to confirm that we start on a blank state. A better test will confirm that in all cases.

当长度大于1时,这按预期方式通过。但是,它违反了进行第一个测试的初衷。 第一个测试旨在确认我们从空白状态开始。 更好的测试将在所有情况下确认这一点。

Let’s change the test so it only passes if we have absolutely no TODOs in store. Make the following changes to index.test.js:

让我们更改测试,使其仅在我们绝对没有待办事项的情况下才通过。 对index.test.js进行以下更改:

todos/index.test.js
待办事项/ 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);});
});

You changed notStrictEqual() to strictEqual(), a function that checks for equality between its actual and expected argument. Strict equal will fail if our arguments are not exactly the same.

您将notStrictEqual()更改为strictEqual() ,该函数检查其实际参数和预期参数之间的相等性。 如果我们的参数不完全相同,则严格相等将失败。

Save and exit, then run the test so we can see what happens:

保存并退出,然后运行测试,以便我们看到发生了什么:

  • npm test npm测试

This time, the output will show an error:

这次,输出将显示错误:

Output
...integration test1) should be able to add and complete TODOs0 passing (16ms)1 failing1) integration testshould 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+0at Context.<anonymous> (index.test.js:9:10)npm ERR! Test failed.  See above for more details.

This text will be useful for us to debug why the test failed. Notice that since the test failed there was no tick at the beginning of the test case.

这段文字对于我们调试测试失败的原因非常有用。 注意,由于测试失败,因此在测试用例的开头没有任何滴答声。

Our test summary is no longer at the bottom of the output, but right after our list of test cases were displayed:

我们的测试摘要不再位于输出的底部,而是在显示测试用例列表之后:

...
0 passing (29ms)1 failing
...

The remaining output provides us with data about our failing tests. First, we see what test case has failed:

其余的输出为我们提供了有关失败测试的数据。 首先,我们看看什么测试用例失败了:

...
1) integrated testshould be able to add and complete TODOs:
...

Then, we see why our test failed:

然后,我们了解为什么测试失败:

...AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
+ expected - actual- 2
+ 0+ expected - actual-2+0at Context.<anonymous> (index.test.js:9:10)
...

An AssertionError is thrown when strictEqual() fails. We see that the expected value, 0, is different from the actual value, 2.

strictEqual()失败时,抛出AssertionError 。 我们看到expected 0与actual值2不同。

We then see the line in our test file where the code fails. In this case, it’s line 10.

然后,我们在测试文件中看到代码失败的那一行。 在这种情况下,它是第10行。

Now, we’ve seen for ourselves that our test will fail if we expect incorrect values. Let’s change our test case back to its right value. First, open the file:

现在,我们亲眼看到如果期望值不正确,则测试将失败。 让我们将测试用例更改回正确的值。 首先,打开文件:

  • nano index.test.js 纳米index.test.js

Then take out the todos.add lines so that your code looks like the following:

然后取出todos.add行,以便您的代码如下所示:

todos/index.test.js
待办事项/ 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);});
});

Save and exit the file.

保存并退出文件。

Run it once more to confirm that it passes without any potential false-positives:

再次运行它,以确认它通过且没有任何潜在的假阳性:

  • npm test npm测试

The output will be as follows:

输出将如下所示:

Output
...
integration test✓ should be able to add and complete TODOs1 passing (15ms)

We’ve now improved our test’s resiliency quite a bit. Let’s move forward with our integration test. The next step is to add a new TODO item to index.test.js:

现在,我们已经大大提高了测试的弹性。 让我们继续进行集成测试。 下一步是向index.test.js添加一个新的TODO项:

todos/index.test.js
待办事项/ 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}]);});
});

After using the add() function, we confirm that we now have one TODO being managed by our todos object with strictEqual(). Our next test confirms the data in the todos with deepStrictEqual(). The deepStrictEqual() function recursively tests that our expected and actual objects have the same properties. In this case, it tests that the arrays we expect both have a JavaScript object within them. It then checks that their JavaScript objects have the same properties, that is, that both their title properties are "run code" and both their completed properties are false.

使用add()函数后,我们确认现在有一个待办事项,由我们的todos对象使用strictEqual() 。 我们的下一个试验证实了该数据todosdeepStrictEqual() deepStrictEqual()函数以递归方式测试我们的预期对象和实际对象具有相同的属性。 在这种情况下,它将测试我们期望的数组中都包含一个JavaScript对象。 然后,检查其JavaScript对象是否具有相同的属性,即,它们的两个title属性均为"run code"并且两个其completed属性均为false

We then complete the remaining tests using these two equality checks as needed by adding the following highlighted lines:

然后,通过添加以下突出显示的行,根据需要使用这两个相等性检查来完成其余测试:

todos/index.test.js
待办事项/ 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}]);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 }]);});
});

Save and exit the file.

保存并退出文件。

Our test now mimics our manual test. With these programmatic tests, we don’t need to check the output continuously if our tests pass when we run them. You typically want to test every aspect of use to make sure the code is tested properly.

现在,我们的测试模仿了手动测试。 使用这些程序化测试,如果我们在运行测试时通过测试,则无需连续检查输出。 您通常希望测试使用的各个方面,以确保代码已正确测试。

Let’s run our test with npm test once more to get this familiar output:

让我们再次使用npm test ,以得到熟悉的输出:

Output
...
integrated test✓ should be able to add and complete TODOs1 passing (9ms)

You’ve now set up an integrated test with the Mocha framework and the assert library.

现在,您已经使用Mocha框架和assert库建立了集成测试。

Let’s consider a situation where we’ve shared our module with some other developers and they’re now giving us feedback. A good portion of our users would like the complete() function to return an error if no TODOs were added as of yet. Let’s add this functionality in our complete() function.

让我们考虑一种情况,我们已经与其他一些开发人员共享了我们的模块,而他们现在正在向我们提供反馈。 如果到目前为止还没有添加TODO,我们的很大一部分用户希望complete()函数返回错误。 让我们在complete()函数中添加此功能。

Open index.js in your text editor:

在文本编辑器中打开index.js

  • nano index.js 纳米index.js

Add the following to the function:

在函数中添加以下内容:

todos/index.js
待办事项/ 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 = falsethis.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}"`);}
}
...

Save and exit the file.

保存并退出文件。

Now let’s add a new test for this new feature. We want to verify that if we call complete on a Todos object that has no items, it will return our special error.

现在让我们为此功能添加一个新测试。 我们要验证是否对没有项目的Todos对象调用complete,它将返回我们的特殊错误。

Go back into index.test.js:

返回index.test.js

  • nano index.test.js 纳米index.test.js

At the end of the file, add the following code:

在文件末尾,添加以下代码:

todos/index.test.js
待办事项/ 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);});
});

We use describe() and it() like before. Our test begins with creating a new todos object. We then define the error we are expecting to receive when we call the complete() function.

我们像以前一样使用describe()it() 。 我们的测试从创建一个新的todos对象开始。 然后,我们定义在调用complete()函数时预期会收到的错误。

Next, we use the throws() function of the assert module. This function was created so we can verify the errors that are thrown in our code. Its first argument is a function that contains the code that throws the error. The second argument is the error we are expecting to receive.

接下来,我们使用assert模块的throws()函数。 已创建此函数,因此我们可以验证代码中引发的错误。 它的第一个参数是一个函数,该函数包含引发错误的代码。 第二个参数是我们期望收到的错误。

In your terminal, run the tests with npm test once again and you will now see the following output:

在您的终端中,再次使用npm test运行测试,现在您将看到以下输出:

Output
...
integrated test✓ should be able to add and complete TODOscomplete()✓ should fail if there are no TODOs2 passing (25ms)

This output highlights the benefit of why we do automated testing with Mocha and assert. Because our tests are scripted, every time we run npm test, we verify that all our tests are passing. We did not need to manually check if the other code is still working; we know that it is because the test we have still passed.

此输出突出显示了为什么我们要使用Mocha和assert进行自动化测试的好处。 因为我们的测试是脚本编写的,所以每次我们运行npm test ,我们都要验证所有测试都通过了。 我们不需要手动检查其他代码是否仍然有效; 我们知道那是因为我们仍然通过了测试。

So far, our tests have verified the results of synchronous code. Let’s see how we would need to adapt our newfound testing habits to work with asynchronous code.

到目前为止,我们的测试已经验证了同步代码的结果。 让我们看看我们需要如何适应新发现的测试习惯以使用异步代码。

第4步-测试异步代码 (Step 4 — Testing Asynchronous Code)

One of the features we want in our TODO module is a CSV export feature. This will print all the TODOs we have in store along with the completed status to a file. This requires that we use the fs module—a built-in Node.js module for working with the file system.

我们希望在TODO模块中使用的功能之一是CSV导出功能。 这会将我们存储的所有待办事项以及完成的状态打印到文件中。 这要求我们使用fs模块-内置的Node.js模块,用于处理文件系统。

Writing to a file is an asynchronous operation. There are many ways to write to a file in Node.js. We can use callbacks, Promises, or the async/await keywords. In this section, we’ll look at how we write tests for those different methods.

写入文件是异步操作 。 有许多方法可以写入Node.js中的文件。 我们可以使用回调,Promises或async / await关键字。 在本节中,我们将研究如何为这些不同的方法编写测试。

回呼 (Callbacks)

A callback function is one used as an argument to an asynchronous function. It is called when the asynchronous operation is completed.

回调函数是用作异步函数的参数的函数。 异步操作完成时调用它。

Let’s add a function to our Todos class called saveToFile(). This function will build a string by looping through all our TODO items and writing that string to a file.

让我们向Todos类添加一个名为saveToFile()的函数。 该函数将通过遍历我们所有的TODO项目并将该字符串写入文件来构建字符串。

Open your index.js file:

打开您的index.js文件:

  • nano index.js 纳米index.js

In this file, add the following highlighted code:

在此文件中,添加以下突出显示的代码:

todos/index.js
待办事项/ index.js
const fs = require('fs');class Todos {constructor() {this.todos = [];}list() {return [...this.todos];}add(title) {let todo = {title: title,completed: false,}this.todos.push(todo);}complete(title) {if (this.todos.length === 0) {throw new Error("You have no TODOs stored. Why don't you add one first?");}let todoFound = falsethis.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}"`);}}saveToFile(callback) {let fileContents = 'Title,Completed\n';this.todos.forEach((todo) => {fileContents += `${todo.title},${todo.completed}\n`});fs.writeFile('todos.csv', fileContents, callback);}
}module.exports = Todos;

We first have to import the fs module in our file. Then we added our new saveToFile() function. Our function takes a callback function that will be used once the file write operation is complete. In that function, we create a fileContents variable that stores the entire string we want to be saved as a file. It’s initialized with the CSV’s headers. We then loop through each TODO item with the internal array’s forEach() method. As we iterate, we add the title and completed properties of the individual todos objects.

我们首先必须在文件中导入fs模块。 然后,我们添加了新的saveToFile()函数。 我们的函数带有一个回调函数,一旦文件写入操作完成,该函数将被使用。 在该函数中,我们创建一个fileContents变量,该变量存储我们要保存为文件的整个字符串。 它使用CSV的标头进行了初始化。 然后,我们使用内部数组的forEach()方法遍历每个TODO项。 进行迭代时,我们添加了各个todos对象的titlecompleted属性。

Finally, we use the fs module to write the file with the writeFile() function. Our first argument is the file name: todos.csv. The second is the contents of the file, in this case, our fileContents variable. Our last argument is our callback function, which handles any file writing errors.

最后,我们使用fs模块通过writeFile()函数写入文件。 我们的第一个参数是文件名: todos.csv 。 第二个是文件的内容,在这种情况下,就是我们的fileContents变量。 最后一个参数是回调函数,该函数处理任何文件写入错误。

Save and exit the file.

保存并退出文件。

Let’s now write a test for our saveToFile() function. Our test will do two things: confirm that the file exists in the first place, and then verify that it has the right contents.

现在让我们为saveToFile()函数编写一个测试。 我们的测试将做两件事:首先确认文件存在,然后确认文件内容正确。

Open the index.test.js file:

打开index.test.js文件:

  • nano index.test.js 纳米index.test.js

let’s begin by loading the fs module at the top of the file, as we’ll use it to help test our results:

让我们首先在文件顶部加载fs模块,因为我们将使用它来测试结果:

todos/index.test.js
待办事项/ index.test.js
const Todos = require('./index');
const assert = require('assert').strict;
const fs = require('fs');
...

Now, at the end of the file let’s add our new test case:

现在,在文件末尾,让我们添加新的测试用例:

todos/index.test.js
待办事项/ index.test.js
...
describe("saveToFile()", function() {it("should save a single TODO", function(done) {let todos = new Todos();todos.add("save a CSV");todos.saveToFile((err) => {assert.strictEqual(fs.existsSync('todos.csv'), true);let expectedFileContents = "Title,Completed\nsave a CSV,false\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);done(err);});});
});

Like before, we use describe() to group our test separately from the others as it involves new functionality. The it() function is slightly different from our other ones. Usually, the callback function we use has no arguments. This time, we have done as an argument. We need this argument when testing functions with callbacks. The done() callback function is used by Mocha to tell it when an asynchronous function is completed.

像以前一样,我们使用describe()将测试与其他测试分开进行分组,因为它涉及新功能。 it()函数与我们其他函数略有不同。 通常,我们使用的回调函数没有参数。 这次,我们已经done了一个论证。 在测试带有回调的函数时,我们需要此参数。 Mocha使用done()回调函数告诉异步函数何时完成。

All callback functions being tested in Mocha must call the done() callback. If not, Mocha would never know when the function was complete and would be stuck waiting for a signal.

在Mocha中测试的所有回调函数都必须调用done()回调。 如果不是这样,Mocha将永远不知道该功能何时完成,并且会停留在等待信号的状态。

Continuing, we create our Todos instance and add a single item to it. We then call the saveToFile() function, with a callback that captures a file writing error. Note how our test for this function resides in the callback. If our test code was outside the callback, it would fail as long as the code was called before the file writing completed.

继续,我们创建Todos实例并向其中添加一个项目。 然后,我们调用saveToFile()函数,并使用一个捕获文件写入错误的回调。 注意我们对该函数的测试如何驻留在回调中。 如果我们的测试代码在回调之外,则只要在文件写入完成之前调用该代码,该测试代码就会失败。

In our callback function, we first check that our file exists:

在回调函数中,我们首先检查文件是否存在:

todos/index.test.js
待办事项/ index.test.js
...
assert.strictEqual(fs.existsSync('todos.csv'), true);
...

The fs.existsSync() function returns true if the file path in its argument exists, false otherwise.

如果fs.existsSync()函数的参数文件路径存在,则返回true ,否则返回false

Note: The fs module’s functions are asynchronous by default. However, for key functions, they made synchronous counterparts. This test is simpler by using synchronous functions, as we don’t have to nest the asynchronous code to ensure it works. In the fs module, synchronous functions usually end with "Sync" at the end of their names.

注意: fs模块的功能默认情况下是异步的。 但是,对于关键功能,它们是同步的。 通过使用同步函数,此测试更加简单,因为我们不必嵌套异步代码以确保其工作。 在fs模块中,同步函数通常在其名称的末尾以"Sync"结尾。

We then create a variable to store our expected value:

然后,我们创建一个变量来存储我们的期望值:

todos/index.test.js
待办事项/ index.test.js
...
let expectedFileContents = "Title,Completed\nsave a CSV,false\n";
...

We use readFileSync() of the fs module to read the file synchronously:

我们使用fs模块的readFileSync()来同步读取文件:

todos/index.test.js
待办事项/ index.test.js
...
let content = fs.readFileSync("todos.csv").toString();
...

We now provide readFileSync() with the right path for the file: todos.csv. As readFileSync() returns a Buffer object, which stores binary data, we use its toString() method so we can compare its value with the string we expect to have saved.

现在,我们为readFileSync()提供文件的正确路径: todos.csv 。 当readFileSync()返回存储二进制数据的Buffer对象时,我们使用其toString()方法,以便可以将其值与我们期望保存的字符串进行比较。

Like before, we use the assert module’s strictEqual to do a comparison:

像以前一样,我们使用assert模块的strictEqual进行比较:

todos/index.test.js
待办事项/ index.test.js
...
assert.strictEqual(content, expectedFileContents);
...

We end our test by calling the done() callback, ensuring that Mocha knows to stop testing that case:

我们通过调用done()回调done()结束测试,确保Mocha知道停止测试这种情况:

todos/index.test.js
待办事项/ index.test.js
...
done(err);
...

We provide the err object to done() so Mocha can fail the test in the case an error occurred.

我们将err对象提供给done()以便Mocha在发生错误的情况下无法通过测试。

Save and exit from index.test.js.

保存并退出index.test.js

Let’s run this test with npm test like before. Your console will display this output:

让我们像以前一样使用npm test运行此测试。 您的控制台将显示以下输出:

Output
...
integrated test✓ should be able to add and complete TODOscomplete()✓ should fail if there are no TODOssaveToFile()✓ should save a single TODO3 passing (15ms)

You’ve now tested your first asynchronous function with Mocha using callbacks. But at the time of writing this tutorial, Promises are more prevalent than callbacks in new Node.js code, as explained in our How To Write Asynchronous Code in Node.js article. Next, let’s learn how we can test them with Mocha as well.

现在,您已经使用回调使用Mocha测试了您的第一个异步函数。 但是在撰写本教程时,Promise比新Node.js代码中的回调更为普遍,正如我们如何在Node.js中编写异步代码中所述。 接下来,让我们学习如何使用Mocha对其进行测试。

承诺 (Promises)

A Promise is a JavaScript object that will eventually return a value. When a Promise is successful, it is resolved. When it encounters an error, it is rejected.

Promise是一个JavaScript对象,最终将返回一个值。 承诺成功后,便会解决。 当遇到错误时,它将被拒绝。

Let’s modify the saveToFile() function so that it uses Promises instead of callbacks. Open up index.js:

让我们修改saveToFile()函数,使其使用Promises而不是回调。 打开index.js

  • nano index.js 纳米index.js

First, we need to change how the fs module is loaded. In your index.js file, change the require() statement at the top of the file to look like this:

首先,我们需要更改fs模块的加载方式。 在index.js文件中,将文件顶部的require()语句更改为如下所示:

todos/index.js
待办事项/ index.js
...
const fs = require('fs').promises;
...

We just imported the fs module that uses Promises instead of callbacks. Now, we need to make some changes to saveToFile() so that it works with Promises instead.

我们只是导入了使用Promises而不是回调的fs模块。 现在,我们需要对saveToFile()进行一些更改,以便它可以与Promises一起使用。

In your text editor, make the following changes to the saveToFile() function to remove the callbacks:

在文本编辑器中,对saveToFile()函数进行以下更改以删除回调:

todos/index.js
待办事项/ index.js
...
saveToFile() {let fileContents = 'Title,Completed\n';this.todos.forEach((todo) => {fileContents += `${todo.title},${todo.completed}\n`});return fs.writeFile('todos.csv', fileContents);
}
...

The first difference is that our function no longer accepts any arguments. With Promises we don’t need a callback function. The second change concerns how the file is written. We now return the result of the writeFile() promise.

第一个区别是我们的函数不再接受任何参数。 使用Promises,我们不需要回调函数。 第二个更改涉及文件的写入方式。 现在,我们返回writeFile()承诺的结果。

Save and close out of index.js.

保存并关闭index.js

Let’s now adapt our test so that it works with Promises. Open up index.test.js:

现在让我们调整测试以使其与Promises一起使用。 打开index.test.js

  • nano index.test.js 纳米index.test.js

Change the saveToFile() test to this:

saveToFile()测试更改为此:

todos/index.js
待办事项/ index.js
...
describe("saveToFile()", function() {it("should save a single TODO", function() {let todos = new Todos();todos.add("save a CSV");return todos.saveToFile().then(() => {assert.strictEqual(fs.existsSync('todos.csv'), true);let expectedFileContents = "Title,Completed\nsave a CSV,false\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);});});
});

The first change we need to make is to remove the done() callback from its arguments. If Mocha passes the done() argument, it needs to be called or it will throw an error like this:

我们需要做的第一个更改是从其参数中删除done()回调。 如果Mocha传递了done()参数,则需要调用它,否则它将引发如下错误:

1) saveToFile()should save a single TODO:Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/ubuntu/todos/index.test.js)at listOnTimeout (internal/timers.js:536:17)at processTimers (internal/timers.js:480:7)

When testing Promises, do not include the done() callback in it().

在测试Promises时,请勿在it()包含done()回调。

To test our promise, we need to put our assertion code in the then() function. Notice that we return this promise in the test, and we don’t have a catch() function to catch when the Promise is rejected.

为了测试我们的承诺,我们需要将断言代码放入then()函数中。 注意,我们在测试中返回了这个promise,并且当Promise被拒绝时,我们没有catch()函数来捕获。

We return the promise so that any errors that are thrown in the then() function are bubbled up to the it() function. If the errors are not bubbled up, Mocha will not fail the test case. When testing Promises, you need to use return on the Promise being tested. If not, you run the risk of getting a false-positive.

我们返回promise,以便将then()函数中引发的任何错误冒泡到it()函数中。 如果错误未冒出,Mocha将不会使测试用例失败。 在测试Promise ,您需要在被测试的Promise上使用return 。 如果不是这样,您将冒着假阳性的风险。

We also omit the catch() clause because Mocha can detect when a promise is rejected. If rejected, it automatically fails the test.

我们还忽略了catch()子句,因为Mocha可以检测到何时诺言被拒绝。 如果被拒绝,它将自动使测试失败。

Now that we have our test in place, save and exit the file, then run Mocha with npm test and to confirm we get a successful result:

现在我们已经完成了测试,保存并退出文件,然后使用npm test运行Mocha并确认我们获得了成功的结果:

Output
...
integrated test✓ should be able to add and complete TODOscomplete()✓ should fail if there are no TODOssaveToFile()✓ should save a single TODO3 passing (18ms)

We’ve changed our code and test to use Promises, and now we know for sure that it works. But the most recent asynchronous patterns use async/await keywords so we don’t have to create multiple then() functions to handle successful results. Let’s see how we can test with async/await.

我们已经更改了代码并测试以使用Promises,现在我们确定它可以正常工作。 但是最新的异步模式使用async / await关键字,因此我们不必创建多个then()函数来处理成功的结果。 让我们看看如何使用async / await进行测试。

异步/等待 (async/await)

The async/await keywords make working with Promises less verbose. Once we define a function as asynchronous with the async keyword, we can get any future results in that function with the await keyword. This way we can use Promises without having to use the then() or catch() functions.

async / await关键字使Promises的工作不再那么冗长。 一旦使用async关键字将函数定义为异步,就可以使用await关键字在该函数中获得任何将来的结果。 这样,我们可以使用Promises,而不必使用then()catch()函数。

We can simplify our saveToFile() test that’s promise based with async/await. In your text editor, make these minor edits to the saveToFile() test in index.test.js:

我们可以使用async / await简化基于promise的saveToFile()测试。 在文本编辑器,让这些小修改到saveToFile()测试index.test.js

todos/index.test.js
待办事项/ index.test.js
...
describe("saveToFile()", function() {it("should save a single TODO", async function() {let todos = new Todos();todos.add("save a CSV");await todos.saveToFile();assert.strictEqual(fs.existsSync('todos.csv'), true);let expectedFileContents = "Title,Completed\nsave a CSV,false\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);});
});

The first change is that the function used by the it() function now has the async keyword when it’s defined. This allows us to the use the await keyword inside its body.

第一个变化是it()函数使用的函数现在在定义时具有async关键字。 这使我们可以在其主体内部使用await关键字。

The second change is found when we call saveToFile(). The await keyword is used before it is called. Now Node.js knows to wait until this function is resolved before continuing the test.

当我们调用saveToFile()时,找到第二个更改。 在调用关键字await之前将使用它。 现在,Node.js知道要等到此功能解决后才能继续测试。

Our function code is easier to read now that we moved the code that was in the then() function to the it() function’s body. Running this code with npm test produces this output:

现在,我们将then()函数中的代码移到了it()函数的主体上,使我们的函数代码更易于阅读。 使用npm test运行此代码将产生以下输出:

Output
...
integrated test✓ should be able to add and complete TODOscomplete()✓ should fail if there are no TODOssaveToFile()✓ should save a single TODO3 passing (30ms)

We can now test asynchronous functions using any of three asynchronous paradigms appropriately.

现在,我们可以适当地使用三种异步范例中的任何一种来测试异步功能。

We have covered a lot of ground with testing synchronous and asynchronous code with Mocha. Next, let’s dive in a bit deeper to some other functionality that Mocha offers to improve our testing experience, particularly how hooks can change test environments.

我们已经使用Mocha测试同步和异步代码涵盖了很多领域。 接下来,让我们更深入地了解Mocha提供的其他一些功能,以改善我们的测试体验,尤其是钩子如何更改测试环境。

第5步—使用挂钩改进测试用例 (Step 5 — Using Hooks to Improve Test Cases)

Hooks are a useful feature of Mocha that allows us to configure the environment before and after a test. We typically add hooks within a describe() function block, as they contain setup and teardown logic specific to some test cases.

挂钩是Mocha的一项有用功能,可让我们在测试之前和之后配置环境。 我们通常将钩子添加到describe()函数块中,因为它们包含特定于某些测试用例的设置和拆卸逻辑。

Mocha provides four hooks that we can use in our tests:

Mocha提供了四个可以在测试中使用的钩子:

  • before: This hook is run once before the first test begins.

    before :此挂钩在第一次测试开始之前运行一次。

  • beforeEach: This hook is run before every test case.

    beforeEach :此挂钩在每个测试用例之前运行。

  • after: This hook is run once after the last test case is complete.

    after :在最后一个测试用例完成之后,此挂钩将运行一次。

  • afterEach: This hook is run after every test case.

    afterEach :在每个测试用例之后都将运行此挂钩。

When we test a function or feature multiple times, hooks come in handy as they allow us to separate the test’s setup code (like creating the todos object) from the test’s assertion code.

当我们多次测试一个函数或功能时,钩子会派上用场,因为它们使我们能够将测试的设置代码(例如创建todos对象)与测试的断言代码分开。

To see the value of hooks, let’s add more tests to our saveToFile() test block.

要查看钩子的值,让我们向saveToFile()测试块添加更多测试。

While we have confirmed that we can save our TODO items to a file, we only saved one item. Furthermore, the item was not marked as completed. Let’s add more tests to be sure that the various aspects of our module works.

虽然我们确认可以将TODO项保存到文件中,但仅保存了一项。 此外,该项目未标记为已完成。 让我们添加更多测试,以确保模块的各个方面都能正常工作。

First, let’s add a second test to confirm that our file is saved correctly when we have a completed a TODO item. Open your index.test.js file in your text editor:

首先,让我们添加第二个测试,以确认我们在完成TODO项目后是否正确保存了文件。 在文本编辑器中打开index.test.js文件:

  • nano index.test.js 纳米index.test.js

Change the last test to the following:

将最后一个测试更改为以下内容:

todos/index.test.js
待办事项/ index.test.js
...
describe("saveToFile()", function () {it("should save a single TODO", async function () {let todos = new Todos();todos.add("save a CSV");await todos.saveToFile();assert.strictEqual(fs.existsSync('todos.csv'), true);let expectedFileContents = "Title,Completed\nsave a CSV,false\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);});it("should save a single TODO that's completed", async function () {let todos = new Todos();todos.add("save a CSV");todos.complete("save a CSV");await todos.saveToFile();assert.strictEqual(fs.existsSync('todos.csv'), true);let expectedFileContents = "Title,Completed\nsave a CSV,true\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);});
});

The test is similar to what we had before. The key differences are that we call the complete() function before we call saveToFile(), and that our expectedFileContents now have true instead of false for the completed column’s value.

该测试与我们之前的测试相似。 关键的区别是,我们所说的complete()函数调用我们之前saveToFile()和我们的expectedFileContents现在有true的而不是falsecompleted列的值。

Save and exit the file.

保存并退出文件。

Let’s run our new test, and all the others, with npm test:

让我们使用npm test运行我们的新测试以及所有其他npm test

  • npm test npm测试

This will give the following:

这将给出以下内容:

Output
...
integrated test✓ should be able to add and complete TODOscomplete()✓ should fail if there are no TODOssaveToFile()✓ should save a single TODO✓ should save a single TODO that's completed4 passing (26ms)

It works as expected. There is, however, room for improvement. They both have to instantiate a Todos object at the beginning of the test. As we add more test cases, this quickly becomes repetitive and memory-wasteful. Also, each time we run the test, it creates a file. This can be mistaken for real output by someone less familiar with the module. It would be nice if we cleaned up our output files after testing.

它按预期工作。 但是,仍有改进的空间。 他们都必须在测试开始时实例化Todos对象。 随着我们添加更多的测试用例,这很快变得重复且浪费内存。 另外,每次我们运行测试时,它都会创建一个文件。 不太熟悉该模块的人可能会将其误认为是实际输出。 如果我们在测试后清理输出文件,那就太好了。

Let’s make these improvements using test hooks. We’ll use the beforeEach() hook to set up our test fixture of TODO items. A test fixture is any consistent state used in a test. In our case, our test fixture is a new todos object that has one TODO item added to it already. We will then use afterEach() to remove the file created by the test.

让我们使用测试挂钩进行这些改进。 我们将使用beforeEach()钩子来设置TODO项目的测试夹具 。 测试夹具是测试中使用的任何一致状态。 在我们的例子中,我们的测试夹具是一个新的todos有添加到它已经一个TODO项目对象。 然后,我们将使用afterEach()删除测试创建的文件。

In index.test.js, make the following changes to your last test for saveToFile():

index.test.js ,对上一次对saveToFile()测试进行以下更改:

todos/index.test.js
待办事项/ index.test.js
...
describe("saveToFile()", function () {beforeEach(function () {this.todos = new Todos();this.todos.add("save a CSV");});afterEach(function () {if (fs.existsSync("todos.csv")) {fs.unlinkSync("todos.csv");}});it("should save a single TODO without error", async function () {await this.todos.saveToFile();assert.strictEqual(fs.existsSync("todos.csv"), true);let expectedFileContents = "Title,Completed\nsave a CSV,false\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);});it("should save a single TODO that's completed", async function () {this.todos.complete("save a CSV");await this.todos.saveToFile();assert.strictEqual(fs.existsSync('todos.csv'), true);let expectedFileContents = "Title,Completed\nsave a CSV,true\n";let content = fs.readFileSync("todos.csv").toString();assert.strictEqual(content, expectedFileContents);});
});

Let’s break down all the changes we’ve made. We added a beforeEach() block to the test block:

让我们分解一下我们所做的所有更改。 我们在测试块中添加了一个beforeEach()块:

todos/index.test.js
待办事项/ index.test.js
...
beforeEach(function () {this.todos = new Todos();this.todos.add("save a CSV");
});
...

These two lines of code create a new Todos object that will be available in each of our tests. With Mocha, the this object in beforeEach() refers to the same this object in it(). this is the same for every code block inside the describe() block. For more information on this, see our tutorial Understanding This, Bind, Call, and Apply in JavaScript.

这两行代码创建了一个新的Todos对象,该对象将在我们的每个测试中可用。 与摩卡,在this对象中beforeEach()是指相同的this对象在it() this对于describe()块中的每个代码块都是相同的。 欲了解更多有关this ,请参阅我们的教程了解这一点,绑定,呼叫,并在JavaScript应用 。

This powerful context sharing is why we can quickly create test fixtures that work for both of our tests.

这种强大的上下文共享是为什么我们可以快速创建适用于我们的两个测试的测试装置的原因。

We then clean up our CSV file in the afterEach() function:

然后,我们在afterEach()函数中清理CSV文件:

todos/index.test.js
待办事项/ index.test.js
...
afterEach(function () {if (fs.existsSync("todos.csv")) {fs.unlinkSync("todos.csv");}
});
...

If our test failed, then it may not have created a file. That’s why we check if the file exists before we use the unlinkSync() function to delete it.

如果我们的测试失败,则可能尚未创建文件。 这就是为什么我们在使用unlinkSync()函数删除文件之前先检查文件是否存在。

The remaining changes switch the reference from todos, which were previously created in the it() function, to this.todos which is available in the Mocha context. We also deleted the lines that previously instantiated todos in the individual test cases.

其余更改将引用从先前在it()函数中创建的todos切换到在Mocha上下文中可用的this.todos 。 我们还删除了先前在各个测试案例中实例化todos的行。

Now, let’s run this file to confirm our tests still work. Enter npm test in your terminal to get:

现在,让我们运行此文件以确认测试仍然有效。 在终端中输入npm test以获取:

Output
...
integrated test✓ should be able to add and complete TODOscomplete()✓ should fail if there are no TODOssaveToFile()✓ should save a single TODO without error✓ should save a single TODO that's completed4 passing (20ms)

The results are the same, and as a benefit, we have slightly reduced the setup time for new tests for the saveToFile() function and found a solution to the residual CSV file.

结果是相同的,并且有好处的是,我们略微减少了saveToFile()函数新测试的设置时间,并找到了残余CSV文件的解决方案。

结论 (Conclusion)

In this tutorial, you wrote a Node.js module to manage TODO items and tested the code manually using the Node.js REPL. You then created a test file and used the Mocha framework to run automated tests. With the assert module, you were able to verify that your code works. You also tested synchronous and asynchronous functions with Mocha. Finally, you created hooks with Mocha that make writing multiple related test cases much more readable and maintainable.

在本教程中,您编写了一个Node.js模块来管理TODO项目,并使用Node.js REPL手动测试了代码。 然后,您创建了一个测试文件,并使用Mocha框架运行自动化测试。 使用assert模块,您可以验证代码是否有效。 您还使用Mocha测试了同步和异步功能。 最后,您使用Mocha创建了钩子,这些钩子使编写多个相关的测试用例更具可读性和可维护性。

Equipped with this understanding, challenge yourself to write tests for new Node.js modules that you are creating. Can you think about the inputs and outputs of your function and write your test before you write your code?

具备了这种理解后,请挑战自己为正在创建的新Node.js模块编写测试。 您可以在编写代码之前考虑一下函数的输入和输出并编写测试吗?

If you would like more information about the Mocha testing framework, check out the official Mocha documentation. If you’d like to continue learning Node.js, you can return to the How To Code in Node.js series page.

如果您想了解有关Mocha测试框架的更多信息,请查阅Mocha官方文档 。 如果您想继续学习Node.js,可以返回“ 如何在Node.js中进行编码”系列页面 。

翻译自: https://www.digitalocean.com/community/tutorials/how-to-test-a-node-js-module-with-mocha-and-assert

node mocha

node mocha_如何使用Mocha和Assert测试Node.js模块相关推荐

  1. node mocha_使用Mocha和Chai测试Node RESTful API

    node mocha 介绍 ( Introduction ) I still remember the satisfaction of being finally able to write the ...

  2. 942.ava.com_如何使用Ava.js测试Node.js应用程序

    942.ava.com by Nitish Phanse 由Nitish Phanse 如何使用Ava.js测试Node.js应用程序 (How you can test your Node.js a ...

  3. Node.js「一」—— Node.js 简介 / Node.js 模块 / 包 与 NPM

    本文为 Node.js 系列笔记第一篇.文章参考:nodejs 教程 -- 大地:<深入浅出 Node.js>:阮一峰 nodejs 博客 文章目录 一.Node 简介 1. 简单介绍 2 ...

  4. node.js 模块_如何创建Node JS可重用模块

    node.js 模块 In my previous post, we have discussed about "How to export and import a Node JS Mod ...

  5. ERROR: cannot launch node of type [map_server/map_server]: can't locate node [map_server] in package

    解决方案 ERROR: cannot launch node of type [map_server/map_server]: can't locate node [map_server] in pa ...

  6. cannot launch node of type [map_server/map_server]: can't locate node [map_server] in package

    ERROR: cannot launch node of type [map_server/map_server]: can't locate node [map_server] in package ...

  7. Node.js 模块以及npm包的管理和使用

    Node.js模块系统 Node.js的模块系统是将可重用代码封装在各种模块中,减少了应用程序的代码量,模块系统提高了开发效率和代码的可读性,并且模块打包代码的方式不会改变全局作用域,开发人员可以在被 ...

  8. node mysql 模块化_Node.js 模块系统

    Node.js模块系统 为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node. ...

  9. DCMTK:测试图像像素模块功能

    DCMTK:测试图像像素模块功能 测试图像像素模块功能 测试图像像素模块功能 #include "dcmtk/config/osconfig.h" #include "d ...

最新文章

  1. Linux下l2tp客户端xl2tpd的安装配置
  2. NHibernate初学二之简单执行SQL及HQL、Linq
  3. mysql系列之2.mysql多实例
  4. VTK:Utilities之SaveSceneToFieldData
  5. socket的NIO操作
  6. 2019我做成的事情
  7. python 通信模块_python 多进程通信模块
  8. 动态规划——物品无限的背包问题
  9. 华为HarmonyOS 2.0 手机开发者Beta版发布
  10. Viod Class 启动
  11. 2020-1-7(169)
  12. 陈小龙书pHP,PHP
  13. Grpc学习之map变量
  14. Leedcode编程题18: 四数之和----C++实现
  15. Micro SD 卡(TF卡) spi 模式实现方法
  16. 汽车销量数据库(分车型、分品牌月度销量数据 2005-2021)
  17. PHP+MySQL实现用户登录注册API接口
  18. 一次手动查杀永恒之蓝病毒木马文件
  19. ofo发布“小黄蜂”,想试试一贴即开的新体验吗
  20. 《勋伯格和声学》读书笔记(五):小调中的七和弦及其转位,没有共同音的和弦的连接

热门文章

  1. 免费敏感词检测API
  2. Eclipse安装最新SVN插件
  3. 恒定PH值分子动力学模拟
  4. 数据库链接失败问题,终于找到解决方案!
  5. 火车头抓取豆瓣影评案例
  6. 从appstore快速安装Xcode 8.0_如何解决xcode8安装慢的问题
  7. git统计历史上某一段时间代码的修改量
  8. 如何在Ubuntu 20.04上用命令行建立l2tp连接
  9. 高通平台android开发总结
  10. springboot 添加第三方jar包