8 réponses

Pour @lru_cache (max_size = 1000)


class MockedLruCache(object):
def __init__(self, maxsize=0, timeout=0):
    pass

def __call__(self, func):
    return func

cache.LruCache = MockedLruCache

Si vous utilisez un décorateur qui n'a pas de paramètres, vous devez:

def MockAuthenticated(func):
    return func

from tornado import web web.authenticated = MockAuthenticated

-2
Yash Mehrotra 20 nov. 2015 à 08:05

Nous avons essayé de nous moquer d'un décorateur qui obtient parfois un autre paramètre comme une chaîne, et parfois non, par exemple:

@myDecorator('my-str')
def function()

OR

@myDecorator
def function()

Grâce à l'une des réponses ci-dessus, nous avons écrit une fonction de simulation et patché le décorateur avec cette fonction de simulation:

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

Notez que cet exemple est bon pour un décorateur qui n'exécute pas la fonction décorée, ne faites que des choses avant l'exécution réelle. Dans le cas où le décorateur exécute également la fonction décorée et qu'il doit donc transférer les paramètres de la fonction, la fonction mock_decorator doit être un peu différente.

J'espère que cela aidera les autres ...

1
InbalZelig 6 janv. 2020 à 10:55

Concept

Cela peut sembler un peu étrange mais on peut patcher sys.path, avec une copie de lui-même, et effectuer une importation dans le cadre de la fonction de test. Le code suivant montre le concept.

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 peut alors être remplacé par le module que vous testez. (Cela fonctionne en Python 3.6 avec MODULE substitué par xml par exemple)

OP

Pour votre cas, disons que la fonction décoratrice réside dans le module pretty et la fonction décorée réside dans present, alors vous patcheriez pretty.decorator en utilisant la machinerie fictive et substitueriez MODULE avec present. Quelque chose comme ce qui suit devrait fonctionner (non testé).

Classe TestDecorator (unittest.TestCase): ...

  @patch(`pretty.decorator`, decorator)
  @patch(`sys.path`, sys.path.copy())
  def testFunction(self, decorator) :
   import present
   ...

Explication

Cela fonctionne en fournissant un sys.path "propre" pour chaque fonction de test, en utilisant une copie du sys.path actuel du module de test. Cette copie est effectuée lors de la première analyse du module, garantissant un sys.path cohérent pour tous les tests.

Nuances

Il y a cependant quelques implications. Si le framework de test exécute plusieurs modules de test sous la même session python, tout module de test qui importe MODULE interrompt globalement tout module de test qui l'importe localement. Cela oblige à effectuer l'importation localement partout. Si le framework exécute chaque module de test sous une session python distincte, cela devrait fonctionner. De même, vous ne pouvez pas importer MODULE globalement dans un module de test où vous importez MODULE localement.

Les importations locales doivent être effectuées pour chaque fonction de test dans une sous-classe de unittest.TestCase. Il est peut-être possible de l'appliquer à la sous-classe unittest.TestCase en rendant directement une importation particulière du module disponible pour toutes les fonctions de test de la classe.

Ins intégré

Ceux qui jouent avec builtin les importations trouveront le remplacement de MODULE par sys, os, etc. échouera, car ceux-ci sont déjà lus sur sys.path lorsque vous essayez de le copier . L'astuce ici est d'invoquer Python avec les importations intégrées désactivées, je pense que python -X test.py le fera mais j'oublie l'indicateur approprié (Voir python --help). Ceux-ci peuvent ensuite être importés localement à l'aide de import builtins, IIRC.

0
Carel 12 mai 2018 à 23:10

Ce qui suit a fonctionné pour moi:

  1. Éliminez l'instruction d'importation qui charge la cible de test.
  2. Patcher le décorateur au démarrage du test comme appliqué ci-dessus.
  3. Appelez importlib.import_module () immédiatement après l'application de correctifs pour charger la cible de test.
  4. Exécutez les tests normalement.

Ça a marché comme sur des roulettes.

2
Eric Mintz 17 janv. 2014 à 15:49

Vous pouvez peut-être appliquer un autre décorateur sur les définitions de tous vos décorateurs qui vérifie essentiellement une variable de configuration pour voir si le mode de test est destiné à être utilisé.
Si oui, il remplace le décorateur qu'il décore par un décorateur factice qui ne fait rien.
Sinon, il laisse passer ce décorateur.

0
Aditya Mukherji 5 oct. 2011 à 21:04

Lorsque j'ai rencontré ce problème pour la première fois, j'utilisais mon cerveau pendant des heures. J'ai trouvé un moyen beaucoup plus simple de gérer cela.

Cela contournera complètement le décorateur, comme si la cible n'était même pas décorée en premier lieu.

Cela se décompose en deux parties. Je suggère de lire l'article suivant.

http://alexmarandon.com/articles/python_mock_gotchas/

Deux Gotchas que je continuais à rencontrer:

1.) Se moquer du décorateur avant l'importation de votre fonction / module.

Les décorateurs et les fonctions sont définis au moment du chargement du module. Si vous ne vous moquez pas avant l'importation, cela ne tiendra pas compte de la maquette. Après le chargement, vous devez faire un mock.patch.object bizarre, ce qui devient encore plus frustrant.

2.) Assurez-vous de vous moquer du bon chemin vers le décorateur.

N'oubliez pas que le patch du décorateur dont vous vous moquez est basé sur la façon dont votre module charge le décorateur, et non sur la façon dont votre test charge le décorateur. C'est pourquoi je suggère de toujours utiliser des chemins d'accès complets pour les importations. Cela rend les choses beaucoup plus faciles à tester.

Pas:

1.) La fonction 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.) Se moquer du décorateur:

2a.) Chemin à l'intérieur avec.

with mock.patch('path.to.my.decorator', mock_decorator):
     from mymodule import myfunction

2b.) Patch en haut du fichier ou dans TestCase.setUp

mock.patch('path.to.my.decorator', mock_decorator).start()

L'une ou l'autre de ces façons vous permettra d'importer votre fonction à tout moment dans TestCase ou ses méthodes / cas de test.

from mymodule import myfunction

2.) Utilisez une fonction distincte comme effet secondaire du mock.patch.

Vous pouvez maintenant utiliser mock_decorator pour chaque décorateur que vous souhaitez simuler. Vous devrez vous moquer de chaque décorateur séparément, alors faites attention à ceux que vous manquez.

7
TheDude 22 mai 2018 à 15:15

Pour patcher un décorateur, vous devez importer ou recharger le module qui utilise ce décorateur après le patcher OU redéfinir la référence du module à ce décorateur.

Les décorateurs sont appliqués au moment de l'importation d'un module. C'est pourquoi si vous avez importé un module qui utilise un décorateur que vous souhaitez patcher en haut de votre fichier et essayer de le patcher plus tard sans le recharger, le patch n'aurait aucun effet.

Voici un exemple de la première façon mentionnée de le faire - recharger un module après avoir patché un décorateur qu'il utilise:

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

Références utiles:

0
Arthur S 25 sept. 2019 à 17:03

Il convient de noter que plusieurs des réponses ici patcheront le décorateur pour toute la session de test plutôt qu'une seule instance de test; ce qui peut être indésirable. Voici comment corriger un décorateur qui ne persiste qu'à travers 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 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

Au moment où notre test est collecté lors d'un test, le décorateur indésirable a déjà été appliqué à notre unité sous test (car cela se produit au moment de l'importation). Afin de se débarrasser de cela, 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 rappel de nettoyage, kill_patches, restaure le décorateur d'origine et le réapplique à l'unité que nous testions. De cette façon, notre correctif ne persiste que pendant un seul test plutôt que pendant toute la session - c'est exactement la façon dont tout autre correctif devrait se comporter. De plus, puisque le nettoyage appelle patch.stopall (), nous pouvons démarrer 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 de savoir comment le rechargement affectera les choses. Si un module prend trop de temps ou a une logique qui s'exécute lors de l'importation, vous devrez peut-être simplement hausser les épaules et tester le décorateur dans le cadre de l'unité. :( J'espère que votre code est mieux écrit que ça.

Si l'on ne se soucie pas si le patch est appliqué à toute la session de test , la façon la plus simple de le faire est juste en haut 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

Assurez-vous de patcher le fichier avec le décorateur plutôt que la portée locale de l'UUT et de démarrer le patch avant d'importer l'unité avec le décorateur.

Fait intéressant, 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é. N'oubliez pas que cette méthode corrigera tous les autres fichiers du test qui seront importés par la suite - même s'ils ne déclarent pas eux-mêmes un correctif.

45
user2859458 2 sept. 2016 à 15:21
7667567