Il convient de noter que plusieurs des réponses données ici corrigeront le décorateur pour l'ensemble de la session de test plutôt que pour une seule instance de test, ce qui peut s'avérer indésirable. Voici comment patcher un décorateur qui ne persiste que pour un seul test.
Notre unité à tester avec le décorateur indésirable :
# app/uut.py
from app.decorators import func_decor
@func_decor
def unit_to_be_tested():
# Do stuff
pass
Du module des décorateurs :
# 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
Lorsque notre test est collecté au cours d'une exécution de test, le décorateur indésirable a déjà été appliqué à notre unité testée (parce que cela se produit au moment de l'importation). Afin de nous en débarrasser, nous devrons remplacer manuellement le décorateur dans le module du décorateur, puis réimporter le module contenant notre UUT.
Notre module de test :
# 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
Le callback de nettoyage, kill_patches, restaure le décorateur d'origine et le réapplique à l'unité que nous étions en train de tester. De cette façon, notre patch ne persiste que pour un seul test et non pour toute la session, ce qui est exactement la façon dont tout autre patch devrait se comporter. De plus, puisque le nettoyage appelle patch.stopall(), nous pouvons lancer tous les autres patchs dans le setUp() dont nous avons besoin et ils seront nettoyés en un seul endroit.
La chose importante à comprendre à propos de cette méthode est la façon dont le rechargement affectera les choses. Si un module prend trop de temps ou a une logique qui s'exécute à l'importation, il se peut que vous deviez simplement hausser les épaules et tester le décorateur dans le cadre de l'unité :( Espérons que votre code est mieux écrit que cela. Pas vrai ?
Si l'on ne se soucie pas de savoir si le correctif est appliqué à l'ensemble de la session de test La façon la plus simple de le faire est de le faire au début du fichier de test :
# 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
Veillez à patcher le fichier avec le décorateur plutôt qu'avec la portée locale de l'UUT et à lancer le patch avant d'importer l'unité avec le décorateur.
Il est intéressant de noter que même si le correctif est arrêté, tous les fichiers déjà importés auront toujours le correctif appliqué au décorateur, ce qui est l'inverse de la situation avec laquelle nous avons commencé. Il faut savoir que cette méthode appliquera un correctif à tous les autres fichiers du test qui seront importés par la suite, même s'ils ne déclarent pas de correctif eux-mêmes.