web-dev-qa-db-fra.com

Lorsque vous utilisez unittest.mock.patch, pourquoi autospec n'est-il pas vrai par défaut?

Lorsque vous corrigez une fonction à l'aide de mock, vous avez la possibilité de spécifier autospec comme True:

Si vous définissez autospec = True, la maquette avec doit être créée avec une spécification de l'objet à remplacer. Tous les attributs de la maquette auront également la spécification de l'attribut correspondant de l'objet à remplacer. Les méthodes et les fonctions dont on se moque verront leurs arguments vérifiés et déclencheront une TypeError si elles sont appelées avec la mauvaise signature.

( http://www.voidspace.org.uk/python/mock/patch.html )

Je me demande pourquoi ce n'est pas le comportement par défaut? Sûrement, nous voudrions presque toujours attraper le passage de paramètres incorrects à n'importe quelle fonction que nous corrigeons?

29
seddonym

La seule façon claire d'expliquer cela est de citer en fait documentation sur le inconvénient de l'utilisation de la spécification automatique et pourquoi vous devez être prudent lorsque vous l'utilisez:

Ce n'est cependant pas sans mises en garde et limitations, c'est pourquoi ce n'est pas le comportement par défaut. Afin de savoir quels attributs sont disponibles sur l'objet de spécification, autospec doit introspecter (accéder aux attributs) la spécification. Lorsque vous traversez des attributs sur la maquette, une traversée correspondante de l'objet d'origine se produit sous le capot. Si l'un de vos objets spécifiés possède des propriétés ou des descripteurs qui peuvent déclencher l'exécution de code, vous ne pourrez peut-être pas utiliser autospec. En revanche, il est préférable de concevoir vos objets de manière à ce que l'introspection soit sûre [4].

Un problème plus grave est qu'il est courant que des attributs par exemple soient créés dans la méthode init et n'existent pas du tout sur la classe. autospec ne peut pas connaître les attributs créés dynamiquement et restreint l’API aux attributs visibles.

Je pense que le point clé à retenir ici est de noter cette ligne: autospec ne peut pas connaître les attributs créés dynamiquement et limite l'api aux attributs visibles

Ainsi, pour aider à être plus explicite avec un exemple de casse de l'échantillonnage automatique, cet exemple tiré de la documentation montre ceci:

>>> class Something:
...   def __init__(self):
...     self.a = 33
...
>>> with patch('__main__.Something', autospec=True):
...   thing = Something()
...   thing.a
...
Traceback (most recent call last):
  ...
AttributeError: Mock object has no attribute 'a'

Comme vous pouvez le voir, la spécification automatique n'a aucune idée de la création d'un attribut a lors de la création de votre objet Something.

Il n'y a rien de mal à attribuer une valeur à votre attribut d'instance.

Observez l'exemple fonctionnel ci-dessous:

import unittest
from mock import patch

def some_external_thing():
    pass

def something(x):
    return x

class MyRealClass:
    def __init__(self):
        self.a = some_external_thing()

    def test_thing(self):
        return something(self.a)



class MyTest(unittest.TestCase):
    def setUp(self):
        self.my_obj = MyRealClass()

    @patch('__main__.some_external_thing')    
    @patch('__main__.something')
    def test_my_things(self, mock_something, mock_some_external_thing):
        mock_some_external_thing.return_value = "there be dragons"
        self.my_obj.a = mock_some_external_thing.return_value
        self.my_obj.test_thing()

        mock_something.assert_called_once_with("there be dragons")


if __name__ == '__main__':
    unittest.main()

Donc, je dis juste pour mon cas de test que je veux m'assurer que la méthode some_external_thing() n'affecte pas le comportement de mon plus unitaire, donc j'assigne juste mon attribut d'instance la maquette par mock_some_external_thing.return_value = "there be dragons".

23
idjaw

L'action d'autospécification elle-même peut exécuter du code, par exemple via l'invocation de descripteurs.

>>> class A: 
...     @property 
...     def foo(self): 
...         print("rm -rf /") 
... 
>>> a = A() 
>>> with mock.patch("__main__.a", autospec=False) as m: 
...     pass 
... 
>>> with mock.patch("__main__.a", autospec=True) as m: 
...     pass 
... 
rm -rf /

Par conséquent, il s'agit d'une fonctionnalité problématique à activer par défaut et à activer uniquement.

1
wim

Répondre à ma propre question plusieurs années plus tard - une autre raison est la vitesse.

Selon la complexité de votre objet, il se peut que l'utilisation d'Autospec puisse ralentir considérablement votre test. Je l'ai trouvé particulièrement lors du patch Django models.

0
seddonym