0
点赞
收藏
分享

微信扫一扫

了解单元测试

梦为马 2022-03-23 阅读 20

了解单元测试的概念

Node Test Helper(节点测试助手)

  此测试助手模块使 Node-RED 核心的节点单元测试框架可供节点贡献者使用。
  使用 test-helper,您的测试可以在启动 Node-RED 时、加载测试流,并接收消息以确保您的节点代码是正确的。

Adding to your node project dependencies(添加到您的节点项目依赖项)

  助手需要 Node-RED 作为对等依赖项,这意味着它必须与助手本身一起安装。 要为您的节点项目创建单元测试,请添加此测试助手和 Node-RED,如下所示:

npm install node-red-node-test-helper node-red --save-dev
//node-red-node-test-helper 和node-red一起安装;

save-dev是添加依赖项
  这会将帮助模块添加到您的package.json文件中:

...
   “ devDependencies ”:{
     “ node-red ”:“ ^0.18.4 ”,
     “ node-red-node-test-helper ”:“ ^0.1.8}
...

Using a local Node-RED install for tests(使用本地 Node-RED 安装进行测试)

  如果您已经为开发安装了 Node-RED,则可以创建指向本地安装的符号链接。 例如,如果在 ~/projects 文件夹中克隆了 Node-RED,请使用:
(这是我们已经安装过node-red之后进行的操作,就不用进行第一个二级子标题的内容了;其中~/projects 文件夹指的是我们创建的项目所在的文件夹,比如说之前在桌面上创建了一个“四则运算”的项目,则应使用 F:\桌面\四则运算 文件夹。)

npm install ~/projects/node-red --no-save
//这是我们已经安装过node-red之后进行的操作,就不用进行第一个二级子标题的内容了;

Adding test script to package.json(将测试脚本添加到package.json)

  要运行测试,您可以在脚本部分的 package.json 文件中添加一个测试脚本。 运行 test 目录中所有带有 _spec.js 前缀的文件,例如:

 ...
  "scripts": {
    "test": "mocha \"test/**/*_spec.js\""
  },
  ...

这将允许您npm test在命令行上使用。

Creating unit tests(创建单元测试)

  我们建议 将单元测试脚本放在项目的 test/ 文件夹中,并使用 *_spec.js(用于规范)后缀命名约定

Example unit test(示例单元测试)

  这是 Node-RED 文档中用于测试小写节点的示例测试。 这里我们将测试脚本命名为 test/lower-case_spec.js。
test/lower-case_spec.js:

var should = require("should");
var helper = require("node-red-node-test-helper");
var lowerNode = require("../lower-case.js");

helper.init(require.resolve('node-red'));

describe('lower-case Node', function () {

  beforeEach(function (done) {
      helper.startServer(done);
  });

  afterEach(function (done) {
      helper.unload();
      helper.stopServer(done);
  });

  it('should be loaded', function (done) {
    var flow = [{ id: "n1", type: "lower-case", name: "lower-case" }];
    helper.load(lowerNode, flow, function () {
      var n1 = helper.getNode("n1");
      try {
        n1.should.have.property('name', 'lower-case');
        done();
      } catch(err) {
        done(err);
      }
    });
  });

  it('should make payload lower case', function (done) {
    var flow = [
      { id: "n1", type: "lower-case", name: "lower-case",wires:[["n2"]] },
      { id: "n2", type: "helper" }
    ];
    helper.load(lowerNode, flow, function () {
      var n2 = helper.getNode("n2");
      var n1 = helper.getNode("n1");
      n2.on("input", function (msg) {
        try {
          msg.should.have.property('payload', 'uppercase');
          done();
        } catch(err) {
          done(err);
        }
      });
      n1.receive({ payload: "UpperCase" });
    });
  });
});

  在这个例子中,我们需要断言,这个帮助模块,以及我们要测试的小写节点,位于父目录中。
  然后我们有一组 mocha 单元测试。 这些测试检查节点是否正确加载,并确保它使有效负载字符串按预期小写。
  注意断言失败是如何被显式捕获并传递给 done() 调用的。 Node-RED 会吞下流中引发的异常,因此我们确保测试框架能够识别它们。 不这样做只会导致测试超时,因为在断言失败的情况下永远不会调用 done()。

Initializing Helper(初始化助手)

  首先,我们需要告诉助手在哪里可以找到 node-red 运行时。 如图所示,这是通过调用 helper.init(require.resolve(‘node-red’)) 来完成的。
  助手采用可选的 userSettings 参数,该参数与运行时默认值合并。

helper.init(require.resolve('node-red'), { 
    functionGlobalContext: { os:require('os') }
});

Getting nodes in the runtime(在运行时获取节点)

  一旦 Node-RED 服务器和运行时准备就绪,异步 helper.load() 方法就会调用提供的回调函数。 然后我们可以调用 helper.getNode(id) 方法来获取运行时节点的引用。 有关这些方法的更多信息,请参阅下面的 API 部分。

Receiving messages from nodes(从节点接收消息)

  第二个测试使用运行时中的helper节点连接到我们被测lower-case节点的输出。 helper节点是一个没有功能的模拟节点。 通过添加示例中的“输入”事件处理程序,我们可以检查 helper)接收到的消息。
  要将消息发送到被测的lower-case节点 n1,我们在该节点上调用 n1.receive({ payload: “UpperCase” })。 然后,我们可以检查helper节点输入事件处理程序中的有效负载确实是小写的。

Working with Spies(与间谍合作)

  间谍(文档)可帮助您收集有关函数被调用次数、调用内容、返回内容等的信息。
  这个帮助程序库会自动为 Node.prototype 上的以下函数创建间谍(这些函数与“创建节点”指南中提到的函数相同):
trace()
debug()
warn()
log()
status()
send()
警告:不要试图用 sinon.spy()自己窥探这些函数; 既然已经是间谍,诗乃就会抛出异常!

Synchronous Example: Initialization(同步示例:初始化)

  如果配置中不存在 (somethingGood),则 FooNode 节点将在初始化/构造时调用 warn(),如下所示:

// /path/to/foo-node.js
module.exports = function FooNode (config) {
  RED.nodes.createNode(this, config);

  if (!config.somethingGood) {
    this.warn('badness');
  }
}

然后你可以断言:

// /path/to/test/foo-node_spec.js
const FooNode = require('/path/to/foo-node');

it('should warn if the `somethingGood` prop is falsy', function (done) {
  const flow = {
    name: 'n1',
    somethingGood: false,
    /* ..etc.. */
  };
  helper.load(FooNode, flow, function () {
    n1.warn.should.be.calledWithExactly('badness');
    done();
  });
});

Synchronous Example: Input(同步示例:输入)

  当它接收到输入时,如果 msg.omg 为真,FooNode 将立即调用 error():

// somewhere in FooNode constructor
this.on('input', msg => {
  if (msg.omg) {
    this.error('lolwtf');
  }
  // ..etc..
});

以下是如何做出该断言的示例:

describe('if `omg` in input message', function () {
  it('should call `error` with "lolwtf" ', function (done) {
    const flow = {
      name: 'n1',
      /* ..etc.. */
    };
    helper.load(FooNode, flow, function () {
      const n1 = helper.getNode('n1')
      n1.receive({omg: true});
      n1.on('input', () => {
        n1.warn.should.be.calledWithExactly('lolwtf');
        done();
      });
    });
  });
});

Asynchronous Example(异步示例)

  稍后在 FooNode 的input侦听器中,可能会异步调用 warn(),如下所示:

// somewhere in FooNode constructor function
this.on('input', msg => {
  if (msg.omg) {
    this.error('lolwtf');
  }
  // ..etc..

  Promise.resolve()
    .then(() => {
      if (msg.somethingBadAndWeird) {
        this.warn('bad weirdness');
      }
    });
});

  前面示例中用于测试 msg.omg 行为的策略将不起作用! n1.warn.should.be.callWithExactly(‘bad weirdness’) 会抛出一个 AssertionError,因为 warn() 还没有被调用; EventEmitters 是同步的,测试的input监听器是在 FooNode 的函数中的input监听器完成之后直接调用的——但在 Promise 被解析之前!
  由于我们不知道何时准确地调用 warn()(缺少使用 setTimeout 并等待 n 毫秒然后检查的缓慢、容易出现竞争条件的解决方案),我们需要一种不同的方法来检查调用。 神奇的是,这个帮助模块提供了一个解决方案。
  当调用 warn 时,helper 将导致 FooNode 异步发出事件(以及上面列表中的其他方法)。 事件名称的格式为 call:; 在这种情况下,methodName 为 warn,因此事件名称为 call:warn。 该事件将传递一个参数:对应于最新方法调用的 Spy Call 对象 (docs)。 然后,您可以对这个 Spy Call 参数进行断言,如下所示:

describe('if `somethingBadAndWeird` in input msg', function () {
  it('should call "warn" with "bad weirdness" ', function (done) {
    const flow = {
      name: 'n1',
      /* ..etc.. */
    };
    helper.load(FooNode, flow, function () {
      const n1 = helper.getNode('n1')
      n1.receive({somethingBadAndWeird: true});
      // because the emit happens asynchronously, this listener
      // will be registered before `call:warn` is emitted.
      n1.on('call:warn', call => {
        call.should.be.calledWithExactly('bad weirdness');
        done();
      });
    });
  });
});

  如您所见,看起来与同步解决方案非常相似; 唯一的区别是事件名称和断言目标。
  注意:当且仅当我们尝试进行断言时对 spy 的同步调用仍然是最新的,“异步”策略也将起作用。 这可能会在重构时导致细微的错误,因此在选择使用哪种策略时要小心。

Running your tests(运行你的测试)

要运行您的测试:

npm test

产生以下输出(对于本例):

> red-contrib-lower-case@0.1.0 test /dev/work/node-red-contrib-lower-case
> mocha "test/**/*_spec.js"

lower-case Node
  ✓ should be loaded
  ✓ should make payload lower case

2 passing (50ms)

Creating test flows with the editor(使用编辑器创建测试流程)

  要使用 Node-RED 编辑器创建测试流,请将测试流导出到剪贴板,然后将流粘贴到您的单元测试代码中。 以这种方式包含(helper)节点的一种有用技术是使用(debug)节点作为(helper)节点的占位符,然后搜索并将“type”:“debug”替换为“type”:“helper” 需要的地方。

Using catch and status nodes in test flows(在测试流程中使用catch和status节点)

  要在测试流中使用 catch 和 status 或其他依赖于运行时特殊处理的节点,您通常需要添加一个选项卡来标识流,并将关联的 z 属性添加到您的节点以将节点与流相关联。 例如:

var flow = [{id:"f1", type:"tab", label:"Test flow"},
  { id: "n1", z:"f1", type: "lower-case", name: "test name",wires:[["n2"]] },
  { id: "n2", z:"f1", type: "helper" }

Additional examples(其他示例)

  有关从 Node-RED 核心获取的其他测试示例,请参阅 test/examples 文件夹中提供的 .js 文件以及 Node-RED 存储库中 test/nodes 中的相关测试代码。

API

Work in progress.

load(testNode, testFlows, testCredentials, cb)(负载(testNode,testFlows,testCredentials,cb))

  加载流程然后启动流程。此函数具有以下参数:

  • testNode: (object|array of objects) 由 require 函数返回的待测试节点的模块对象。该节点将被注册,并且可以在 testFlows 中使用。
  • testFlow:(对象数组)流数据以测试节点。如果要使用从 Node-RED 编辑器导出的流数据,请将流导出到剪贴板并将内容粘贴到测试脚本中。
  • testCredentials:(对象)可选节点凭据。
  • cb: (function) testFlows 启动时回调的函数。

unload() (卸下())

  返回停止所有流程的承诺,清理测试运行时。

getNode(id) (获取节点(ID))

  在 testFlow 中按 id 返回节点实例。可以检索在 testFlows 中定义的任何节点,包括添加到流中的任何辅助节点。

clearFlows() (清除流)

  停止所有流程

request() (要求())

  创建对编辑器/管理员 url 的 http(超级测试)请求。
例如:

helper.request().post('/inject/invalid').expect(404).end(done);

settings(userSettings) (设置(用户设置))

  将任何 userSettings 与 RED.settings 返回的默认值合并。 每次调用此方法都会覆盖以前的 userSettings 以防止测试中出现意外问题。
  这将使您能够在测试中复制生产环境,例如,您使用 functionGlobalContext 在函数中启用额外的节点模块。

// functions can now access os via global.get('os')
helper.settings({ functionGlobalContext: { os:require('os') } });

// reset back to defaults
helper.settings({ });

startServer(done)    (启动服务器(完成))

  启动一个 Node-RED 服务器,用于测试依赖于 http 或 web 套接字端点(如调试节点)的节点。 在所有测试用例之前启动 Node-RED 服务器:

before(function(done) {
    helper.startServer(done);
});

stopServer(done)    (停止服务器(完成))

  停止服务器。 通常在 unload() 完成后调用。 例如,要卸载一个流,然后在每次测试后停止服务器:

url()   (网址( ))

  返回帮助服务器的 URL,包括启动服务器时使用的临时端口。

log()   (日志( ))

var logEvents = helper.log().args.filter(function(evt {
    return evt[0].type == "batch";
});

Running helper examples(运行帮助程序示例)

npm run examples

  这会在包含的小写节点(如上所述)以及一些核心节点的 Javascript 文件的快照上运行测试,以确保帮助程序按预期工作。

Keywords

test ,iot ,node-red

举报

相关推荐

0 条评论