Los decoradores se aplican en el momento de la definición de la función. Para la mayoría de las funciones, esto es cuando se carga el módulo. (Las funciones que están definidas en otras funciones tienen el decorador aplicado cada vez que se llama a la función adjunta).
Entonces, si desea parchear a un decorador, lo que debe hacer es:
- Importar el módulo que lo contiene
- Definir la función de decorador simulado
- Establecer por ejemplo
module.decorator = mymockdecorator
- Importe los módulos que utilizan el decorador o utilícelo en su propio módulo
Si el módulo que contiene el decorador también contiene funciones que lo usan, esas ya están decoradas para cuando pueda verlas, y probablemente sea SOL
Edite para reflejar los cambios en Python desde que escribí esto originalmente: si el decorador usa functools.wraps()
y la versión de Python es lo suficientemente nueva, es posible que pueda desenterrar la función original usando el __wrapped__
atributo y volver a decorarla, pero esto de ninguna manera garantizado, y el decorador al que desea reemplazar tampoco puede ser el único decorador aplicado.
reload
función incorporada para regenerar el código binario de Python docs.python.org/2/library/functions.html#reload y monkeypatch su decorador__init__
. Eso aseguró que el parche se cargara antes que cualquier archivo de prueba. Tenemos una carpeta de pruebas aislada, por lo que la estrategia funciona para nosotros, pero es posible que esto no funcione para todos los diseños de carpeta.Cabe señalar que varias de las respuestas aquí parchearán el decorador para toda la sesión de prueba en lugar de una sola instancia de prueba; que puede ser indeseable. A continuación, se explica cómo parchear un decorador que solo persiste a través de una única prueba.
Nuestra unidad para ser probada con el decorador no deseado:
# app/uut.py from app.decorators import func_decor @func_decor def unit_to_be_tested(): # Do stuff pass
Desde el módulo de decoradores:
# app/decorators.py def func_decor(func): def inner(*args, **kwargs): print "Do stuff we don't want in our test" return func(*args, **kwargs) return inner
Para cuando nuestra prueba se recopila durante una ejecución de prueba, el decorador no deseado ya se ha aplicado a nuestra unidad bajo prueba (porque eso ocurre en el momento de la importación). Para deshacernos de eso, necesitaremos reemplazar manualmente el decorador en el módulo del decorador y luego volver a importar el módulo que contiene nuestra UUT.
Nuestro módulo de prueba:
# test_uut.py from unittest import TestCase from app import uut # Module with our thing to test from app import decorators # Module with the decorator we need to replace import imp # Library to help us reload our UUT module from mock import patch class TestUUT(TestCase): def setUp(self): # Do cleanup first so it is ready if an exception is raised def kill_patches(): # Create a cleanup callback that undoes our patches patch.stopall() # Stops all patches started with start() imp.reload(uut) # Reload our UUT module which restores the original decorator self.addCleanup(kill_patches) # We want to make sure this is run so we do this in addCleanup instead of tearDown # Now patch the decorator where the decorator is being imported from patch('app.decorators.func_decor', lambda x: x).start() # The lambda makes our decorator into a pass-thru. Also, don't forget to call start() # HINT: if you're patching a decor with params use something like: # lambda *x, **y: lambda f: f imp.reload(uut) # Reloads the uut.py module which applies our patched decorator
La devolución de llamada de limpieza, kill_patches, restaura el decorador original y lo vuelve a aplicar a la unidad que estábamos probando. De esta manera, nuestro parche solo persiste a través de una única prueba en lugar de toda la sesión, que es exactamente como debería comportarse cualquier otro parche. Además, dado que la limpieza llama a patch.stopall (), podemos iniciar cualquier otro parche en el setUp () que necesitemos y se limpiarán todo en un solo lugar.
Lo importante que hay que entender sobre este método es cómo afectará la recarga a las cosas. Si un módulo tarda demasiado o tiene una lógica que se ejecuta en la importación, es posible que solo deba encogerse de hombros y probar el decorador como parte de la unidad. :( Esperemos que tu código esté mejor escrito que eso. ¿Verdad?
Si a uno no le importa si el parche se aplica a toda la sesión de prueba , la forma más sencilla de hacerlo es en la parte superior del archivo de prueba:
# test_uut.py from mock import patch patch('app.decorators.func_decor', lambda x: x).start() # MUST BE BEFORE THE UUT GETS IMPORTED ANYWHERE! from app import uut
Asegúrese de parchear el archivo con el decorador en lugar del alcance local de la UUT y de iniciar el parche antes de importar la unidad con el decorador.
Curiosamente, incluso si se detiene el parche, todos los archivos que ya se importaron tendrán el parche aplicado al decorador, que es lo contrario de la situación con la que comenzamos. Tenga en cuenta que este método parcheará cualquier otro archivo en la ejecución de prueba que se importe posteriormente, incluso si ellos mismos no declaran un parche.
fuente
Cuando me encontré con este problema por primera vez, solía devanar mi cerebro durante horas. Encontré una forma mucho más fácil de manejar esto.
Esto evitará por completo al decorador, como si el objetivo ni siquiera estuviera decorado en primer lugar.
Esto se divide en dos partes. Sugiero leer el siguiente artículo.
http://alexmarandon.com/articles/python_mock_gotchas/
Dos problemas con los que seguí encontrándome:
1.) Burlarse del Decorador antes de importar su función / módulo.
Los decoradores y funciones se definen en el momento en que se carga el módulo. Si no realiza la simulación antes de la importación, se ignorará la simulación. Después de la carga, tienes que hacer un mock.patch.object extraño, que se vuelve aún más frustrante.
2.) Asegúrate de burlarte del camino correcto hacia el decorador.
Recuerde que el parche del decorador del que se está burlando se basa en cómo su módulo carga al decorador, no en cómo su prueba carga al decorador. Es por eso que sugiero usar siempre rutas completas para las importaciones. Esto facilita mucho las pruebas.
Pasos:
1.) La función Mock:
from functools import wraps def mock_decorator(*args, **kwargs): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): return f(*args, **kwargs) return decorated_function return decorator
2.) Burlarse del decorador:
2a.) Camino interior con.
with mock.patch('path.to.my.decorator', mock_decorator): from mymodule import myfunction
2b.) Parche en la parte superior del archivo o en TestCase.setUp
mock.patch('path.to.my.decorator', mock_decorator).start()
Cualquiera de estas formas le permitirá importar su función en cualquier momento dentro de TestCase o su método / casos de prueba.
from mymodule import myfunction
2.) Utilice una función separada como efecto secundario del mock.patch.
Ahora puedes usar mock_decorator para cada decorador del que quieras burlarte. Tendrás que burlarte de cada decorador por separado, así que ten cuidado con los que echas de menos.
fuente
Lo siguiente funcionó para mí:
Funcionó a las mil maravillas.
fuente
Intentamos burlarnos de un decorador que a veces obtiene otro parámetro como una cadena, y otras veces no, por ejemplo:
@myDecorator('my-str') def function() OR @myDecorator def function()
Gracias a una de las respuestas anteriores, escribimos una función simulada y parcheamos el decorador con esta función simulada:
from mock import patch def mock_decorator(f): def decorated_function(g): return g if callable(f): # if no other parameter, just return the decorated function return decorated_function(f) return decorated_function # if there is a parametr (eg. string), ignore it and return the decorated function patch('path.to.myDecorator', mock_decorator).start() from mymodule import myfunction
Tenga en cuenta que este ejemplo es bueno para un decorador que no ejecuta la función decorada, solo hace algunas cosas antes de la ejecución real. En caso de que el decorador también ejecute la función decorada y, por tanto, necesite transferir los parámetros de la función, la función mock_decorator tiene que ser un poco diferente.
Espero que esto ayude a otros ...
fuente
Tal vez pueda aplicar otro decorador a las definiciones de todos sus decoradores que básicamente verifique alguna variable de configuración para ver si el modo de prueba está destinado a ser utilizado.
Si es así, reemplaza al decorador que está decorando con un decorador ficticio que no hace nada.
De lo contrario, deja pasar a este decorador.
fuente
Concepto
Esto puede sonar un poco extraño, pero se puede parchear
sys.path
, con una copia de sí mismo, y realizar una importación dentro del alcance de la función de prueba. El siguiente código muestra el concepto.from unittest.mock import patch import sys @patch('sys.modules', sys.modules.copy()) def testImport(): oldkeys = set(sys.modules.keys()) import MODULE newkeys = set(sys.modules.keys()) print((newkeys)-(oldkeys)) oldkeys = set(sys.modules.keys()) testImport() -> ("MODULE") # Set contains MODULE newkeys = set(sys.modules.keys()) print((newkeys)-(oldkeys)) -> set() # An empty set
MODULE
luego se puede sustituir por el módulo que está probando. (Esto funciona en Python 3.6MODULE
sustituido por,xml
por ejemplo)OP
Para su caso, digamos que la función decoradora reside en el módulo
pretty
y la función decorada reside enpresent
, luego parchearíapretty.decorator
usando la maquinaria simulada y sustituiríaMODULE
porpresent
. Algo como lo siguiente debería funcionar (no probado).clase TestDecorator (unittest.TestCase): ...
@patch(`pretty.decorator`, decorator) @patch(`sys.path`, sys.path.copy()) def testFunction(self, decorator) : import present ...
Explicación
Esto funciona proporcionando una "limpieza"
sys.path
para cada función de prueba, utilizando una copia de la corrientesys.path
del módulo de prueba. Esta copia se realiza cuando el módulo se analiza por primera vez, lo que garantiza una coherenciasys.path
para todas las pruebas.Matices
Sin embargo, hay algunas implicaciones. Si el marco de prueba ejecuta varios módulos de prueba en la misma sesión de Python, cualquier módulo de prueba que importe
MODULE
globalmente rompe cualquier módulo de prueba que lo importe localmente. Esto obliga a realizar la importación localmente en todas partes. Si el marco ejecuta cada módulo de prueba en una sesión de Python separada, esto debería funcionar. De manera similar, no puede importarMODULE
globalmente dentro de un módulo de prueba donde está importandoMODULE
localmente.Las importaciones locales deben realizarse para cada función de prueba dentro de una subclase de
unittest.TestCase
. Quizás sea posible aplicar esto a launittest.TestCase
subclase directamente haciendo que una importación particular del módulo esté disponible para todas las funciones de prueba dentro de la clase.Incorporado Ins
Aquellos que estén jugando con las
builtin
importaciones encontrarán que el reemplazoMODULE
consys
,os
etc. fallará, ya que estos ya están activadossys.path
cuando intente copiarlos. El truco aquí es invocar Python con las importaciones integradas deshabilitadas, creo quepython -X test.py
lo haré, pero olvido la bandera apropiada (Verpython --help
). Posteriormente, estos pueden importarse localmente utilizandoimport builtins
, IIRC.fuente
Para parchear un decorador, necesitas importar o recargar el módulo que usa ese decorador después de parchearlo O redefinir la referencia del módulo a ese decorador por completo.
Los decoradores se aplican en el momento en que se importa un módulo. Esta es la razón por la que si importó un módulo que usa un decorador que desea parchear en la parte superior de su archivo e intenta parchearlo más tarde sin volver a cargarlo, el parche no tendrá ningún efecto.
Aquí hay un ejemplo de la primera forma mencionada de hacer esto: recargar un módulo después de parchear un decorador que usa:
import moduleA ... # 1. patch the decorator @patch('decoratorWhichIsUsedInModuleA', examplePatchValue) def setUp(self) # 2. reload the module which uses the decorator reload(moduleA) def testFunctionA(self): # 3. tests... assert(moduleA.functionA()...
Referencias útiles:
imp.reload
reload
fuente
para @lru_cache (max_size = 1000)
class MockedLruCache(object):
cache.LruCache = MockedLruCache
si usa un decorador que no tiene parámetros, debe:
def MockAuthenticated(func): return func
from tornado import web web.authenticated = MockAuthenticated
fuente