Atributo de clase simulada de Python
- Motivo para simular un atributo de clase
- Posibles soluciones para simular un atributo de clase
- Constructor de clase simulada de Python
El objetivo principal de este artículo es demostrar cómo manipular un atributo de clase utilizando el módulo de pruebas unitarias de python unittest
con fines de prueba y depuración.
Motivo para simular un atributo de clase
Probar el código desarrollado en busca de fallas, errores y casos extremos es uno de los aspectos más importantes al desarrollar una aplicación, principalmente cuando la aplicación está destinada a múltiples usuarios.
Usando el módulo integrado de Python unittest
, podemos llevar a cabo casos de prueba para probar la integridad de nuestro código. Uno de los elementos más comunes que requieren pruebas rigurosas son los atributos de clase.
El atributo de clase puede manejar entradas aleatorias para evitar un comportamiento inesperado.
Considere el siguiente código:
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
Considere una clase llamada Calcular
, que contiene un atributo llamado valor
y un método llamado Proceso
. Un diccionario se almacena dentro del valor
, que luego se procesa según el requisito y el tipo de datos.
Para garantizar que el atributo pueda almacenar casi cualquier tipo de diccionario y se procese sin errores, se debe probar el atributo para asegurarse de que la implementación esté libre de errores y no necesite revisiones.
Posibles soluciones para simular un atributo de clase
Podemos burlarnos de un atributo de clase de dos maneras; usando PropertyMock
y sin usar PropertyMock
. Aprendamos cada uno de ellos a continuación usando un código de ejemplo.
Use PropertyMock
para simular un atributo de clase
Para simular un atributo, podemos usar PropertyMock
, principalmente destinado a ser utilizado como simulacro de una propiedad o descriptor de una clase.
Vale la pena señalar que PropertyMock
proporciona métodos __get__
y __set__
para alterar el valor de retorno de la propiedad una vez que se obtiene.
Considere el siguiente código:
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
class Test(unittest.TestCase):
@patch.object(Calculate, "value", new_callable=PropertyMock)
def test_method(self, mocked_attrib):
mocked_attrib.return_value = 1
# Will pass without any complaints
self.assertEqual(Calculate().value, 1)
print("First Assertion Passed!")
self.assertEqual(Calculate().value, 22) # Will throw an assertion
print("Second Assertion Passed!")
if __name__ == "__main__":
Test().test_method()
Producción :
First Assertion Passed!
Traceback (most recent call last):
File "d:\Python Articles\a.py", line 24, in <module>
Test().test_method()
File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
return func(*newargs, **newkeywargs)
File "d:\Python Articles\a.py", line 20, in test_method
self.assertEqual(Calculate().value, 22) # Will throw an assertion
File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
raise self.failureException(msg)
AssertionError: 1 != 22
En la solución se crea un nuevo método, test_method
, para modificar el valor de Calculate.value
. Es vital tener en cuenta que una función está decorada con un patch.object
.
El decorador parche
en el módulo ayuda a parchear módulos y atributos de nivel de clase. Para hacer que parchear sea un poco más específico, usamos patch.object
en lugar de patch
para parchear el método directamente.
En el decorador, primero, se pasa el nombre de la clase Calculate
, indicando que el objeto a parchear es una parte de Calculate
con el nombre del atributo value
que se pasa.
El último parámetro es un objeto PropertyMock
, donde sobrescribimos el atributo valor
pasando un número diferente.
El flujo general del programa es el siguiente:
- Se llama
test_method
. - Se parchea el
valor
de la claseCalculate
, con unnew_callable
asignado, una instancia dePropertyMock
. Calculate.value
se sobrescribe con1
usando la propiedadreturn_value
.- Se comprueba la primera afirmación, donde
Calcular.valor
debe ser igual a1
. - Se comprueba la segunda afirmación, donde
Calcular.valor
debe ser igual a22
.
Simular un atributo de clase sin usar PropertyMock
También podemos resolverlo sin usar PropertyMock
. Solo se requiere una ligera modificación al ejemplo anterior.
Considere el siguiente código:
import unittest
from unittest.mock import patch
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
class Test(unittest.TestCase):
@patch.object(Calculate, "value", 1)
def test_method(self):
# Will pass without any complaints
self.assertEqual(Calculate().value, 1)
print("First Assertion Passed!")
# Will throw an assertion because "Calculate.value" is now 1
self.assertEqual(Calculate().value, 22)
print("Second Assertion Passed!")
if __name__ == "__main__":
Test().test_method()
Producción :
First Assertion Passed!
Traceback (most recent call last):
File "d:\Python Articles\a.py", line 23, in <module>
Test().test_method()
File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
return func(*newargs, **newkeywargs)
File "d:\Python Articles\a.py", line 19, in test_method
self.assertEqual(Calculate().value, 22) # Will throw an assertion because "Calculate.value" is now 1
File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
raise self.failureException(msg)
AssertionError: 1 != 22
En lugar de pasar una instancia de PropertyMock
a new_callable
, podemos dar directamente el valor con el que deseamos que se almacene en Calculate.value
.
Constructor de clase simulada de Python
Asegúrese de que todas las variables inicializadas funcionen según lo previsto y no muestren un comportamiento no deseado. También es necesario probar constructores con entradas variadas para reducir los casos de esquina.
Considere el siguiente código:
class Calculate:
num = 0
dic = {}
def __init__(self, number, dictt):
self.num = number
self.dic = dictt
def Process(self): # An example method
pass
Digamos que queremos probar el constructor de la clase Calcular
. Para garantizar que los atributos funcionen según lo previsto, debemos “parchear” el constructor y pasarlo con entradas variadas para eliminar cualquier posible error.
¿Cómo podemos hacer eso? Consulte la siguiente solución.
Use el decorador patch.object
para parchear el constructor
Podemos usar el decorador patch.object
para parchear el constructor.
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock
class Calculate:
num = 0
dic = {}
def __init__(self):
self.num = 1
self.dic = {"11", 2}
def Process(self): # An example method
pass
class Test(unittest.TestCase):
@patch.object(Calculate, "__new__")
def test_method(self, mocked_calc):
mocked_instance = MagicMock()
mocked_instance.num = 10
mocked_instance.dic = {"22": 3}
mocked_calc.return_value = mocked_instance
self.assertEqual(Calculate().num, 10)
self.assertEqual(Calculate().dic, {"22": 3})
print("First set of Asserts done!")
self.assertEqual(Calculate().num, 1)
self.assertEqual(Calculate().dic, {"11", 2})
print("Second set of Asserts done!")
if __name__ == "__main__":
Test().test_method()
Producción :
The first set of Asserts is done!
Traceback (most recent call last):
File "d:\Python Articles\a.py", line 37, in <module>
Test().test_method()
File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
return func(*newargs, **newkeywargs)
File "d:\Python Articles\a.py", line 32, in test_method
self.assertEqual(Calculate().num, 1)
File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
raise self.failureException(msg)
AssertionError: 10 != 1
Vale la pena señalar que en lugar de parchear __init__
, hemos parcheado __new__
.
Es porque la instancia de una clase se crea cuando se ejecuta __new__
, mientras que en __init__
, solo se inicializan las variables. Entonces, dado que necesitamos crear una nueva instancia simulada, ¿por qué parcheamos __new__
en lugar de __init__
?
Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.
LinkedIn