ブロックを積みながら(121) Node-RED、自前ノードにユニットテスト追加

Joseph Halfmoon

本当は「テスト駆動設計」的に先にテスト書いてからノードの作成にかかりたいものです。しかしノードの書き方自体がよく分からん、ということで先にそっちに突っ込んでました。しかしノードの機能も増えつつあります。この辺でユニットテストをキチンとしておきたいです。またしかし、ユニットテストの書き方自体が怪しいデス。堂々巡りやな。

※「ブロックを積みながら」投稿順 index はこちら

※動作確認にはRaspberry Pi 3 model B+のRaspberry Pi OS(32bit)上にインストールした以下を使用しています。

    • Node-RED v2.0.5
    • node-red-dashboard 3.2.0
自前ノードへのユニットテストの組み込み

大分以前の回でノード開発のExampleプロジェクトを勉強したときにユニットテストについて1度やってみてます。

ブロックを積みながら(115) Node-RED、『はじめてのノード開発』つづき

だいたいの段取りをおさらいすれば

    1. 開発ディレクトリに node-redとnode-red-node-test-helper をローカルにインストール
    2. 開発ディレクトリの配下に test フォルダを掘って、そこにtest用のjavascriptファイルを作成する
    3. package.json内の”srcipts”オブジェクト内 test プロパティに 上記のjavascriptファイルを実行できるように指定

詳しい手順は、npm様の以下のページに書かれております。

Node Test Helper

なお、上記のページでは、Javasciptで定番らしい mocha フレームワークを使うとともに、アサーションに should というモジュールも使ってますのでそれらのインストールの必要もありです。

ユニットテストがエラーの時どうみえるか?

テストを理解するには、成功、passのときでなく、エラーの時を知っておくのが肝心かと思います。まあ、書いてりゃ意図しなくても普通に遭遇するけど。

今回使わせていただいている node-red-node-test-helper というモジュールは、node-redと協力して、テスト用の仮のノードとノード間の結合を作り出してくれるものみたいです。以下のコードでは、n1という名のノードに被テストノード(自前ノード、data-check)を設定し、n2というヘルパノードをn1の下流に接続しているみたいです。

後方に全文掲載しているユニットテストのコードの一部を取り出したものが以下に掲げます。後ろの方のn1.receive(オブジェクト)という部分で被テストノードにオブジェクトを流し込むようになってます。これにより被テストノードが動作に、その結果として被テストノードが加工したオブジェクトが被テストノードから送出されるとその前で定義済の n2.on()メソッドで待ち受けているアサーション用のメソッドに受信されるいう塩梅のようです。受信したらば、「should.なんたら」というメソッドを駆使して受信したオブジェクトを確認するということみたいです。

以下は意図的にアサーション・エラーを起こしているケースです。被テストノードからはDATAMAXに1.23という値が到来する筈なのですが、9.99とかウソの値を書いてあります(気にせず浮動小数を比較しているけど。)itError2

実際に npm test でテスト実施してみた結果が以下に。他に2項目のテストがあるのですが、それらは passingとな。最後の1項目が failing です。Error2

念のためノーエラーのとき

上記のエラーの原因である間違ったアサーションを改めたものが以下です。itOK1_2

そして実行してみたところが以下に。reseiveOK1

全3項目全部 passing とな。

テストコード全文

今回使用のテストコードは「とりあえず npm test が使える」、というところまでの「外形だけ」です。テストケースを網羅しているわけではないざんす。ファイルは、

test/data-check_spec.js

です。

var should = require("should")
var helper = require("node-red-node-test-helper");
var datacheckNode = require("../data-check.js");

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

describe('data-check 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: "data-check", name: "test name" }];
    helper.load(datacheckNode, flow, function () {
      var n1 = helper.getNode("n1");
      n1.should.have.property('name', 'test name');
      done();
    });
  });

 it('should RESET data-check', function (done) {
   var flow = [{ id: "n1", type: "data-check", name: "test name",wires:[["n2"]] },
   { id: "n2", type: "helper" }];
   helper.load(datacheckNode, flow, function () {
     var n2 = helper.getNode("n2");
     var n1 = helper.getNode("n1");
     n2.on("input", function (msg) {
       msg.should.have.property('payload', 'RESET');
       done();
     });
     n1.receive({ payload: "RESET" });
   });
 });

 it('should make payload data-check', function (done) {
   var flow = [{ id: "n1", type: "data-check", name: "test name",wires:[["n2"]] },
   { id: "n2", type: "helper" }];
   helper.load(datacheckNode, flow, function () {
     var n2 = helper.getNode("n2");
     var n1 = helper.getNode("n1");
     n2.on("input", function (msg) {
       msg.should.have.property('payload', 1.23);
       msg.should.have.property('NDATA', 1);
       msg.should.have.property('DATAMAX', 1.23);
       msg.should.have.property('DATAMIN', 1.23);
       done();
     });
     n1.receive({
                         payload: 1.23
               });
   });
 });

});

テストケースを網羅していくのはメンドイなあ。

ブロックを積みながら(120) Node-RED、自前ノードに層別集計機能追加 へ戻る

ブロックを積みながら(122) Node-RED、自前ノードのユニットテスト、Should へ進む