wake-up-neo.com

Klassenmethode im Python-Unit-Test nachahmen

Ich habe eine Basisklasse, die ein Klassenattribut und einige davon abhängige untergeordnete Klassen definiert, z.

class Base(object):
    assignment = dict(a=1, b=2, c=3)

Ich möchte diese Klasse mit verschiedenen Zuweisungen , z. leeres Wörterbuch, einzelnes Element usw. Dies ist natürlich extrem vereinfacht, es geht nicht darum, meine Klassen oder Tests zu überarbeiten

Die (Pytest-) Tests, mit denen ich mich beschäftigt habe, sind letztendlich die Arbeit

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

Das fühlt sich ziemlich kompliziert und hackig an - ich verstehe nicht einmal, warum es funktioniert (ich bin jedoch mit Deskriptoren vertraut). Wandelt Klassen automatisch Attribute in Deskriptoren um?

Eine Lösung, die sich logischer anfühlt, funktioniert nicht:

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

oder nur

def test_single(self):
    with mock.patch("base.Base") as a:
        a.assignment = {'a':1}
        assert len(Base().assignment.values()) == 1

Andere Varianten, die ich ausprobiert habe, funktionieren ebenfalls nicht (Zuweisungen bleiben im Test unverändert). 

Wie kann ein Klassenattribut nachgeahmt werden? Gibt es einen besseren/verständlicheren Weg als den obigen?

18

base.Base.assignment wird einfach durch ein MockObjekt ersetzt. Sie gemacht es eine Beschreibung, indem Sie eine __get__-Methode hinzufügen.

Es ist ein wenig wortreich und etwas unnötig; Sie könnten einfach base.Base.assignment direkt einstellen:

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

Dies ist natürlich bei Verwendung der Test-Parallelität nicht zu sicher.

Um einen PropertyMockzu verwenden, würde ich Folgendes verwenden:

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

oder auch:

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

Zur besseren Lesbarkeit können Sie den @patch-Dekorator verwenden:

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

Weitere Informationen finden Sie unter http://www.voidspace.org.uk/python/mock/patch.html#mock.patch .

6
Dan Keder

Wenn sich Ihre Klasse (beispielsweise in der Warteschlange) in bereits importiert in Ihrem Test befindet - und Sie MAX_RETRY attr patchen möchten, können Sie @ patch.object oder einfach verwenden besser @ 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

Vielleicht fehlt mir etwas, aber ist dies nicht möglich, ohne PropertyMock zu verwenden?

with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
   # do stuff
3
igniteflow

Hier ein Beispiel, wie Sie Ihre Base-Klasse einem Unit-Test unterziehen:

  • verspottet mehrere Klassenattribute verschiedener Typen (zB: dict und int
  • verwenden des @patch-Dekorators und des pytest-Frameworks mit python 2.7+ oder 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