web-dev-qa-db-fra.com

Meilleure façon de simuler un attribut de classe dans le test unitaire python

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?

18
Ivo van der Wijk

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}):
17
Martijn Pieters

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 .

6
Dan Keder

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()
3
pymen

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
3
igniteflow

Voici un exemple de test unitaire de votre classe Base:

  • moqueur attributs de classe multiples de types différents (ie: dict et int
  • en utilisant les cadres @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)
0
x0s