使用 Mocha 和 Assert 测试 Node.js 模块(下)

发表于:2022-3-24 10:08

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:前端指北    来源:掘金

分享:
  我们在 TODO 模块中想要的功能之一是 CSV 导出功能。这会将我们存储的所有 TODO 连同完成的状态打印到一个文件中。这要求我们使用该fs模块——一个用于处理文件系统的内置 Node.js 模块。
  写入文件是一种异步操作。在 Node.js 中有很多方法可以写入文件。我们可以使用回调、Promises 或async/await关键字。在本节中,我们将看看我们如何为这些不同的方法编写测试

  回调
  回调函数是用作异步函数的参数的函数。当异步操作完成时调用它。
  让我们在我们的Todos类中添加一个名为saveToFile(). 这个函数将通过遍历我们所有的 TODO 项并将该字符串写入文件来构建一个字符串。
  打开你的index.js文件:
nano 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 = 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}"`);
        }
    }

    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;

  我们首先必须在我们的文件中导入fs模块。然后我们添加了我们的新saveToFile()功能。我们的函数采用一个回调函数,一旦文件写入操作完成,就会使用该回调函数。在该函数中,我们创建一个fileContents变量来存储我们想要保存为文件的整个字符串。它使用 CSV 的标头初始化。forEach()然后我们使用内部数组的方法遍历每个 TODO 项。当我们迭代时,我们添加各个对象的title和属性。
  最后,我们使用fs模块来编写带有writeFile()函数的文件。我们的第一个参数是文件名:todos.csv. 第二个是文件的内容,在本例中是我们的fileContents变量。我们的最后一个参数是我们的回调函数,它处理任何文件写入错误。
  保存并退出文件。
  现在让我们为我们的saveToFile()函数编写一个测试。我们的测试将做两件事:首先确认文件存在,然后验证它的内容是否正确。
  打开index.test.js文件:
nano index.test.js

  让我们首先fs在文件顶部加载模块,因为我们将使用它来帮助测试我们的结果:
const Todos = require('./index');
const assert = require('assert').strict;
const fs = require('fs');
...

  现在,在文件末尾添加我们的新测试用例
...
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);
        });
    });
});

  像以前一样,我们习惯describe()将我们的测试与其他测试分开分组,因为它涉及新功能。该it()功能与我们的其他功能略有不同。通常,我们使用的回调函数没有参数。这一次,我们有done一个论据。当测试带有回调的函数时,我们需要这个参数。Mocha使用done()回调函数来告诉它异步函数何时完成。
  在 Mocha 中测试的所有回调函数都必须调用done()回调。如果没有,Mocha 永远不会知道函数何时完成,并且会一直等待信号。
  继续,我们创建我们的Todos实例并向其中添加一个项目。然后我们调用该saveToFile()函数,并带有一个捕获文件写入错误的回调。请注意我们对此函数的测试如何驻留在回调中。如果我们的测试代码在回调之外,只要在文件写入完成之前调用代码,它就会失败。
  在我们的回调函数中,我们首先检查我们的文件是否存在:
...
assert.strictEqual(fs.existsSync('todos.csv'), true);
...

  如果其参数中的文件路径存在,则该fs.existsSync()函数返回,否则返回。
  [note] 注意: 模块的fs函数默认是异步的。然而,对于关键功能,他们做了同步对应。通过使用同步函数,这个测试更简单,因为我们不必嵌套异步代码来确保它工作。在fs模块中,同步函数通常以"Sync"名称结尾。
  然后我们创建一个变量来存储我们的期望值:
...
let expectedFileContents = "Title,Completed\nsave a CSV,false\n";
...

  我们使用readFileSync()模块fs来同步读取文件:
...
let content = fs.readFileSync("todos.csv").toString();
...

  我们现在为readFileSync()文件提供正确的路径:todos.csv. 作为readFileSync()返回一个Buffer存储二进制数据的对象,我们使用它的toString()方法,以便我们可以将它的值与我们期望保存的字符串进行比较。
  和之前一样,我们使用assert模块的strictEqual做一个比较:
...
assert.strictEqual(content, expectedFileContents);
...

  我们通过调用回调来结束我们的测试done(),确保 Mocha 知道停止测试该案例:
...
done(err);
...

  我们提供err对象,done()以便 Mocha 在发生错误的情况下无法通过测试。
  保存并退出index.test.js。
  npm test让我们像以前一样运行这个测试。您的控制台将显示以下输出:
Output
...
integrated test
    ✓ should be able to add and complete TODOs

  complete()
    ✓ should fail if there are no TODOs

  saveToFile()
    ✓ should save a single TODO


  3 passing (15ms)

  您现在已经使用回调测试了第一个使用 Mocha 的异步函数。但是Promise 在Node.js代码中比回调更普遍,接下来,让我们学习如何使用 Mocha 来测试它们。

  Promise
  Promise是一个JavaScript 对象,它最终会返回一个值。当一个 Promise 成功时,它就被解决了。当它遇到错误时,它会被拒绝。
  让我们修改saveToFile()函数,使其使用 Promises 而不是回调。打开index.js:
nano index.js

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

  我们刚刚导入了fs使用 Promises 而不是回调的模块。现在,我们需要进行一些更改,saveToFile()以便它可以与 Promises 一起使用。
  在您的文本编辑器中,对函数进行以下更改以saveToFile()删除回调:
...
saveToFile() {
    let fileContents = 'Title,Completed\n';
    this.todos.forEach((todo) => {
        fileContents += `${todo.title},${todo.completed}\n`
    });

    return fs.writeFile('todos.csv', fileContents);
}
...

  第一个区别是我们的函数不再接受任何参数。使用 Promises,我们不需要回调函数。第二个更改涉及文件的写入方式。我们现在返回writeFile()promise 的结果。
  保存并关闭index.js.
  现在让我们调整我们的测试,使其适用于 Promises。打开index.test.js:
nano index.test.js

  saveToFile()将测试更改为:
...
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);
        });
    });
});

  我们需要做的第一个改变是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)

  测试 Promises 时,不要done()在it().
  为了测试我们的Promises,我们需要将我们的代码放入then()函数中。请注意,我们在测试中返回了这个 Promise,并且我们没有一个函数可以在被拒绝catch()时捕获。
  我们返回承诺,以便函数中抛出的任何错误都会then()冒泡到it()函数中。如果错误没有冒泡,Mocha 不会使测试用例失败。测试 Promises 时,需要return在Promise被测试对象上使用。如果没有,您将面临误报的风险。
  我们也省略了该catch()子句,因为 Mocha 可以检测何时拒绝承诺。如果被拒绝,它会自动通过测试。
  现在我们已经完成了测试,保存并退出文件,然后运行 Mochanpm test并确认我们得到了成功的结果:
Output
...
integrated test
    ✓ should be able to add and complete TODOs

  complete()
    ✓ should fail if there are no TODOs

  saveToFile()
    ✓ should save a single TODO


  3 passing (18ms)

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

  async/await
  async/关键字使使用awaitPromises 变得不那么冗长。一旦我们使用关键字将函数定义为异步async,我们就可以使用关键字在该函数中获得任何未来的结果await。这样我们就可以使用 Promises 而不必使用then()orcatch()函数。
  我们可以使用/来简化saveToFile()基于 promise 的测试。在您的文本编辑器中,对测试进行以下小修改:async``await``saveToFile()``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()函数现在在async定义时具有关键字。这允许我们await在其正文中使用关键字。
  第二个变化是在我们调用时发现的saveToFile()。关键字在await调用之前使用。现在 Node.js 知道要等到该函数解决后再继续测试。
  现在我们将then()函数中的代码移到it()函数体中,我们的函数代码更易于阅读。运行此代码npm test会产生以下输出:
Output
...
integrated test
    ✓ should be able to add and complete TODOs

  complete()
    ✓ should fail if there are no TODOs

  saveToFile()
    ✓ should save a single TODO


  3 passing (30ms)

  我们现在可以适当地使用三种异步范式中的任何一种来测试异步函数。

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

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号