Atributo de clase simulada de Python

Salman Mehmood 21 junio 2023
  1. Motivo para simular un atributo de clase
  2. Posibles soluciones para simular un atributo de clase
  3. Constructor de clase simulada de Python
Atributo 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:

  1. Se llama test_method.
  2. Se parchea el valor de la clase Calculate, con un new_callable asignado, una instancia de PropertyMock.
  3. Calculate.value se sobrescribe con 1 usando la propiedad return_value.
  4. Se comprueba la primera afirmación, donde Calcular.valor debe ser igual a 1.
  5. Se comprueba la segunda afirmación, donde Calcular.valor debe ser igual a 22.

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__?

Salman Mehmood avatar Salman Mehmood avatar

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

Artículo relacionado - Python Unit Test