2008-11-28 10 views
8

Vengo del mundo de Java, donde puede ocultar variables y funciones y luego ejecutar pruebas unitarias contra ellas mediante la reflexión. He utilizado funciones anidadas para ocultar los detalles de implementación de mis clases para que solo la API pública esté visible. Estoy intentando escribir pruebas unitarias contra estas funciones anidadas para asegurarme de que no las rompo a medida que desarrollo. He intentado llamar a una de las funciones anidadas como:Pruebas de unidad de ejecución en funciones anidadas

def outer(): 
    def inner(): 
     pass 

outer.inner() 

que se traduce en el mensaje de error:

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

¿Hay alguna manera de escribir pruebas unitarias contra estas funciones anidados? Si no, ¿hay alguna manera de activar el nombre de munging para los nombres de las funciones como se puede para las variables de la clase prefijando con __?

Respuesta

5

La convención de Python es nombrar funciones y métodos "privados" con un guion bajo. Cuando ve un subrayado inicial, sabe que no debe intentarlo.

Recuerde, Python is not Java.

+11

¿Cómo afecta esto a responder la pregunta? ¿Existen buenas soluciones para aislar tales funciones internas para pruebas unitarias o no? Incluso sin acceso al objeto de función interno desde el exterior, tal podría lograrse aparentemente con el uso de los módulos de python analizador, ast o tokenizador para cortar el código en sí mismo. Obviamente, es más fácil de decir que codificar. :-) –

+1

Responde la pregunta porque uno * no * prueba * privado, detalles de implementación. Puede confiar en las herramientas de cobertura de prueba para informarle si las pruebas de la interfaz pública ejercen adecuadamente la implementación privada. – SingleNegationElimination

+0

@Tim: Según la última oración de la pregunta, el autor está bien con una función no interna si se puede marcar como privada. Por lo tanto, conocer esta convención es útil. – orip

11

interno no existe hasta que el exterior lo hace. Debería moverse hacia arriba a una función de nivel superior para probarla, o hacer que la prueba externa pruebe todas las rutas de ejecución posibles de sí mismo e internas.

Tenga en cuenta que la función interna no es una función simple, es un cierre. Considere este caso:

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

Esa es la compensación de la capacidad de prueba estándar. Si su cyclomatic complexity es demasiado alto, sus pruebas serán demasiado numerosas.

1

No creo que haya ninguna posibilidad de acceder a inner() desde el espacio de nombres externo.

Sin embargo, en mi opinión, el hecho de que usted mantenga inner() anidado implica que el único "contrato" que realmente importa es el externo(). inner() es parte de la implementación y no debería probar la implementación. Si realmente quieres probar inner(), realiza pruebas exhaustivas en outer() con datos que involucrarán todas las funcionalidades de inner().

+0

Es posible acceder a la función interna en cpython, creo. Hay una respuesta a continuación que muestra exactamente cómo hacerlo. – Aditya369

2

No hay manera de obtener la función interna desde el objeto de función externo (ver las otras respuestas!). Sin embargo, tanto las pruebas unitarias como los cierres han hecho (al menos para mí) increíbles mejoras en el rendimiento del desarrollador. Podemos tener ambos? ¿Podemos probar funciones anidadas de forma aislada?

No es fácil.

Sin embargo, aparentemente podría lograrse con el uso de módulos de python analizador, ast o tokenizador para dividir el código en sí mismo, extraer funciones internas (por algún camino a través del anidamiento) y permitir que las pruebas se ejecuten con estado desde funciones adjuntas (valores para nombres cerrados) y stubs/mocks para funciones más anidadas (definidas dentro del objetivo de prueba).

¿Alguien sabe algo como esto? Google no pudo encontrar nada.

1

Tuve la misma duda y encontré una forma de hacer pruebas para las funciones internas.

def outer(): 
    def inner(): 
     pass 

    if __debug__: 
     test_inner(inner) 
     # return 

def test_inner(f): 
    f() # this calls the inner function 

outer() 

Básicamente se puede enviar a la función interna como un parámetro para el exterior y probarlo como desee.Cuando llame al outer(), su prueba se ejecutará, y dado que es un cierre, conservará cualquier propiedad adicional de la función externa (como variables). Usando una lista, puede enviar tantas funciones como desee. Para ignorar la si, una opción es ejecutar el código de esa manera:

python -O code.py 
1

He escrito un módulo de ayuda pequeña que permite exactamente esto:

Ejemplos de funciones anidadas:

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

Estos pueden ser unidad probada usando este tipo de código:

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

El módulo de ayuda pequeña nested se ve así:

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)) 
Cuestiones relacionadas