8

私は単体テストに慣れていないので、何かが欠けている可能性がありますが、requirejs モジュールを完全にテスト可能にするためにどのように構造化すればよいでしょうか? エレガントな公開モジュール パターンを考えてみましょう。

define([], function () {
    "use strict";

    var func1 = function(){
        var data = func2();
    };
    var func2 = function(){
        return db.call();
    };

    return {
        func1 : func1
    }
});

私の知る限り、これは requirejs モジュールを構築するための最も一般的なパターンです。私が間違っている場合は、私を修正してください! func1したがって、この単純なシナリオでは、グローバルであるため、戻り値と動作を簡単にテストできます。ただし、テストするにfunc2は、参照も返す必要があります。右?

return {
    func1 : func1,
    _test_func2 : func2
}

これにより、コードの見栄えが若干悪くなりますが、全体的には問題ありません。ただし、そのメソッドがクロージャー内にあるfunc2ため、その戻り値を使用し てモックして置き換えたい場合はできません。Jasmine spy

私の質問は、requirejs モジュールを完全にテストできるように構成する方法です。モジュールパターンを明らかにするよりも、この状況に適したパターンはありますか?

4

2 に答える 2

8

プライベート関数 func2 をテストしますか?

開発者は、プライベート関数のテストを作成しようとすると、単体テストのポイントを見逃していると思います。

依存関係は、ソフトウェアを開発するときに私たちを悩ませるものです。そして、依存関係が多いほど、スクイーズがきつくなります。したがって、モジュールの内部動作に依存する多くのテストがある場合、内部実装を変更したい場合は非常に困難になります。したがって、テストをパブリック インターフェイスに依存させ、プライベートなものをプライベートに保ちます。

私のアドバイス:

  1. モジュールへのパブリック インターフェイスを設計します。
  2. パブリック インターフェイスに対するテストを記述して、予想される動作を指定します。
  3. そのテストに合格するために必要なコードを実装します。
  4. リファクタリング (必要な場合)
  5. すべての機能がテストによって定義され、すべてのテストに合格するまで、ステップ 2 から繰り返します。

実装およびリファクタリングの段階で、モジュールの内部が変更されます。たとえば、func2 はさまざまな関数に分割できます。そして危険なのは、特に func2 のテストがある場合、リファクタリング時にテストを書き直さなければならない可能性があることです。

単体テストの主な利点の 1 つは、モジュールの内部動作を変更したときに、既存の機能を壊さないようにすることです。リファクタリングによってテストを更新する必要がある場合、そのメリットが失われ始めます。

func2 のコードが非常に複雑になり、明示的にテストする必要がある場合は、それを別のモジュールに抽出し、パブリック インターフェイスに対する単体テストで動作を定義します。理解しやすいパブリック インターフェイスを持つ、十分にテストされた小さなモジュールを目指します。

単体テストに関するヘルプが必要な場合は、Kent Beck の書籍「TDD by example」を強くお勧めします。単体テストが不十分に記述されていると、メリットではなく障害になります。私の意見では、TDD が唯一の方法です。

于 2013-10-30T14:51:57.823 に答える
6

モジュール内の関数がモジュールの他の関数を直接 (つまり、モジュールに対してローカルな参照を使用して)呼び出す場合、これらの呼び出しを外部からインターセプトする方法はありません。ただし、モジュール内の関数がモジュールのコードと同じ方法でモジュールの関数を呼び出すようにモジュールを変更すると、これらの呼び出しをインターセプトできます。

必要なものを許可する例を次に示します。

define([], function () {
    "use strict";

    var foo = function(){
        return exports.bar();
    };

    var bar = function(){
        return "original";
    };

    var exports =  {
        foo: foo,
        bar: bar
    };

    return exports;
});

重要なのは、直接呼び出すのでfooはなく、exportsアクセスすることです。bar

ここに実行可能な例を示しました。spec/main.spec.jsファイルには次が含まれます。

    expect(moduleA.foo()).toEqual("original");

    spyOn(moduleA, "bar").andReturn("patched");

    expect(moduleA.foo()).toEqual("patched");

bar関数にパッチが適用されていますfooが、パッチの影響を受けていることがわかります。

また、永続的にテスト コードによってエクスポートが汚染されるのを避けるために、モジュールがテスト環境で実行され、テスト モードでのみテストに必要な関数をエクスポートするかどうかを判断するために環境チェックを行うことがあります。これが私が書いた実際のコードの例です:

var options = module.config();
var test = options && options.test;

[...]
// For testing only
if (test) {
    exports.__test = {
        $modal: $modal,
        reset: _reset,
        is_terminating: _is_terminating
    };
}

requirejs 構成がモジュールを ( を使用してconfig) 構成し、testオプションが true 値に設定されている場合、__testエクスポートには、モジュールのテスト時にエクスポートするいくつかの追加項目を含むシンボルがさらに含まれます。それ以外の場合、これらのシンボルは使用できません。

編集:上記の最初の方法で、内部関数へのすべての呼び出しの前に を付けなければならないことが気になる場合は、次のようexportsにすることができます。

define(["module"], function (module) {
    "use strict";

    var debug = module.config().debug;
    var exports = {};

    /**
     * @function
     * @param {String} name Name of the function to export
     * @param {Function} f Function to export.
     * @returns {Function} A wrapper for <code>f</code>, or <code>f</code>.
     */
    var _dynamic = (debug ?
        function (name, f) {
            exports[name] = f;
            return function () {
                // This call allows for future changes to arguments passed..
                return exports[name].apply(this, arguments);
            };
        } :
        _dynamic = function (name, f) { return f; });

    var foo = function () {
        return bar(1, 2, 3);
    };

    var bar = _dynamic("bar", function (a, b, c) {
        return "original: called with " + a + " " + b + " " + c;
    });

    exports.foo = foo;

    return exports;
});

RequireJS 構成が上記のモジュールを構成し、これdebugが true になると、 によってラップされた関数がエクスポートされ_dynamic を経由せずにそれらを参照できるローカル シンボルが提供されますexports。が false の場合debug、関数はエクスポートされず、ラップされません。この方法を示すためにを更新しました。例にmoduleBあります。

于 2013-10-28T11:31:24.873 に答える