2008-11-28 8 views
8

Ich komme aus der Java-Welt, wo Sie Variablen und Funktionen verstecken und dann Unit-Tests gegen sie mithilfe von Reflektion ausführen können. Ich habe verschachtelte Funktionen verwendet, um Implementierungsdetails meiner Klassen zu verbergen, so dass nur die öffentliche API sichtbar ist. Ich versuche Einheitentests gegen diese verschachtelten Funktionen zu schreiben, um sicherzustellen, dass ich sie nicht bei der Entwicklung zerbringe. Ich habe eine der verschachtelten Funktionen versuchte Aufruf wie:Laufende Unit Tests auf verschachtelte Funktionen

def outer(): 
    def inner(): 
     pass 

outer.inner() 

, die in der Fehlermeldung führt:

AttributeError: 'function' object has no attribute 'inner'

Gibt es eine Möglichkeit für mich, Unit-Tests gegen diese verschachtelten Funktionen zu schreiben? Wenn nicht, gibt es eine Möglichkeit, den Namen für Funktionsnamen wie für Klassenvariablen auszulösen, indem man ihnen __ vorschreibt?

Antwort

5

Die Python-Konvention besteht darin, "private" Funktionen und Methoden mit einem führenden Unterstrich zu benennen. Wenn Sie einen führenden Unterstrich sehen, wissen Sie, dass Sie ihn nicht verwenden sollen.

Denken Sie daran, Python is not Java.

+11

Wie dies die Frage nicht beantworten? Gibt es gute Lösungen, solche inneren Funktionen für Komponententests zu isolieren oder nicht? Auch ohne Zugang zum inneren vom äußeren Funktionsobjekt, könnte dies scheinbar mit der Verwendung von Python-Modulen Parser, Ast oder Tokenizer erreicht werden, um den Code selbst zu würfeln. Offensichtlich ist das einfacher zu sagen als zu kodieren. :-) –

+1

Es beantwortet die Frage, weil man nicht * private * Implementierungsdetails * testet. Sie können sich auf Testabdeckungstools verlassen, um Ihnen zu sagen, ob Sie Tests der öffentlichen Schnittstelle die private Implementierung angemessen ausführen. – SingleNegationElimination

+0

@Tim: Basierend auf dem letzten Satz in der Frage, ist der Autor gut mit einer nicht-inneren Funktion, wenn es als privat markiert werden kann. Daher ist es nützlich, diese Konvention zu kennen. – orip

11

innere existiert nicht, bis äußere macht es. Sie sollten entweder innerlich zu einer Toplevel-Funktion zur Testbarkeit wechseln oder den äußeren Test alle möglichen Ausführungspfade von sich selbst und von innen testen lassen.

Beachten Sie, dass die innere Funktion keine einfache Funktion ist, es ist eine Schließung. Betrachte diesen Fall:

def outer(a): 
    b = compute_something_from(a) 
    def inner(): 
     do_something_with(a, b) 

Das ist der Standardtestbarkeits-Kompromiss. Wenn Ihre cyclomatic complexity zu hoch ist, werden Ihre Tests zu zahlreich sein.

1

Ich glaube nicht, dass es eine Chance gibt, auf inner() vom externen Namespace zuzugreifen.

Meiner Meinung nach bedeutet die Tatsache, dass Sie inner() verschachtelt haben, dass der einzige "Vertrag", der wirklich zählt, der äußere() ist. inner() ist Teil der Implementierung, und Sie sollten die Implementierung nicht testen wollen. Wenn Sie wirklich inner() testen möchten, führen Sie umfangreiche Tests auf external() mit Daten durch, die alle Funktionen von inner() beinhalten.

+0

Es ist möglich, auf die innere Funktion in cpython zu gelangen, glaube ich. Es gibt eine Antwort unten, die genau zeigt, wie das geht. – Aditya369

2

Keine Möglichkeit, innere Funktion vom äußeren Funktionsobjekt zu bekommen (siehe die anderen Antworten!). Dennoch haben sowohl Komponententests als auch -schließungen (zumindest für mich) erstaunliche Verbesserungen der Entwicklerleistung gebracht. Können wir beides haben? Können wir verschachtelte Funktionen isoliert testen?

Nicht leicht.

Allerdings könnte dies mit der Verwendung von Python-Modulen Parser, Ast oder Tokenizer erreicht werden, um den Code selbst zu entschlüsseln, innere Funktionen (durch einen Weg durch die Verschachtelung) zu extrahieren und Tests mit Status aus ausführen zu lassen umschließende Funktionen (Werte für geschlossene Namen) und Stubs/Mocks für mehr verschachtelte Funktionen (definiert innerhalb des Testziels).

Weiß jemand von so etwas? Googeln konnte nichts finden.

1

Ich hatte die gleichen Zweifel und fand einen Weg, Tests für innere Funktionen zu bekommen.

Grundsätzlich können Sie die innere Funktion als Parameter nach außen senden und testen wie Sie es wünschen.Wenn Sie outer() aufrufen, wird Ihr Test ausgeführt, und da es eine Schließung ist, behält es jede zusätzliche Eigenschaft von der äußeren Funktion (wie Variablen) bei. Mit einer Liste können Sie beliebig viele Funktionen senden. Um die wenn zu ignorieren, eine Option ist, den Code so laufen:

python -O code.py 
1

ich einen kleinen Helfers-Modul geschrieben habe, die genau dies ermöglicht:

Beispiele für verschachtelte Funktionen:

def f(v1): 
    v2 = 1 
    def g(v3=2): 
    return v1 + v2 + v3 + 4 
    def h(): 
    return 16 
    return g() + h() + 32 

class C(object): 
    def foo(self): 
    def k(x): 
     return [ self, x ] 
    return k(3) 

def m(): 
    vm = 1 
    def n(an=2): 
    vn = 4 
    def o(ao=8): 
     vo = 16 
     return vm + an + vn + ao + vo 
    return o() 
    return n() 

diese können getestet werden Einheit diese Art von Code verwendet:

import unittest 
from nested import nested 

class TestNested(unittest.TestCase): 
    def runTest(self): 
    nestedG = nested(f, 'g', v1=8, v2=1) 
    self.assertEqual(nestedG(2), 15) 
    nestedH = nested(f, 'h') 
    self.assertEqual(nestedH(), 16) 
    nestedK = nested(C.foo, 'k', self='mock') 
    self.assertEqual(nestedK(5), [ 'mock', 5 ]) 
    nestedN = nested(m, 'n', vm=1) 
    nestedO = nested(nestedN, 'o', vm=1, an=2, vn=4) 
    self.assertEqual(nestedO(8), 31) 

def main(argv): 
    unittest.main() 

if __name__ == '__main__': 
    import sys 
    sys.exit(main(sys.argv)) 

Der kleine Helfer-Modul nested sieht wie folgt aus:

import types 

def freeVar(val): 
    def nested(): 
    return val 
    return nested.__closure__[0] 

def nested(outer, innerName, **freeVars): 
    if isinstance(outer, (types.FunctionType, types.MethodType)): 
    outer = outer.func_code 
    for const in outer.co_consts: 
    if isinstance(const, types.CodeType) and const.co_name == innerName: 
     return types.FunctionType(const, globals(), None, None, tuple(
      freeVar(freeVars[name]) for name in const.co_freevars))