Ejecución de pruebas unitarias en funciones anidadas

Vengo del mundo de Java, donde puedes ocultar variables y funciones y luego ejecutar pruebas de unidad contra ellas usando la reflexión. He usado funciones anidadas para ocultar los detalles de implementación de mis clases para que solo se vea la API pública. Estoy tratando de escribir pruebas unitarias contra estas funciones anidadas para asegurarme de no romperlas a medida que me desarrollo. He intentado llamar a una de las funciones anidadas como:

def outer(): def inner(): pass outer.inner() 

lo que resulta en el mensaje de error:

AttributeError: el objeto ‘function’ no tiene atributo ‘inner’

¿Hay alguna manera de que escriba pruebas unitarias contra estas funciones anidadas? Si no es así, ¿hay alguna manera de activar el nombre de búsqueda de nombres de funciones como usted puede para las variables de clase prefijándolas con __?

La convención de Python es nombrar funciones y métodos “privados” con un guión bajo. Cuando ve un guión bajo, sabe que no debe intentar usarlo.

Recuerda, Python no es Java .

Lo interno no existe hasta que lo exterior lo hace. Debe mover desde el interior hasta una función de nivel superior para la capacidad de prueba, o hacer que la prueba externa pruebe todas las rutas de ejecución posibles de sí misma e interna.

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 estándar de testabilidad. Si su complejidad ciclomática es demasiado alta, sus pruebas serán demasiado numerosas.

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 mantengas el interior () nested implica que el único “contrato” que realmente importa es el externo (). inner () es parte de la implementación, y no debería querer probar la implementación. Si realmente desea probar inner (), realice pruebas exhaustivas en external () con datos que involucren todas las funcionalidades de inner ().

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

No es fácil.

Sin embargo, al parecer, esto podría lograrse con el uso de los módulos de Python parser, ast, o tokenizer para cortar el propio código, extrayendo las funciones internas (por algún camino a través del anidamiento), y permitiendo que las pruebas las ejecuten con el estado desde las funciones de cierre (valores). para nombres cerrados) y apéndices / simulacros para funciones más anidadas (definidas dentro del objective de prueba).

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

Tenía la misma duda y encontré una forma de realizar pruebas para 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, puede enviar la función interna como un parámetro al exterior y probarla como desee. Al llamar externo () , su prueba se ejecutará, y como 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 el if , una opción es ejecutar el código así:

 python -O code.py 

He escrito un pequeño módulo de ayuda 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 probados por unidad 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 pequeño módulo de ayuda nested 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))