首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何编写可测试的需求模块

如何编写可测试的需求模块
EN

Stack Overflow用户
提问于 2013-10-25 11:29:45
回答 2查看 1K关注 0票数 8

我刚开始进行单元测试,所以我可能遗漏了一些东西,但是我应该如何构造需求模块,以使它们完全可测试呢?考虑一下优雅的显示模块模式。

代码语言:javascript
复制
define([], function () {
    "use strict";

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

    return {
        func1 : func1
    }
});

据我所知,这是构建需求模块的最常见模式。如果我错了,请纠正我!因此,在这个简单的场景中,我可以很容易地测试func1的返回值和行为,因为它是全局的。但是,为了测试func2,我还必须返回它的引用。对吗?

代码语言:javascript
复制
return {
    func1 : func1,
    _test_func2 : func2
}

这使得代码稍微不那么漂亮,但总体上还是可以的。但是,如果我想模拟func2并使用Jasmine spy替换它的返回值,我将无法这样做,因为该方法在闭包中。

那么,我的问题是如何构造需求模块以使其完全可测试呢?对于这种情况,是否有比显示模块模式更好的模式?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-10-28 11:31:24

如果模块中的函数直接调用模块的其他函数(即通过使用模块的本地引用),则无法从外部拦截这些调用。但是,如果您更改了模块,使其内部的函数以与外部代码相同的方式调用模块的函数,那么您可以拦截这些调用。

下面是一个允许您想要的示例:

代码语言:javascript
复制
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文件包含:

代码语言:javascript
复制
    expect(moduleA.foo()).toEqual("original");

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

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

您会注意到,bar是修补的函数,但是foo受到补丁的影响。

此外,为了避免测试代码永久地污染导出,我有时会进行环境检查,以确定模块是否在测试环境中运行,并将导出测试只在测试模式下的所需的函数。下面是我编写的实际代码示例:

代码语言:javascript
复制
var options = module.config();
var test = options && options.test;

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

如果请求配置将我的模块(使用config)配置为将test选项设置为真值,则导出将另外包含一个__test符号,该符号包含测试模块时想要导出的几个附加项。否则,这些符号将不可用。

编辑:--如果上面第一种方法让您感到困扰的是必须用exports对内部函数的所有调用进行前缀,您可以这样做:

代码语言:javascript
复制
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为真时,它导出由_dynamic包装的函数,并提供允许引用它们的本地符号,而无需经过exports。如果debug为false,则该函数不会导出,也不会被包装。我更新了示例以显示此方法。在这个例子中它是moduleB

票数 6
EN

Stack Overflow用户

发布于 2013-10-30 14:51:57

您确定要测试私有函数func2吗?

我认为开发人员在尝试为私有函数编写测试时忽略了单元测试的要点。

在开发软件时,依赖关系是最关键的因素。依赖性越大,压力就越大。因此,如果您有许多依赖于模块内部工作的测试,那么当您想要更改内部实现时,这将是非常痛苦的。因此,让您的测试依赖于公共接口,并将私有内容保持为私有。

我的建议是:

  1. 设计模块的公共接口。
  2. 针对公共接口编写测试,以指定某些预期的行为。
  3. 实现通过测试所需的代码。
  4. 重构(如有必要)
  5. 从步骤2开始重复,直到测试定义了所有的功能,并且所有的测试都通过了。

在实现和重构阶段,模块的内部结构将发生变化。例如,可以将func2划分为不同的函数。危险在于,如果您有专门针对func2的测试,那么您可能需要在重构时重写测试。

单元测试的主要好处之一是,当我们更改模块的内部工作时,它们可以确保我们不会破坏现有的功能。如果重构意味着需要更新测试,则开始失去这种好处。

如果func2中的代码变得如此复杂,您想要显式地测试它,那么将其提取到一个单独的模块中,在该模块中,您将使用针对公共接口的单元测试来定义行为。目标小,测试良好的模块,有一个易于理解的公共接口。

如果您正在寻找有关单元测试的帮助,我会彻底地推荐Kent的"TDD由示例“一书。编写不好的单元测试将成为一种障碍,而不是一种好处,在我看来,TDD是唯一的出路。

票数 8
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19588622

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档