序章

テストはソフトウェア開発の不可欠な部分です。 プログラマーは、アプリケーションが希望どおりに動作していることを確認するために、変更を加えたときにアプリケーションをテストするコードを実行するのが一般的です。 適切なテスト設定を使用すると、このプロセスを自動化することもでき、時間を大幅に節約できます。 新しいコードを記述した後に一貫してテストを実行することで、新しい変更によって既存の機能が損なわれないことが保証されます。 これにより、開発者はコードベースに自信を持てるようになります。特に、ユーザーがコードベースを操作できるように、コードベースを本番環境にデプロイする場合はそうです。

テストフレームワークは、テストケースを作成する方法を構成します。 Mocha は、テストケースを整理して実行する人気のあるJavaScriptテストフレームワークです。 ただし、Mochaはコードの動作を検証しません。 テストの値を比較するには、Node.js assertモジュールを使用できます。

この記事では、 Node.jsTODOリストモジュールのテストを作成します。 Mochaテストフレームワークを設定して使用し、テストを構成します。 次に、Node.jsを使用します assert テスト自体を作成するモジュール。 この意味で、モカをプランビルダーとして使用し、 assert 計画を実行します。

前提条件

ステップ1—ノードモジュールの作成

テストしたいNode.jsモジュールを書くことからこの記事を始めましょう。 このモジュールは、TODOアイテムのリストを管理します。 このモジュールを使用すると、追跡しているすべてのTODOを一覧表示し、新しいアイテムを追加し、一部を完了としてマークすることができます。 さらに、TODOアイテムのリストをCSVファイルにエクスポートできるようになります。 Node.jsモジュールの作成について復習したい場合は、Node.jsモジュールの作成方法に関する記事を読むことができます。

まず、コーディング環境を設定する必要があります。 ターミナルにプロジェクトの名前でフォルダを作成します。 このチュートリアルでは、名前を使用します todos:

  1. mkdir todos

次に、そのフォルダに入ります。

  1. cd todos

ここで、 npm を初期化します。これは、CLI機能を使用して後でテストを実行するためです。

  1. npm init -y

依存関係はMochaだけで、これを使用してテストを整理および実行します。 Mochaをダウンロードしてインストールするには、次を使用します。

  1. npm i request --save-dev mocha

Mochaを dev 実稼働環境のモジュールでは必要ないため、依存関係。 Node.jsパッケージまたはnpmの詳細については、npmおよびpackage.jsonでNode.jsモジュールを使用する方法に関するガイドをご覧ください。

最後に、モジュールのコードを含むファイルを作成しましょう。

  1. touch index.js

これで、モジュールを作成する準備が整いました。 開ける index.js のようなテキストエディタで nano:

  1. nano index.js

を定義することから始めましょう Todos クラス。 このクラスには、TODOリストを管理するために必要なすべての関数が含まれています。 次のコード行をに追加します index.js:

todos / index.js
class Todos {
    constructor() {
        this.todos = [];
    }
}

module.exports = Todos;

ファイルを作成することから始めます Todos クラス。 これは constructor() 関数は引数をとらないため、このクラスのオブジェクトをインスタンス化するために値を指定する必要はありません。 初期化するときに行うすべて Todos オブジェクトは作成されます todos 空の配列であるプロパティ。

The modules 行を使用すると、他のNode.jsモジュールで Todos クラス。 クラスを明示的にエクスポートしないと、後で作成するテストファイルで使用できなくなります。

の配列を返す関数を追加しましょう todos 保存しました。 次の強調表示された行に書き込みます。

todos / index.js
class Todos {
    constructor() {
        this.todos = [];
    }
    
    list() {
        return [...this.todos];
    }
}

module.exports = Todos;

私たちの list() 関数は、クラスで使用されている配列のコピーを返します。 JavaScriptのdestructuring構文を使用して配列のコピーを作成します。 配列のコピーを作成して、ユーザーがによって返される配列に加えた変更を行います。 list() によって使用されるアレイには影響しません Todos 物体。

注:JavaScript配列は参照型です。 これは、配列への variable 割り当て、または配列をパラメーターとして使用する関数呼び出しの場合、JavaScriptは作成された元の配列を参照することを意味します。 たとえば、3つのアイテムを含む配列がある場合 x、および新しい変数を作成します y そのような y = x, yx どちらも同じことを指します。 アレイに加えた変更 y 変数に影響を与える x およびその逆。

それでは、 add() 新しいTODOアイテムを追加する関数:

todos / 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;

私たちの add() 関数は文字列を受け取り、それを新しいJavaScriptオブジェクトに配置します title 財産。 新しいオブジェクトには、 completed に設定されているプロパティ false デフォルトでは。 次に、この新しいオブジェクトをTODOの配列に追加します。

TODOマネージャーの重要な機能は、アイテムを完了としてマークすることです。 この実装では、ループします todos ユーザーが検索しているTODOアイテムを見つけるための配列。 見つかった場合は、完了のマークを付けます。 何も見つからない場合は、エラーをスローします。

追加します complete() このように機能します:

todos / 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;

ファイルを保存して、テキストエディタを終了します。

これで、実験できる基本的なTODOマネージャーができました。 次に、コードを手動でテストして、アプリケーションが機能しているかどうかを確認しましょう。

ステップ2—コードを手動でテストする

このステップでは、コードの関数を実行し、出力を観察して、期待どおりであることを確認します。 これは手動テストと呼ばれます。 これは、プログラマーが適用する最も一般的なテスト方法である可能性があります。 後でMochaを使用してテストを自動化しますが、最初に手動でコードをテストして、手動テストがテストフレームワークとどのように異なるかをよりよく理解できるようにします。

2つのTODOアイテムをアプリに追加し、1つを完了としてマークしましょう。 Node.jsREPLindex.js ファイル:

  1. node

が表示されます > JavaScriptコードを入力できることを通知するREPLのプロンプト。 プロンプトで次のように入力します。

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

require()、TODOモジュールをにロードします Todos 変数。 モジュールが Todos デフォルトではクラス。

それでは、そのクラスのオブジェクトをインスタンス化しましょう。 REPLで、次のコード行を追加します。

  1. const todos = new Todos();

使用できます todos 実装が機能することを確認するためのオブジェクト。 最初のTODOアイテムを追加しましょう:

  1. todos.add("run code");

これまでのところ、端末に出力は表示されていません。 保存したことを確認しましょう "run code" すべてのTODOのリストを取得することによるTODOアイテム:

  1. todos.list();

この出力はREPLに表示されます。

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

これは期待される結果です。TODOの配列に1つのTODOアイテムがあり、デフォルトでは完了していません。

別のTODOアイテムを追加しましょう:

  1. todos.add("test everything");

最初のTODOアイテムを完了としてマークします。

  1. todos.complete("run code");

私たちの todos オブジェクトは2つのアイテムを管理するようになります。 "run code""test everything". The "run code" TODOも完成します。 電話で確認しましょう list() もう一度:

  1. todos.list();

REPLは以下を出力します:

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

ここで、次のようにしてREPLを終了します。

  1. .exit

モジュールが期待どおりに動作することを確認しました。 コードをテストファイルに入れたり、テストライブラリを使用したりはしませんでしたが、コードを手動でテストしました。 残念ながら、この形式のテストは、変更を加えるたびに実行するのに時間がかかります。 次に、Node.jsで自動テストを使用して、Mochaテストフレームワークでこの問題を解決できるかどうかを確認しましょう。

ステップ3—MochaとAssertを使用して最初のテストを作成する

最後のステップでは、アプリケーションを手動でテストしました。 これは個々のユースケースで機能しますが、モジュールが拡張されるにつれて、この方法は実行可能性が低くなります。 新しい機能をテストするときは、追加された機能によって古い機能に問題が発生していないことを確認する必要があります。 コードを変更するたびに各機能をもう一度テストしたいと思いますが、これを手動で行うと、多大な労力を要し、エラーが発生しやすくなります。

より効率的な方法は、自動テストを設定することです。 これらは、他のコードブロックと同じように記述されたスクリプトテストです。 定義された入力を使用して関数を実行し、それらの効果を検査して、期待どおりに動作することを確認します。 コードベースが大きくなると、自動テストも大きくなります。 機能と一緒に新しいテストを作成すると、モジュール全体が引き続き機能することを確認できます。すべて、毎回各関数の使用方法を覚えておく必要はありません。

このチュートリアルでは、Node.jsでMochaテストフレームワークを使用しています assert モジュール。 それらがどのように連携するかを実際に体験してみましょう。

まず、テストコードを保存する新しいファイルを作成します。

  1. touch index.test.js

次に、お好みのテキストエディタを使用してテストファイルを開きます。 使用できます nano 以前のように:

  1. nano index.test.js

テキストファイルの最初の行で、Node.jsシェルで行ったようにTODOsモジュールをロードします。 次に、 assert テストを書くときのモジュール。 次の行を追加します。

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

The strict のプロパティ assert このモジュールを使用すると、Node.jsで推奨され、より多くのユースケースを考慮できるため、将来を保証するのに適した特別な同等性テストを使用できます。

テストの作成に入る前に、Mochaがコードをどのように編成するかについて説明しましょう。 Mochaで構造化されたテストは通常、次のテンプレートに従います。

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

2つの重要な機能に注意してください。 describe()it(). The describe() 関数は、同様のテストをグループ化するために使用されます。 Mochaがテストを実行する必要はありませんが、テストをグループ化すると、テストコードの保守が容易になります。 同様のテストを簡単に更新できるように、テストをグループ化することをお勧めします。

The it() テストコードが含まれています。 これは、モジュールの関数と対話し、 assert 図書館。 たくさんの it() 関数はで定義することができます describe() 関数。

このセクションの目標は、Mochaと assert 手動テストを自動化します。 これは、describeブロックから始めて段階的に実行します。 モジュール行の後にファイルに以下を追加します。

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

このコードブロックを使用して、統合テストのグループ化を作成しました。 ユニットテストは、一度に1つの機能をテストします。 統合テストは、モジュール内またはモジュール間で機能がどの程度連携して機能するかを検証します。 Mochaがテストを実行すると、そのdescribeブロック内のすべてのテストが "integration test" グループ。

追加しましょう it() 関数を使用して、モジュールのコードのテストを開始できます。

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

テストの名前をわかりやすく説明していることに注目してください。 誰かが私たちのテストを実行すると、何が合格か不合格かがすぐに明らかになります。 十分にテストされたアプリケーションは通常、十分に文書化されたアプリケーションであり、テストは効果的な種類の文書化になる場合があります。

最初のテストでは、新しいテストを作成します Todos オブジェクトを作成し、アイテムが含まれていないことを確認します。

todos / 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);
    });
});

コードの最初の新しい行は、新しいインスタンス化されました Todos Node.jsREPLまたは別のモジュールで行うのと同じようにオブジェクト。 2番目の新しい行では、 assert モジュール。

から assert 使用するモジュール notStrictEqual() 方法。 この関数は2つのパラメーターを取ります:テストしたい値( actual 値)と私たちが得ると期待する値( expected 価値)。 両方の引数が同じである場合、 notStrictEqual() テストに失敗するとエラーがスローされます。

保存して終了します index.test.js.

長さは次のようになりますので、基本ケースは真になります 0、そうではありません 1. Mochaを実行してこれを確認しましょう。 これを行うには、 package.json ファイル。 あなたの package.json テキストエディタでファイル:

  1. nano package.json

今、あなたの scripts プロパティ、次のように変更します。

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

npmのCLIの動作を変更しました test 指図。 走るとき npm test、npmは入力したコマンドを確認します package.json. Mochaライブラリを検索します node_modules フォルダを実行し、 mocha テストファイルを使用してコマンドを実行します。

保存して終了 package.json.

テストを実行するとどうなるか見てみましょう。 ターミナルで、次のように入力します。

  1. npm test

このコマンドは、次の出力を生成します。

Output
> [email protected] test your_file_path/todos > mocha index.test.js integrated test ✓ should be able to add and complete TODOs 1 passing (16ms)

この出力は、最初に、実行しようとしているテストのグループを示します。 グループ内の個々のテストごとに、テストケースはインデントされます。 テスト名は、 it() 関数。 テストケースの左側にあるチェックマークは、テストに合格したことを示します。

下部に、すべてのテストの要約が表示されます。 私たちの場合、1つのテストに合格し、16ミリ秒で完了しました(時間はコンピューターによって異なります)。

私たちのテストは成功から始まりました。 ただし、この現在のテストケースでは、誤検知が発生する可能性があります。 false-positive は、失敗するはずのときに合格するテストケースです。

現在、配列の長さが次の値と等しくないことを確認しています 1. この条件が当てはまらないときに当てはまるように、テストを変更してみましょう。 次の行をに追加します index.test.js:

todos / 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アイテムを2つ追加しました。 テストを実行して、何が起こるかを確認しましょう。

  1. npm test

これにより、次のようになります。

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

長さが1より大きいため、これは期待どおりに通過します。 ただし、最初のテストを行うという本来の目的は無効になります。 最初のテストは、空白の状態から開始することを確認するためのものです。 より良いテストは、すべての場合にそれを確認します。

TODOがまったくない場合にのみ合格するように、テストを変更しましょう。 次の変更を加えます index.test.js:

todos / 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()、実際の引数と期待される引数が等しいかどうかをチェックする関数。 引数が完全に同じでない場合、厳密な等しいは失敗します。

保存して終了し、テストを実行して、何が起こるかを確認します。

  1. 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行目です。

これで、誤った値が予想される場合、テストが失敗することがわかりました。 テストケースを正しい値に戻しましょう。 まず、ファイルを開きます。

  1. nano index.test.js

次に、 todos.add コードが次のようになるように行します。

todos / 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);
    });
});

ファイルを保存して終了します。

もう一度実行して、誤検知の可能性がないことを確認します。

  1. npm test

出力は次のようになります。

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

テストの復元力が大幅に向上しました。 統合テストを進めましょう。 次のステップは、新しいTODOアイテムをに追加することです index.test.js:

todos / 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() 機能、私たちは今私たちによって管理されている1つのTODOがあることを確認します todos オブジェクトと strictEqual(). 次のテストでは、 todosdeepStrictEqual(). The deepStrictEqual() 関数は、期待されるオブジェクトと実際のオブジェクトが同じプロパティを持っていることを再帰的にテストします。 この場合、両方の配列にJavaScriptオブジェクトが含まれていることをテストします。 次に、JavaScriptオブジェクトが同じプロパティを持っていること、つまり、両方が同じプロパティを持っていることを確認します。 title プロパティは "run code" そして両方の completed プロパティは false.

次に、次の強調表示された行を追加して、必要に応じてこれら2つの同等性チェックを使用して残りのテストを完了します。

todos / 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 }
            ]
    );
  });
});

ファイルを保存して終了します。

これで、テストは手動テストを模倣します。 これらのプログラムによるテストでは、実行時にテストに合格した場合、出力を継続的にチェックする必要はありません。 通常、使用のあらゆる側面をテストして、コードが適切にテストされていることを確認します。

でテストを実行してみましょう npm test このおなじみの出力を取得するためにもう一度:

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

これで、Mochaフレームワークと assert 図書館。

モジュールを他の開発者と共有し、彼らがフィードバックを提供している状況を考えてみましょう。 私たちのユーザーのかなりの部分は、 complete() TODOがまだ追加されていない場合にエラーを返す関数。 この機能を私たちに追加しましょう complete() 関数。

開ける index.js テキストエディタで:

  1. nano index.js

関数に以下を追加します。

todos / 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}"`);
    }
}
...

ファイルを保存して終了します。

次に、この新機能の新しいテストを追加しましょう。 でcompleteを呼び出す場合は、 Todos アイテムがないオブジェクトは、特別なエラーを返します。

に戻る index.test.js:

  1. nano index.test.js

ファイルの最後に、次のコードを追加します。

todos / 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);
    });
});

を使用しております describe()it() 以前のように。 私たちのテストは、新しいものを作成することから始まります todos 物体。 次に、呼び出したときに受け取ると予想されるエラーを定義します。 complete() 関数。

次に、 throws() の機能 assert モジュール。 この関数は、コードでスローされたエラーを確認できるように作成されました。 その最初の引数は、エラーをスローするコードを含む関数です。 2番目の引数は、受け取ると予想されるエラーです。

ターミナルで、次のコマンドを使用してテストを実行します npm test もう一度、次の出力が表示されます。

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

この出力は、Mochaとを使用して自動テストを行う理由の利点を強調しています。 assert. テストはスクリプト化されているため、実行するたびに npm test、すべてのテストに合格していることを確認します。 他のコードがまだ機能しているかどうかを手動で確認する必要はありませんでした。 それは私たちがまだ合格したテストのためだということを私たちは知っています。

これまでのところ、私たちのテストは同期コードの結果を検証しました。 非同期コードで動作するように、新しく見つかったテストの習慣をどのように適応させる必要があるかを見てみましょう。

ステップ4—非同期コードのテスト

TODOモジュールに必要な機能の1つは、CSVエクスポート機能です。 これにより、保存されているすべてのTODOが、完了ステータスとともにファイルに出力されます。 これには、 fs module-ファイルシステムを操作するための組み込みのNode.jsモジュール。

ファイルへの書き込みは非同期操作です。 Node.jsのファイルに書き込む方法はたくさんあります。 コールバック、Promise、または async/await キーワード。 このセクションでは、これらのさまざまなメソッドのテストを作成する方法を見ていきます。

コールバック

callback 関数は、非同期関数の引数として使用される関数です。 非同期操作が完了すると呼び出されます。

関数を追加してみましょう Todos と呼ばれるクラス saveToFile(). この関数は、すべてのTODOアイテムをループし、その文字列をファイルに書き込むことによって文字列を作成します。

あなたの index.js ファイル:

  1. nano index.js

このファイルに、次の強調表示されたコードを追加します。

todos / 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のヘッダーで初期化されます。 次に、各TODOアイテムを内部配列の forEach() 方法。 繰り返しながら、 titlecompleted 個人の特性 todos オブジェクト。

最後に、 fs でファイルを書き込むモジュール writeFile() 関数。 最初の引数はファイル名です。 todos.csv. 2つ目はファイルの内容で、この場合は fileContents 変数。 最後の引数は、ファイル書き込みエラーを処理するコールバック関数です。

ファイルを保存して終了します。

今、私たちのテストを書いてみましょう saveToFile() 関数。 このテストでは、ファイルが最初に存在することを確認してから、ファイルの内容が正しいことを確認するという2つのことを行います。

を開きます index.test.js ファイル:

  1. nano index.test.js

ロードすることから始めましょう fs 結果のテストに使用するため、ファイルの先頭にあるモジュール:

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

ここで、ファイルの最後に新しいテストケースを追加しましょう。

todos / 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);
        });
    });
});

以前のように、私たちは使用します describe() 新しい機能が含まれるため、テストを他のテストとは別にグループ化します。 The it() 機能は他のものとは少し異なります。 通常、使用するコールバック関数には引数がありません。 今回は done 引数として。 コールバックを使用して関数をテストする場合は、この引数が必要です。 The done() コールバック関数は、非同期関数が完了したときにそれを通知するためにMochaによって使用されます。

Mochaでテストされているすべてのコールバック関数は、 done() 折り返し電話。 そうでない場合、Mochaは関数がいつ完了したかを知ることができず、シグナルを待ってスタックします。

続けて、私たちは私たちを作成します Todos インスタンスを作成し、それに1つのアイテムを追加します。 次に、 saveToFile() 関数、ファイル書き込みエラーをキャプチャするコールバック付き。 この関数のテストがコールバックにどのように存在するかに注意してください。 テストコードがコールバックの外にある場合、ファイルの書き込みが完了する前にコードが呼び出されている限り、テストコードは失敗します。

コールバック関数では、最初にファイルが存在することを確認します。

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

The fs.existsSync() 関数は戻ります true 引数にファイルパスが存在する場合、 false それ以外は。

<$>[注] ノート: The fs モジュールの機能はデフォルトで非同期です。 ただし、主要な機能については、同期の対応物を作成しました。 このテストは、同期関数を使用することで簡単になります。非同期コードをネストして機能させる必要がないためです。 の中に fs モジュール、同期機能は通常で終了します "Sync" 彼らの名前の最後に。 <$>

次に、期待値を格納する変数を作成します。

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

を使用しております readFileSync()fs ファイルを同期的に読み取るモジュール:

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

私たちは今提供します readFileSync() ファイルの正しいパスを使用して: todos.csv. として readFileSync() を返します Buffer バイナリデータを格納するオブジェクトは、 toString() その値を、保存したと予想される文字列と比較できるようにするためのメソッド。

以前と同様に、 assert モジュールの strictEqual 比較するには:

todos / index.test.js
...
assert.strictEqual(content, expectedFileContents);
...

を呼び出してテストを終了します done() コールバック、Mochaがそのケースのテストを停止することを認識していることを確認します。

todos / index.test.js
...
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で最初の非同期関数をテストしました。 ただし、このチュートリアルの執筆時点では、 Node.jsで非同期コードを作成する方法の記事で説明されているように、Promisesは新しいNode.jsコードでのコールバックよりも一般的です。 次に、Mochaでもテストする方法を学びましょう。

約束

Promise は、最終的に値を返すJavaScriptオブジェクトです。 Promiseが成功すると、解決されます。 エラーが発生すると、拒否されます。

変更してみましょう saveToFile() コールバックの代わりにPromisesを使用するように機能します。 開く index.js:

  1. nano index.js

まず、方法を変更する必要があります fs モジュールがロードされます。 あなたの中で index.js ファイル、変更 require() ファイルの先頭にあるステートメントは次のようになります。

todos / index.js
...
const fs = require('fs').promises;
...

インポートしたばかりです fs コールバックの代わりにPromisesを使用するモジュール。 今、私たちはいくつかの変更を加える必要があります saveToFile() 代わりにPromisesで動作するようにします。

テキストエディタで、次の変更を加えます。 saveToFile() コールバックを削除する関数:

todos / index.js
...
saveToFile() {
    let fileContents = 'Title,Completed\n';
    this.todos.forEach((todo) => {
        fileContents += `${todo.title},${todo.completed}\n`
    });

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

最初の違いは、関数が引数を受け入れなくなったことです。 Promisesでは、コールバック関数は必要ありません。 2番目の変更は、ファイルの書き込み方法に関するものです。 ここで、結果を返します。 writeFile() 約束。

保存して終了します index.js.

Promisesで動作するように、テストを調整してみましょう。 開く index.test.js:

  1. nano index.test.js

変更 saveToFile() これをテストします:

todos / 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);
        });
    });
});

最初に行う必要のある変更は、 done() その引数からのコールバック。 モカが合格した場合 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)

Promiseをテストするときは、 done() のコールバック it().

約束をテストするには、アサーションコードを then() 関数。 テストでこの約束を返すことに注意してください。 catch() キャッチする機能 Promise 拒否されます。

でスローされたエラーが発生するように、promiseを返します。 then() 関数はにバブルアップされます it() 関数。 エラーが発生しない場合、Mochaはテストケースに失敗しません。 Promisesをテストするときは、次を使用する必要があります returnPromise テストされています。 そうでない場合は、誤検知が発生するリスクがあります。

また、 catch() モカは約束が拒否されたときにそれを検出できるためです。 拒否された場合、自動的にテストに失敗します。

テストが完了したので、ファイルを保存して終了し、Mochaを実行します。 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 (18ms)

Promisesを使用するようにコードとテストを変更しましたが、これで確実に機能することがわかりました。 しかし、最新の非同期パターンは async/await キーワードを作成する必要がないので、複数作成する必要はありません then() 成功した結果を処理する関数。 でテストする方法を見てみましょう async/await.

非同期/待機

The async/await キーワードを使用すると、Promisesの操作が冗長になりません。 関数を非同期として定義すると、 async キーワード、私たちはその関数で将来の結果を得ることができます await キーワード。 このようにして、Promisesを使用せずに使用できます。 then() また catch() 機能。

簡素化できます saveToFile() に基づく約束であるテスト async/await. テキストエディタで、これらの小さな編集を saveToFile() でテスト index.test.js:

todos / 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 その本体内のキーワード。

2番目の変更は、 saveToFile(). The 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)

これで、3つの非同期パラダイムのいずれかを適切に使用して非同期関数をテストできます。

Mochaを使用して同期および非同期コードをテストすることで多くのことをカバーしました。 次に、Mochaがテストエクスペリエンスを向上させるために提供する他の機能、特にフックがテスト環境をどのように変更できるかについて、もう少し詳しく見ていきましょう。

ステップ5—フックを使用してテストケースを改善する

フックはMochaの便利な機能であり、テストの前後に環境を構成できます。 通常、フックを describe() 一部のテストケースに固有のセットアップおよびティアダウンロジックが含まれているため、関数ブロック。

Mochaは、テストで使用できる4つのフックを提供します。

  • before:このフックは、最初のテストが開始される前に1回実行されます。
  • beforeEach:このフックは、すべてのテストケースの前に実行されます。
  • after:このフックは、最後のテストケースが完了した後に1回実行されます。
  • afterEach:このフックは、すべてのテストケースの後に実行されます。

関数または機能を複数回テストする場合、フックを使用すると、テストのセットアップコードを分離できるので便利です( todos オブジェクト)テストのアサーションコードから。

フックの値を確認するために、さらにテストを追加してみましょう。 saveToFile() テストブロック。

TODOアイテムをファイルに保存できることを確認しましたが、保存したアイテムは1つだけです。 さらに、アイテムは完了としてマークされませんでした。 モジュールのさまざまな側面が機能することを確認するために、さらにテストを追加しましょう。

まず、TODOアイテムが完成したときにファイルが正しく保存されていることを確認するために、2番目のテストを追加しましょう。 あなたの index.test.js テキストエディタのファイル:

  1. nano index.test.js

最後のテストを次のように変更します。

todos / 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);
    });
});

テストは以前と同じです。 主な違いは、 complete() 呼び出す前に関数 saveToFile()、そしてそれは私たちの expectedFileContents 今持っている true それ以外の false のために completed 列の値。

ファイルを保存して終了します。

新しいテストと他のすべてのテストを実行してみましょう npm test:

  1. 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 ✓ should save a single TODO that's completed 4 passing (26ms)

期待どおりに動作します。 ただし、改善の余地はあります。 両方ともインスタンス化する必要があります Todos テストの開始時にオブジェクト。 テストケースを追加すると、これはすぐに繰り返してメモリを浪費します。 また、テストを実行するたびに、ファイルが作成されます。 これは、モジュールにあまり詳しくない人が実際の出力と間違える可能性があります。 テスト後に出力ファイルをクリーンアップすると便利です。

テストフックを使用してこれらの改善を行いましょう。 を使用します beforeEach() TODOアイテムのテストフィクスチャをセットアップするためのフック。 テストフィクスチャは、テストで使用される一貫した状態です。 私たちの場合、テストフィクスチャは新しいものです todos すでに1つのTODOアイテムが追加されているオブジェクト。 次に使用します afterEach() テストによって作成されたファイルを削除します。

index.test.js、最後のテストに次の変更を加えます saveToFile():

todos / 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);
    });
});

行ったすべての変更を分析してみましょう。 追加しました beforeEach() テストブロックへのブロック:

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

これらの2行のコードは、新しい Todos 各テストで使用できるオブジェクト。 モカと一緒に、 this のオブジェクト beforeEach() 同じを指します this のオブジェクト it(). this 内のすべてのコードブロックで同じです describe() ブロック。 詳細については this、チュートリアルこれを理解し、JavaScriptでバインド、呼び出し、適用するを参照してください。

この強力なコンテキスト共有により、両方のテストで機能するテストフィクスチャをすばやく作成できます。

次に、CSVファイルをクリーンアップします。 afterEach() 関数:

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

テストが失敗した場合は、ファイルが作成されていない可能性があります。 そのため、使用する前にファイルが存在するかどうかを確認します。 unlinkSync() それを削除する関数。

残りの変更は、参照をから切り替えます todos、以前に作成された it() 機能、へ this.todos これは、Mochaコンテキストで使用できます。 以前にインスタンス化した行も削除しました todos 個々のテストケースで。

それでは、このファイルを実行して、テストがまだ機能することを確認しましょう。 入る 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 without error ✓ should save a single TODO that's completed 4 passing (20ms)

結果は同じであり、利点として、新しいテストのセットアップ時間をわずかに短縮しました。 saveToFile() 関数を実行し、残りのCSVファイルの解決策を見つけました。

結論

このチュートリアルでは、TODOアイテムを管理するNode.jsモジュールを作成し、Node.jsREPLを使用してコードを手動でテストしました。 次に、テストファイルを作成し、Mochaフレームワークを使用して自動テストを実行しました。 とともに assert モジュールでは、コードが機能することを確認できました。 また、Mochaを使用して同期および非同期機能をテストしました。 最後に、Mochaを使用してフックを作成しました。これにより、関連する複数のテストケースの記述がはるかに読みやすく保守しやすくなります。

この理解を身に付けて、作成する新しいNode.jsモジュールのテストを作成してみてください。 コードを書く前に、関数の入力と出力について考え、テストを書くことができますか?

Mochaテストフレームワークの詳細については、Mochaの公式ドキュメントをご覧ください。 Node.jsの学習を続けたい場合は、Node.jsシリーズのコーディング方法ページに戻ることができます。