【问题标题】:Access inner function variables in Javascript在 Javascript 中访问内部函数变量
【发布时间】:2010-05-05 18:31:08
【问题描述】:

在很多框架中,内部函数变量被用作私有变量,例如

Raphael = (function(){
    var _private = function(a,b) {return a+b;};
    var _public = function(a) {return _private(a,a);}
    var object = {mult2:_public};
    return object;
})();

这里,我们不能从全局命名空间访问名为private的变量,因为它是第一行匿名函数的内部变量。

有时这个函数包含一个大的 Javascript 框架,所以它不会污染全局命名空间。

我需要对一些内部使用的对象Raphael 进行单元测试(在上面的示例中,我希望对对象private 运行单元测试)。我该如何测试它们?

编辑:我收到了关于单元测试的 cmets,这些单元测试应该测试公共接口。

让我指定一个用例。我正在编写一个名为Raphael 的库。这个库应该只向全局命名空间添加一个名称,仅此而已。这是 Javascript 的特殊要求,因为 Javascript 没有命名空间。

假设Raphael 使用链表。如果 Javascript 有包的概念,我会这样做

require 'linked_list'
Raphael = (function(){/* use linked list */})();

然而 Javascript 不允许我以任何不会污染链表对象的全局范围的方式这样做!因此,我必须将linked_list 内联到 Raphael 的本地范围:

Raphael = (function(){
    /* implement linked list */
    var linked_list = function(){/*implementation*/};
})();

现在我想测试linked_list 的实现。

【问题讨论】:

标签: javascript unit-testing private-methods


【解决方案1】:

你仍然没有抓住重点。

单元测试的重点是验证对象的公共接口是否符合预期。单元测试显示代码是如何工作的。

唯一需要测试的是对象的公共接口。这样,当您的开发人员想要更改对象的实现方式时,您只需担心正在测试的对象是否仍然按照预期执行。

如果您觉得该闭包内的对象需要测试,请对其进行测试,但在外部进行,然后将其传递给闭包。

var Raphael= function(listIterator) {
  listIterator.method();
}(new ListIterator());

如下所示的虚假 hack 是完全不合适的(在单元测试或任何地方)。

测试函数应该简单,只测试一件事,并且有一个断言。这通常发生在三到十行测试代码中。

当您的测试功能变得复杂时,因为它们会遵循您所询问的方法,那么要么 (1) 意识到你的设计可能不是你想要的样子,然后改变它,或者 (2) 改变你在测试中的期望。

关于您发布的代码,您忘记了var,遗漏了一个分号,并使用了两个保留字作为标识符:privatepublic

不使用var 的后果是可能触发与非标准GlobalScopePolluter 类型对象的各种实现相关的错误和问题(在IE 中看到“对象不支持此属性或方法”)。使用 FutureReservedWord 的结果是SyntaxError。实现可能会提供一个语法扩展以将 allow FutureReservedWord 作为标识符,确实有很多这样做,但是最好不要依赖这样的扩展,如果出现错误,那完全是你的错。

您提到向用户交付代码。我建议你在获得更多经验和理解你正在做的事情之前不要这样做。

// DO NOT USE THIS CODE.
var Raphael = (function(){
    var _private = function(a,b) {return a+b;};
    var _public = function(a) {return _private(a,a);};
    var object = {mult2:_public};
    return object;
})();

var leakedFunction;

// Spurious hack:
//   Give valueOf a side effect of leaking function.
//   valueOf is called by the _private function as a
//   side effect of primitive conversion, where 
//   ToPrimitive(input argument, hint Number) results 
//   in calling valueOf.

function valueOfSnoop(){ 
    leakedFunction = leakedFunction || valueOfSnoop.caller || function(){};
    return 2;
}

var a = {
  valueOf : valueOfSnoop
};

Raphael.mult2(a, 3);
var privateMathod = leakedFunction;
alert(leakedFunction(1, 2));

该示例代码只是为了证明这种事情是可能的。鉴于选择,它是前面提到的替代方案的糟糕替代方案;要么改变你的设计,要么改变你的测试。

【讨论】:

  • @Garrett,声明 function(){} 而不仅仅是 {} 的任何原因。我喜欢!通常在声明var 时,我喜欢为防御性编码编写类型。
  • 如果你的意思是 var leakedFunction = function(){}; - 我这样做是因为 leakedFunction 稍后会被调用。
  • 但只有在你用这个arguments.callee.caller重新分配它之后。是吗?
  • 嗯,是的,leakedFunction is set to arguments.callee.caller` 在 valueOf 中。如果 function.caller 或 arguments.callee 不可用,我会这样做。任务应该进去了,我可以重用一个函数...正在编辑...
【解决方案2】:

试试这个:

var adder = function(a,b) {
    return a + b;
}

Raphael = function(fn){
    var _private = function(a,b) {
        fn(a,b);
    }

    var _public = function(a) {
        return _private(a,a);
    }

    var object = {doubleIt: _public};

    return object;
}(adder);

只是一点函数注入

【讨论】:

  • private 作为函数体中的变量的整个想法是不污染全局范围。在您的解决方案中,Raphael 使用 adder 污染了全局范围。
  • 如果你打算使用“私有属性”,你要么必须有一个传感变量/函数来测试私有属性。或者您必须坚持使用公共界面。您还可以在代码中添加另一个命名空间层,并将其设为私有,而不是其所在的位置。单元测试较低级别,然后在新层 API 上运行集成测试。
【解决方案3】:

我想出的最佳解决方案:

在源 Javascript 文件中使用

Raphael = (function(){
// start linked_list
    var linked_list = function() {/*...*/};
// end linked_list
    var object = {mult2:_public};
    return object;
})();

现在,使用脚本提取// start ([a-zA-Z_]*)// end ([a-zA-Z_]*) 之间的对象,并对提取的代码进行单元测试。

显然,从外部范围访问函数内部范围内的变量是不可能的。正如在 cmets 中链接到的 SO question Jason 中所写。

【讨论】:

    【解决方案4】:
    var Raphael;
    var test = true; //or false;
    
    Raphael = (function(){
        var private = function(a,b) {return a+b;};
        var public = function(a) {return private(a,a);}
        var object = {mult2:public};
    
        if (test) Raphael.private = private;
    
        return object;
    })();
    

    【讨论】:

    • 当我将库交付给用户时,我将如何取消“测试”代码,#ifdef ;-)
    • 是的,您必须在文件顶部声明测试变量或其他内容。
    猜你喜欢
    • 1970-01-01
    • 2017-02-02
    • 2017-06-01
    • 2013-02-17
    • 2015-04-29
    • 2012-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多