J'ai une classe de base qui définit un attribut de classe et certaines classes d'enfants qui en dépendent, par exemple.
class Base(object):
assignment = dict(a=1, b=2, c=3)
Je veux unittest cette classe avec différents devoirs , par ex. dictionnaire vide, élément unique, etc. Ceci est extrêmement simplifié bien sûr, il ne s'agit pas de refactoriser mes cours ou mes tests
Les tests (pytest) que j’ai trouvés, finalement, que le travail sont
from .base import Base
def test_empty(self):
with mock.patch("base.Base.assignment") as a:
a.__get__ = mock.Mock(return_value={})
assert len(Base().assignment.values()) == 0
def test_single(self):
with mock.patch("base.Base.assignment") as a:
a.__get__ = mock.Mock(return_value={'a':1})
assert len(Base().assignment.values()) == 1
Cela semble assez compliqué et hacky - je ne comprends même pas tout à fait pourquoi cela fonctionne (je connais bien les descripteurs). Mock transforme-t-il automatiquement les attributs de classe en descripteurs?
Une solution qui semblerait plus logique ne fonctionne pas:
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = mock.PropertyMock(return_value={'a':1})
assert len(Base().assignment.values()) == 1
ou juste
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = {'a':1}
assert len(Base().assignment.values()) == 1
Les autres variantes que j'ai essayées ne fonctionnent pas non plus (les tâches restent inchangées dans le test).
Quelle est la bonne façon de se moquer d'un attribut de classe? Y a-t-il un moyen meilleur/plus compréhensible que celui ci-dessus?
base.Base.assignment
est simplement remplacé par un objet Mock
. Vous avez fait un descripteur en ajoutant une méthode __get__
.
C'est un peu verbeux et un peu inutile; vous pouvez simplement définir base.Base.assignment
directement:
def test_empty(self):
Base.assignment = {}
assert len(Base().assignment.values()) == 0
Ce n'est pas trop sûr lors de l'utilisation de test d'accès simultané, bien sûr.
Pour utiliser un PropertyMock
, j'utiliserais:
with patch('base.Base.assignment', new_callable=PropertyMock) as a:
a.return_value = {'a': 1}
ou même:
with patch('base.Base.assignment', new_callable=PropertyMock,
return_value={'a': 1}):
Pour améliorer la lisibilité, vous pouvez utiliser le décorateur @patch
:
from mock import patch
from unittest import TestCase
from base import Base
class MyTest(TestCase):
@patch('base.Base.assignment')
def test_empty(self, mock_assignment):
# The `mock_assignment` is a MagicMock instance,
# you can do whatever you want to it.
mock_assignment.__get__.return_value = {}
self.assertEqual(len(Base().assignment.values()), 0)
# ... and so on
Vous pouvez trouver plus de détails sur http://www.voidspace.org.uk/python/mock/patch.html#mock.patch .
Si votre classe (file d'attente par exemple) dans déjà importé à l'intérieur de votre test - et que vous souhaitez corriger MAX_RETRY attr - vous pouvez utiliser @ patch.object ou mieux @ patch.multiple
from mock import patch, PropertyMock, Mock
from somewhere import Queue
@patch.multiple(Queue, MAX_RETRY=1, some_class_method=Mock)
def test_something(self):
do_something()
@patch.object(Queue, 'MAX_RETRY', return_value=1, new_callable=PropertyMock)
def test_something(self, _mocked):
do_something()
Peut-être qu'il me manque quelque chose, mais n'est-ce pas possible sans utiliser PropertyMock
?
with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
# do stuff
Voici un exemple de test unitaire de votre classe Base
:
dict
et int
) @patch
décorateur et pytest
avec avec python 2.7+
ou 3+
.# -*- coding: utf-8 -*-
try: #python 3
from unittest.mock import patch, PropertyMock
except ImportError as e: #python 2
from mock import patch, PropertyMock
from base import Base
@patch('base.Base.assign_dict', new_callable=PropertyMock, return_value=dict(a=1, b=2, c=3))
@patch('base.Base.assign_int', new_callable=PropertyMock, return_value=9765)
def test_type(mock_dict, mock_int):
"""Test if mocked class attributes have correct types"""
assert isinstance(Base().assign_dict, dict)
assert isinstance(Base().assign_int , int)