ejecutar - pruebas de python
Mejor manera de simular atributos de clase en la prueba unitaria de python (5)
Tengo una clase base que define un atributo de clase y algunas clases secundarias que dependen de él, por ejemplo,
class Base(object):
assignment = dict(a=1, b=2, c=3)
Quiero hacer una prueba de unidad de esta clase con diferentes asignaciones , por ejemplo, diccionario vacío, solo elemento, etc. Esto es extremadamente simplificado, por supuesto, no es una cuestión de refactorizar mis clases o pruebas
Las pruebas (pytest) que he encontrado, con el tiempo, ese trabajo son
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
Esto se siente bastante complicado y intrépido, ni siquiera entiendo completamente por qué funciona (aunque estoy familiarizado con los descriptores). ¿La mofa transforma automágicamente los atributos de clase en descriptores?
Una solución que se sentiría más lógica no funciona:
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
o solo
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = {''a'':1}
assert len(Base().assignment.values()) == 1
Otras variantes que he intentado tampoco funcionan (las asignaciones permanecen sin cambios en la prueba).
¿Cuál es la forma correcta de burlarse de un atributo de clase? ¿Hay una forma mejor / más comprensible que la anterior?
Aquí hay un ejemplo de cómo hacer una prueba unitaria de su clase Base
:
- burlándose de múltiples atributos de clase de diferentes tipos (es decir,
dict
yint
) - usando el decorador
@patch
y el frameworkpytest
conpython 2.7+
o3+
.
# -*- 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)
Para mejorar la legibilidad puede usar el decorador @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
Puede encontrar más detalles en http://www.voidspace.org.uk/python/mock/patch.html#mock.patch .
Si su clase (Cola, por ejemplo) ya se importó dentro de su prueba, y desea parchar MAX_RETRY attr, puede usar @patch.object o simplemente mejor @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()
Tal vez me esté perdiendo algo, pero ¿no es esto posible sin usar PropertyMock
?
with mock.patch.object(Base, ''assignment'', {''bucket'': ''head''}):
# do stuff
base.Base.assignment
simplemente se reemplaza con un objeto Mock
. Lo hiciste un descriptor agregando un método __get__
.
Es un poco verboso y un poco innecesario; simplemente puede establecer base.Base.assignment
directamente:
def test_empty(self):
Base.assignment = {}
assert len(Base().assignment.values()) == 0
Esto no es demasiado seguro cuando se usa la concurrencia de prueba, por supuesto.
Para usar un PropertyMock
, yo usaría:
with patch(''base.Base.assignment'', new_callable=PropertyMock) as a:
a.return_value = {''a'': 1}
o incluso:
with patch(''base.Base.assignment'', new_callable=PropertyMock,
return_value={''a'': 1}):