2010-05-05 11 views
8

In vielen Frameworks werden interne Funktionsvariablen als private Variablen verwendet, zum BeispielZugang innere Funktionsvariablen in 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; 
})(); 

hier, wir nicht private aus dem globalen Namensraum der Variablen mit dem Namen zugreifen können, als es ein inneres ist Variable der anonymen Funktion in der ersten Zeile.

Manchmal enthält diese Funktion ein großes Javascript-Framework, so dass es den globalen Namespace nicht verschmutzen würde.

Ich brauche Unit-Tests einige Objekt Raphael verwendet intern (im obigen Beispiel möchte ich Unit-Tests auf das Objekt private ausführen). Wie kann ich sie testen?

bearbeiten: Ich erhielt Kommentare über Unit-Tests, die öffentliche Schnittstellen testen sollen.

Lassen Sie mich einen Anwendungsfall angeben. Ich schreibe eine Bibliothek namens Raphael. Diese Bibliothek soll dem globalen Namespace nur einen einzigen Namen hinzufügen, und nichts mehr. Dies ist eine besondere Voraussetzung für Javascript, da Javascript keine Namespaces hat.

Nehmen wir an, Raphael verwendet eine verkettete Liste. Wenn Sie Javascript um die Vorstellung der Pakete hätte, würde ich tun

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

aber Javascript erlaubt mir nicht, dass in irgendeiner Weise zu tun, die nicht die globale Reichweite mit der verknüpften Liste Objekt verschmutzen würde!

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

Und nun möchte ich linked_list Implementierung testen: Ich bin daher Inline linked_list in Raffaels lokalen Bereich gebunden.

+3

Die Idee hinter Unittests ist, öffentliche Funktionen/Methoden nur zu testen – Andrey

+0

@Andrey, Javascript ist nicht Ihre Alltagssprache! Sie können andere Module nicht vernünftig importieren. Wenn ich also eine kleine, sagenumwobene, verknüpfte Listenimplementierung für Raphael verwenden möchte, muss sie in Raphaels interne Funktion eingebunden werden, damit sie nicht angezeigt wird der Benutzer von Raphael. Ich kann nicht einfach "#" es einschließen ... –

+1

Siehe: 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 all – gnarf

Antwort

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

Und wie würde ich den "Test" -Code abbrechen, wenn ich die Bibliothek an die Benutzer liefern , '# ifdef' ;-) –

+0

Ja, Sie müssen die Testvariable direkt an der Spitze der Datei oder etwas deklarieren. –

2

Versuchen Sie folgendes:

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

Nur eine kleine Funktion Injektion

+1

Die ganze Idee, "privat" als Var im Funktionskörper zu haben, soll den globalen Geltungsbereich nicht verschmutzen. In Ihrer Lösung verschmutzt "Raphael" den globalen Geltungsbereich mit "Addierer". –

+0

Wenn Sie die Route der Verwendung von "privaten Eigenschaften" gehen, müssen Sie entweder eine Erfassungsvariable/Funktion zum Testen auf der privaten Eigenschaft haben. oder du musst dich an die öffentliche Schnittstelle halten. Sie können Ihrem Code auch eine weitere Namespace-Ebene hinzufügen und sie zu ihrer eigenen anstatt zu ihrer eigenen machen. Unit test low level, führen Sie dann einen Integrationstest für die neue Layer-API aus. – Gutzofter

12

Sie fehlen noch den Punkt.

Der Punkt der Komponententests ist zu überprüfen, dass die öffentliche Schnittstelle des Objekts tut, was von ihm erwartet wird. Unit-Tests zeigen, wie der Code funktioniert.

Das einzige, was getestet werden sollte, ist die öffentliche Schnittstelle des Objekts. Wenn der Entwickler die Art und Weise ändern möchte, wie das Objekt implementiert wird, müssen Sie sich nur Gedanken darüber machen, ob das getestete Objekt immer noch das tut, was es erwartet.

Wenn Sie das Gefühl haben, dass das Objekt, das in diesem Verschluss ist, getestet werden muss, dann testen Sie es, aber machen Sie es extern und geben Sie es dann in den Verschluss.

Unerwünschte Hacks, wie sie unten gezeigt werden, sind völlig ungeeignet (in Unit Tests oder überall).

Testfunktionen sollten einfach sein, nur eine Sache testen und eine Aussage haben. Dies kann normalerweise in drei bis zehn Zeilen Testcode passieren.

Wenn Sie zu dem Punkt kommen, an dem Ihre Testfunktionen kompliziert sind, da sie dem von Ihnen gewünschten Ansatz folgen würden, dann erkennen Sie entweder, dass Ihr Entwurf nicht das ist, was Sie wollen und ändern es so, dass es, oder (2) Ihre Erwartungen im Test ändert.

Bezüglich des von Ihnen veröffentlichten Codes haben Sie var vergessen, ein Semikolon verpasst und zwei reservierte Wörter als Bezeichner verwendet: private und public.

Die Konsequenz der Nichtbenutzung var ist die Möglichkeit, Fehler und Probleme im Zusammenhang mit verschiedenen Implementierungen von Nicht-Standard GlobalScopePolluter-Typ-Objekten auszulösen ("Objekt unterstützt diese Eigenschaft oder Methode nicht" in IE gesehen). Die Konsequenz der Verwendung eines FutureReservedWord ist SyntaxError. Die Implementierung kann einen Syntax-Extension zu ermöglichen FutureReservedWord als Bezeichner, und in der Tat viele tun, aber es ist am besten, nicht auf solche Erweiterungen verlassen und wenn Sie einen Fehler haben, wäre es vollständig Ihre Schuld.

Sie erwähnten die Bereitstellung von Code für Benutzer. Ich schlage vor, dass Sie das nicht tun, bis Sie etwas mehr Erfahrung und Verständnis mit dem haben, was Sie tun.

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

Dieser Beispielcode dient nur als Demonstration, dass so etwas möglich ist. Angesichts der Wahl ist es eine schlechte Alternative zu den zuvor genannten Alternativen; entweder ändern Sie Ihr Design oder ändern Sie Ihre Tests.

+0

@Garrett, einen Grund für die Erklärung von Funktion() {} statt nur {}. Ich mag das! Normalerweise deklariere ich bei der Deklaration einer 'var' den Typ nur für defensive Codierung. – Gutzofter

+0

Wenn Sie var leakedFunction = function(){}; bedeuten - ich mache das, weil leakedFunction später aufgerufen wird. – Garrett

+0

Aber erst, nachdem Sie es mit diesem 'arguments.callee.caller' neu zugewiesen haben. Ja? – Gutzofter

1

Die beste Lösung kam ich mit:

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

nun ein Skript verwenden, um Objekte zwischen // start ([a-zA-Z_]*) und // end ([a-zA-Z_]*) und Unit-Test des extrahierte Code zu extrahieren.

Anscheinend ist es unmöglich, auf Variablen im inneren Bereich einer Funktion von einem äußeren Bereich aus zuzugreifen. Wie in der SO-Frage geschrieben steht Jason in den Kommentaren.