在Javascript中访问内部函数变量

时间:2010-05-05 18:31:09

标签: javascript unit-testing private-methods

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

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运行单元测试)。我该如何测试它们?

编辑:我收到了有关测试公共接口的单元测试的评论。

让我指出一个用例。我正在写一个名为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实现。

4 个答案:

答案 0 :(得分:12)

你仍然忽略了这一点。

单元测试的目的是验证对象的公共接口是否符合预期。单元测试显示代码的工作原理。

唯一需要测试的是对象的公共接口。这样,当开发人员想要更改对象的实现方式时,您只会担心被测对象是否仍然能够达到预期的效果。

如果你觉得那个闭包内的对象需要测试,那么测试一下,但是在外部做,然后将它传递给闭包。

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

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

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

当你到达测试功能复杂的程度时,因为他们会遵循你所询问的方法,然后要么 (1)意识到你的设计可能不是你想要的,并改变它,或者(2)改变你对测试的期望。

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

不使用var的结果是可能触发与非标准GlobalScopePolluter - 类型对象的各种实现相关的错误和问题(“对象不支持此属性或方法”在IE中看到)。使用FutureReservedWord的结果是SyntaxError。实现可以提供允许 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));

该示例代码仅用于证明这样的事情是可能的。鉴于这种选择,它是前面提到的替代品的不良替代品;要么改变你的设计,要么改变你的测试。

答案 1 :(得分: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);

只需一点功能注射

答案 2 :(得分:1)

我提出的最佳解决方案:

在源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_]*)之间提取对象,并对提取的代码进行单元测试。

显然,无法从外部作用域访问函数内部范围中的变量。正如Jason在评论中所提到的SO问题所写的那样。

答案 3 :(得分:0)

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;
})();