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?
base.Base.assignment
wird einfach durch ein Mock
Objekt 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 PropertyMock
zu 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}):
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 .
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()
Vielleicht fehlt mir etwas, aber ist dies nicht möglich, ohne PropertyMock
zu verwenden?
with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
# do stuff
Hier ein Beispiel, wie Sie Ihre Base
-Klasse einem Unit-Test unterziehen:
dict
und int
) @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)