Meilleure façon de simuler un attribut de classe dans le test de l'unité python

j'ai une classe de base qui définit un attribut de classe et certaines classes enfant 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 affectations, p.ex. dictionnaire vide, article unique, etc. Ceci est extrêmement simplifié bien sûr, il ne s'agit pas de réaménager mes cours ou mes tests

L' (pytest) tests je suis venu avec, éventuellement, que les travaux 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-I ne comprenez même pas tout à fait pourquoi cela fonctionne (je suis familier avec les descripteurs cependant). N'maquette magiquement transformer les attributs de classe dans les descripteurs?

Une solution qui permettrait de se sentir 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

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 assignations restent inchangées dans le test).

Quelle est la bonne façon de se moquer d'un attribut de classe? Est-il mieux / plus compréhensible que l' l'un au-dessus?

18
demandé sur Martijn Pieters 2014-03-11 15:39:11

5 réponses

base.Base.assignment est simplement remplacé par un Mock objet. Vous un descripteur par l'ajout d'un __get__ méthode.

c'est un peu verbeux et un peu inutile; vous pouvez simplement mettre base.Base.assignment directement:

def test_empty(self):
    Base.assignment = {}
    assert len(Base().assignment.values()) == 0

ce n'est pas très sûr quand on utilise la simultanéité des tests, bien sûr.

Pour un PropertyMock, j'utilise:

with patch('base.Base.assignment', new_callable=PropertyMock) as a:
    a.return_value = {'a': 1}

ou encore:

with patch('base.Base.assignment', new_callable=PropertyMock, 
           return_value={'a': 1}):
17
répondu Martijn Pieters 2014-03-11 12:17:33

pour améliorer la lisibilité, vous pouvez utiliser le @patch décorateur:

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 à http://www.voidspace.org.uk/python/mock/patch.html#mock.patch.

5
répondu Dan Keder 2014-03-12 11:41:22

si votre classe (File par exemple) dans déjà importé à l'intérieur de votre test et vous souhaitez patch MAX_RESTRY attr - vous pouvez utiliser @patch.l'objet ou tout simplement mieux @patch.plusieurs

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()
2
répondu pymen 2017-02-11 19:56:55

peut-être que je manque quelque chose, mais n'est-ce pas possible sans utiliser PropertyMock?

with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
   # do stuff
2
répondu igniteflow 2017-06-13 17:05:13

voici un exemple pour tester votre Base catégorie:

  • moqueur attributs de classes multiples de différents types (c'est-à-dire: dict et int)
  • @patch décorateur et pytest cadre 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
répondu x0s 2017-05-04 08:51:02