Simulacro de prueba unitaria de Python:¿Es posible burlarse del valor de los argumentos predeterminados de un método en el momento de la prueba?

StackOverflow https://stackoverflow.com//questions/24021491

  •  21-12-2019
  •  | 
  •  

Pregunta

Tengo un método que acepta argumentos predeterminados:

def build_url(endpoint, host=settings.DEFAULT_HOST):
    return '{}{}'.format(host, endpoint)

Tengo un caso de prueba que ejercita este método:

class BuildUrlTestCase(TestCase):
    def test_build_url(self):
        """ If host and endpoint are supplied result should be 'host/endpoint' """

        result = build_url('/end', 'host')
        expected = 'host/end'

        self.assertEqual(result,expected)

     @patch('myapp.settings')
     def test_build_url_with_default(self, mock_settings):
        """ If only endpoint is supplied should default to settings"""
        mock_settings.DEFAULT_HOST = 'domain'

        result = build_url('/end')
        expected = 'domain/end'

        self.assertEqual(result,expected)

Si dejo caer un punto de depuración en build_url e inspeccionar este atributo settings.DEFAULT_HOST devuelve el valor simulado.Sin embargo, la prueba continúa fallando y la afirmación indica host se le asigna el valor de mi real settings.py.Sé que esto se debe a que el host El argumento de palabra clave se establece en el momento de la importación y mi simulacro no se considera.

depurador

(Pdb) settings
<MagicMock name='settings' id='85761744'>                                                                                                                                                                                               
(Pdb) settings.DEFAULT_HOST
'domain'
(Pdb) host
'host-from-settings.com'                                                                                                                                                 

¿Hay alguna manera de anular este valor en el momento de la prueba para poder ejercer la ruta predeterminada con un simulado? settings ¿objeto?

¿Fue útil?

Solución

Las funciones almacenan los valores predeterminados de sus parámetros en el func_defaults atributo cuando se define la función, para que pueda parchearlo.Algo como

def test_build_url(self):
    """ If only endpoint is supplied should default to settings"""

    # Use `func_defaults` in Python2.x and `__defaults__` in Python3.x.
    with patch.object(build_url, 'func_defaults', ('domain',)):
      result = build_url('/end')
      expected = 'domain/end'

    self.assertEqual(result,expected)

yo suelo patch.object como administrador de contexto en lugar de decorador para evitar que el objeto de parche innecesario se pase como argumento a test_build_url.

Otros consejos

Apliqué la otra respuesta a esta pregunta, pero después del administrador de contexto, la función parcheada no era la misma que antes.

Mi función parcheada se ve así:

def f(foo=True):
    pass

En mi prueba, hice esto:

with patch.object(f, 'func_defaults', (False,)):

al llamar f después (no en) el administrador de contexto, el valor predeterminado desapareció por completo en lugar de volver al valor anterior.Vocación f sin argumentos dio el error TypeError: f() takes exactly 1 argument (0 given)

En cambio, hice esto antes de mi prueba:

f.func_defaults = (False,)

Y esto después de mi prueba:

f.func_defaults = (True,)

Una forma alternativa de hacer esto:Utilice functools.partial para proporcionar los argumentos "predeterminados" que desee.esto no es técnicamente lo mismo que anularlos;la persona que llama ve un argumento explícito, pero la persona que llama no tiene que proporcionarlo.Eso está bastante cerca la mayor parte del tiempo y hace lo correcto después de que sale el administrador de contexto:

# mymodule.py
def myfunction(arg=17):
    return arg

# test_mymodule.py
from functools import partial
from mock import patch

import mymodule

class TestMyModule(TestCase):
    def test_myfunc(self):
        patched = partial(mymodule.myfunction, arg=23)
        with patch('mymodule.myfunction', patched):
            self.assertEqual(23, mymodule.myfunction())  # Passes; default overridden
        self.assertEqual(17, mymodule.myfunction()) # Also passes; original default restored

Utilizo esto para anular las ubicaciones predeterminadas de los archivos de configuración durante las pruebas.Crédito a quien corresponde, obtuve la idea de Danilo Bargen. aquí

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top