2010-05-05 3 views
8

Dans de nombreux cadres, les variables internes de la fonction sont utilisées comme variables privées, par exemplevariables de la fonction intérieure d'accès en Javascript

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

ici, nous ne pouvons pas accéder à partir de l'espace de noms global de la variable nommée private, car il est un intérieur variable de la fonction anonyme dans la première ligne.

Parfois, cette fonction contient un grand framework Javascript, de sorte qu'elle ne pollue pas l'espace de noms global.

J'ai besoin de tester un objet Raphael utilise en interne (dans l'exemple ci-dessus, je souhaite exécuter des tests unitaires sur l'objet private). Comment puis-je les tester?

edit: J'ai reçu des commentaires sur des tests unitaires censés tester des interfaces publiques. Permettez-moi de spécifier un cas d'utilisation. J'écris une bibliothèque appelée Raphael. Cette bibliothèque est supposée ajouter un seul nom à l'espace de noms global, et rien de plus. Ceci est une exigence particulière pour Javascript, car Javascript n'a pas d'espaces de noms.

Disons que Raphael utilise une liste chaînée. Si Javascript avait la notion de paquets, je ferais

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

Cependant Javascript ne me permet pas de le faire d'une façon qui ne polluerait pas la portée globale avec l'objet de liste chaînée! Je suis donc lié à inline linked_list dans le périmètre local Raphaël:

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

Et maintenant, je veux tester linked_list mise en œuvre.

+3

l'idée derrière unittests est de tester uniquement les fonctions/méthodes publiques – Andrey

+0

@Andrey, Javascript n'est pas votre langue de tous les jours! Vous ne pouvez pas importer d'autres modules de façon rationnelle, donc si je dois inclure une petite implémentation de liste liée à Raphael, elle devra être intégrée dans la fonction interne de Raphael, afin qu'elle ne soit pas exposée à l'utilisateur de Raphael. Je ne peux pas simplement le "# inclure" ... –

+1

Voir: http://stackoverflow.com/questions/2230469/javascript-sandbox-unit-testing http://stackoverflow.com/questions/716207/testing-private -functions-in-javascript http://stackoverflow.com/questions/1881078/testing-javascript-functions-inside-anonymous-functions et tous – gnarf

Répondre

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

Et comment pourrais-je annuler le code "test" quand je livre la bibliothèque aux utilisateurs , '# ifdef' ;-) –

+0

Oui, vous devez déclarer la variable de test directement en haut du fichier ou quelque chose. –

2

Essayez ceci:

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

Juste une petite injection de fonction

+1

L'idée d'avoir 'private' comme var dans le corps de la fonction n'est pas de polluer la portée globale. Dans votre solution 'Raphael' pollue la portée globale avec' adder'. –

+0

Si vous allez utiliser les propriétés privées, vous devez avoir une variable/fonction de détection à tester sur la propriété privée. ou vous devez vous en tenir à l'interface publique. De plus, vous pouvez ajouter une autre couche d'espace de noms à votre code et le rendre privé au lieu d'où il est. Testez le niveau inférieur de l'unité, puis exécutez un test d'intégration sur la nouvelle couche API. – Gutzofter

12

vous manquent le point.

Le but du test unitaire est de vérifier que l'interface publique de l'objet fait ce que l'on attend de lui. Les tests unitaires montrent comment le code fonctionne.

La seule chose qui doit être testée est l'interface publique de l'objet. De cette façon, lorsque le développeur souhaite modifier la façon dont l'objet est implémenté, vous ne vous souciez que si l'objet testé fait toujours ce que l'on attend d'elle. Si vous pensez que l'objet qui se trouve à l'intérieur de cette fermeture doit être testé, testez-le, mais faites-le de l'extérieur, puis passez-le dans la fermeture.

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

Les hacks parasites, tels que montrés ci-dessous, sont totalement inappropriés (dans des tests unitaires ou n'importe où).

Les fonctions de test doivent être simples, ne tester qu'une chose et avoir une assertion. Cela peut généralement se produire dans trois à dix lignes de code de test.Quand vous arrivez au point où vos fonctions de test sont compliquées, car elles suivraient l'approche que vous posez, alors (1) réalisera que votre design pourrait ne pas être ce que vous voulez qu'il soit et changer ou (2) changer vos attentes dans le test.

En ce qui concerne le code que vous avez publié, vous avez oublié var, avez manqué un point-virgule et utilisé deux mots réservés comme identifiants: private et public. La conséquence de ne pas utiliser var est la possibilité de déclencher des erreurs et des problèmes liés à diverses implémentations d'objets de type non standard GlobalScopePolluter ("Objet ne prend pas en charge cette propriété ou méthode" vu dans IE). La conséquence de l'utilisation d'un FutureReservedWord est SyntaxError. L'implémentation peut fournir une extension de syntaxe à permettre à FutureReservedWord comme identifiant, et en effet beaucoup le font, cependant il est préférable de ne pas compter sur de telles extensions et si vous avez une erreur, ce serait complètement votre faute.

Vous avez mentionné l'envoi de code aux utilisateurs. Je suggère que vous ne fassiez pas cela jusqu'à ce que vous obteniez plus d'expérience et de compréhension avec ce que vous faites.

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

Cet exemple de code est seulement une démonstration qu'une telle chose est possible. Étant donné le choix, c'est une alternative pauvre aux alternatives mentionnées plus tôt; soit changez votre design ou changez vos tests.

+0

@ Garrett, une raison pour déclarer la fonction() {} au lieu de juste {}. J'aime ça! Normalement, quand je déclare un 'var', j'aime bien coder le type juste pour le codage défensif. – Gutzofter

+0

Si vous voulez dire var leakedFunction = function(){}; - Je le fais parce que leakedFunction est appelé plus tard. – Garrett

+0

Mais seulement après l'avoir réaffecté avec ce 'arguments.callee.caller'. Oui? – Gutzofter

1

La meilleure solution que je suis venu avec:

Dans la source des fichiers Javascript

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

Maintenant, utilisez un script pour extraire les objets entre // start ([a-zA-Z_]*) et // end ([a-zA-Z_]*) et test unitaire du code extrait.

Apparemment, il est impossible d'accéder à des variables dans la portée interne d'une fonction à partir d'une étendue externe. Comme écrit dans la question SO Jason a lié à dans les commentaires.

Questions connexes