Mejor manera de simular atributos de clase en la prueba unitaria de python

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). ¿Se burlan 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?

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 prueba de concurrencia, 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}): 

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 

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 e int )
  • usando el decorador @patch y el framework pytest con python 2.7+ o 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)