Estoy tratando de entender las diferentes formas de parchear una constante en Python usando mock.patch . Mi objetivo es poder usar una variable definida en mi clase de prueba como el valor de parcheo para mi constante.
Encontré esta pregunta que explica cómo parchear una constante: Cómo parchear una constante en python Y esta pregunta que explica cómo usar self in patch: using self in python @patch decorator
Pero a partir de este segundo enlace, no puedo hacer que testTwo way (proporcionando el simulacro como un parámetro de función) funcione
Aquí está mi caso de uso simplificado:
mimodulo.py
MY_CONSTANT = 5 def get_constant(): return MY_CONSTANTprueba_mimodulo.py
import unittest from unittest.mock import patch import mymodule class Test(unittest.TestCase): #This works @patch("mymodule.MY_CONSTANT", 3) def test_get_constant_1(self): self.assertEqual(mymodule.get_constant(), 3) #This also works def test_get_constant_2(self): with patch("mymodule.MY_CONSTANT", 3): self.assertEqual(mymodule.get_constant(), 3) #But this doesn't @patch("mymodule.MY_CONSTANT") def test_get_constant_3(self, mock_MY_CONSTANT): mock_MY_CONSTANT.return_value = 3 self.assertEqual(mymodule.get_constant(), 3) #AssertionError: <MagicMock name='MY_CONSTANT' id='64980808'> != 3Supongo que no debería usar return_value , porque mock_MY_CONSTANT no es una función. Entonces, ¿qué atributo se supone que debo usar para reemplazar el valor devuelto cuando se llama a la constante?
Creo que está tratando de aprender sobre pruebas unitarias, objetos simulados y cómo reemplazar el valor de una constante en el código bajo prueba.
Comenzaré con su pregunta específica sobre parchear una constante y luego describiré un enfoque más general para reemplazar valores constantes.
Su pregunta específica fue sobre la diferencia entre patch("mymodule.MY_CONSTANT", 3) y patch("mymodule.MY_CONSTANT") . De acuerdo con losdocumentos , el segundo parámetro es nuevo y contiene el valor de reemplazo que se parcheará. Si lo deja como predeterminado, se parcheará un objeto MagicMock . Como señaló en su pregunta, MagicMock.return_value funciona bien para las funciones, pero no está llamando a MY_CONSTANT , por lo que el valor de retorno nunca se usa.
Mi respuesta corta a esta pregunta es: "No use MagicMock para reemplazar una constante". Si por alguna razón, quisiera desesperadamente hacerlo, podría anular lo único que está llamando en esa constante, su __eq__() . (No puedo pensar en ningún escenario en el que esta sea una buena idea).
import unittest from unittest.mock import patch import mymodule class Test(unittest.TestCase): #This works @patch("mymodule.MY_CONSTANT", 3) def test_get_constant_1(self): self.assertEqual(mymodule.get_constant(), 3) #This also works def test_get_constant_2(self): with patch("mymodule.MY_CONSTANT", 3): self.assertEqual(mymodule.get_constant(), 3) #This now "works", but it's a horrible idea! @patch("mymodule.MY_CONSTANT") def test_get_constant_3(self, mock_MY_CONSTANT): mock_MY_CONSTANT.__eq__ = lambda self, other: other == 3 self.assertEqual(mymodule.get_constant(), 3)Ahora la pregunta más general. Creo que el enfoque más simple no es cambiar la constante, sino proporcionar una forma de anular la constante. Cambiar la constante me parece mal, porque se llama constante. (Por supuesto, eso es solo una convención, porque Python no impone valores constantes).
Así es como manejaría lo que estás tratando de hacer.
MY_CONSTANT = 5 def get_constant(override=MY_CONSTANT): return override Luego, su código regular puede simplemente llamar a get_constant() , y su código de prueba puede proporcionar una anulación.
import unittest import mymodule class Test(unittest.TestCase): def test_get_constant(self): self.assertEqual(mymodule.get_constant(override=3), 3)Esto puede volverse más doloroso a medida que su código se vuelve más complicado. Si tiene que pasar esa anulación a través de un montón de capas, es posible que no valga la pena. Sin embargo, tal vez eso le esté mostrando un problema con su diseño que hace que el código sea más difícil de probar.
Simplemente puede asignar un valor simulado a la constante antes de cada afirmación:
def test_get_constant_3(self): mymodule.MY_CONSTANT = 3 self.assertEqual(mymodule.get_constant(), 3) mymodule.MY_CONSTANT = 7 self.assertEqual(mymodule.get_constant(), 7)Algun otro ejemplo
# --- config.py --- class AppConf: APP_TIMEZONE = os.environ.get['APP_TIMEZONE'] # --- my_mod.py --- from datetime import datetime from config import AppConf LOCAL_TZ = AppConf.APP_TIMEZONE def to_local_tz(dt_obj, tz): """Return datetime obj for specific timezone""" # some code here return local_dt_obj def get_local_time(): return to_local_tz(datetime.utcnow(), LOCAL_TZ).strftime('%H:%M') # --- test_my_mod.py --- import my_mod class TestMyMod(unittest.TestCase): @patch('my_mod.datetime') def test_get_local_time(self, mock_dt): # Mock to 15:00 UTC mock_dt.utcnow.return_value = datetime(2017, 5, 3, 15) # Test with TZ 'Europe/Kiev' +02:00 +03:00(DST) my_mod.LOCAL_TZ = 'Europe/Kiev' assert my_mod.get_local_time() == '18:00' # Test with TZ 'America/New_York' -05:00 -04:00(DST) my_mod.LOCAL_TZ = 'America/New_York' assert my_mod.get_local_time() == '11:00'Así que no hay necesidad de parchear una constante en absoluto