• Introducción
  • unittest.,mock or mock
  • Decorator
  • Resource location
  • mock return_value vs side_effect
  • Mock Anided Calls
  • Verify Exceptions
  • Clearing lru_cache
  • Mock Module Level/Global Variables
  • Mock Instance Method
  • Mock Class Method
  • Mock Entire Class
  • Mock Async Calls
  • mock instance types
  • mock BUILTIN open function
  • conclusion

introduction

burlarse de los recursos al escribir pruebas en Python puede ser confuso si no está familiarizado con hacer tales cosas., En este post voy a cubrir varios aspectos del código de burla, que espero sea un recurso útil para aquellos que están un poco atascados.

nota: en los ejemplos de código Estoy usando pytest, pero en su mayor parte eso no debería importar.

unittest.mock or mock

para ‘mockear’ un recurso primero necesitaremos el módulo mock, y este es nuestro primer obstáculo: ¿qué versión necesitamos? es decir, hay dos y ambos parecen ser oficiales (mock y unittest.mock).,

el módulo mock es una biblioteca compatible con versiones anteriores que puedes descargar desde PyPy, donde as unittest.mock es lo mismo pero solo compatible con la versión de Python que estás usando.,

por lo tanto, en casi todos los casos, querrá importarlo de la siguiente manera:

import unittest.mock as mock

para obtener más ejemplos, consulte esta guía de referencia

Decorator

mock Resources es usar un decorador Python alrededor de su función de prueba:

@mock.patch("thing")def test_stuff(mock_thing): mock_thing.return_value = 123

en este caso, lo que estamos parcheando (thing) puede ser una variable o una función.,

Cuando hagas esto tendrás que pasar un argumento a tu función (puedes nombrarlo como quieras †) que será un MagicMock.

esto significa que si no hace nada más, entonces las llamadas a thing darán como resultado (al menos en el ejemplo anterior) que se devuelva el valor 123.

† convención es el nombre de la variable mock_<noun>.,

Si se está burlando de varias cosas, apilará los decoradores de simulación uno encima del otro y los pasará a la función de prueba:

@mock.patch("third")@mock.patch("second")@mock.patch("first")def test_stuff(mock_first, mock_second, mock_third): ...

ubicación del recurso

es importante saber que al burlarse debe especificar la ubicación del recurso a donde se importa., Esto se explica mejor a modo de ejemplo

Imagine que tengo un móduloapp.foo y dentro de ese módulo importo otra dependencia como así:

from app.bar import thing

podría pensar que cuando llama amock.patch que le pasa una referencia al recurso como app.bar.thing. Eso solo sería relevante si el recurso estaba siendo llamado con esa ruta completa dentro del módulo app.foo (por ejemplo, if app.foo called app.bar.thing(...)).,

si no se hace referencia a la ruta completa del espacio de nombres, que no está en el ejemplo anterior (observe que importamos solo el recurso thing). Significa que necesitamos especificar el espacio de nombres de referencia para simular como donde se importa:

@mock.patch('app.foo.thing')

así que aunque thing existe dentro de app.bar especificamos app.foo.thing como app.foo es donde lo hemos importado para su uso. Esto atrapa a la gente todo el tiempo.,

Mock return_value vs side_effect

Si su función tiene un try/except alrededor de ella, entonces puede usar side_effect para hacer que la llamada de la función Active una excepción como el valor devuelto:

@mock.patch('app.aws.sdk.confirm_sign_up', side_effect=Exception('whoops'))

nota: si hubiera utilizado return_value=Exception('whoops') entonces el mock devolvería la representación de cadena de la excepción en lugar de levantar una excepción como lo hace side_effect.,se llamó al método en un objeto simulado:

la razón por la que esto se puede complicar es debido a cómo un mock devolverá un nuevo mock al acceder a una propiedad en un mock:

el código anterior se equivocará:

AssertionError: expected call not found.Expected: listen(8080)Actual: listen(123)

deberá asegurarse de que afirma el mock en el momento correcto:

verificar excepciones

Si queremos verificar que alguna pieza de código lanza un tipo Exception cuando lo necesitamos podemos simular recursos específicos para lanzar una excepción y luego usar pytest.raises como un administrador de contexto alrededor del llamante de nuestro código para ser verificado.,

podemos capturar y hacer aserciones contra este comportamiento esperado burlando primero el recurso que queremos lanzar una excepción y conseguir que lance nuestra propia excepción falsa usando el parámetro side_effect.

lo Siguiente que especifica exactamente el tipo de excepción estamos esperando que se planteó la utilización de pytest.raises(T):

Nota: no cometa el error de poner cualquier afirmación en el with context manager., Una vez que la excepción es levantada por la función que es llamada dentro del gestor de contexto with, todo el código después de ella dentro del bloque es omitido.

Clearing lru_cache

Si una función que desea probar tiene el functools.lru_cache decorator aplicado, entonces deberá ser consciente de burlarse de la respuesta de esa función, ya que se almacenará en caché en una prueba y el resultado almacenado en caché se devolverá cuando llame a la función nuevamente para probar algún otro comportamiento (y podría es probable que te confunda cuando veas la respuesta inesperada).,

Para solucionar este problema es muy fácil, porque lru_cache proporciona funciones adicionales cuando decoratoring sus funciones, contiene:

  • cache_info
  • cache_clear

El último (cache_clear), es lo que sería necesario llamar. Esto se demuestra a continuación:

Nota: depurar esto no siempre es obvio., Más tarde demostré cómo simular la función builtin open, y en ese escenario me topé con este problema, porque aunque no me estaba burlando de la función de nivel superior en sí (me estaba burlando de la llamada a open dentro), el contenido del archivo que se abría era lo que se devolvía y se almacenaba en caché.

Mock Module Level/Global Variables

con una variable de módulo puede establecer el valor directamente o usar mock.patch.,

En el siguiente ejemplo tenemos la variable client_id que es una variable global dentro de la etiqueta app.aws módulo que vamos a importar a referencia en otras partes de nuestro código:

En el mock.patch ejemplo, hay dos cosas a considerar:

  1. no usamos return_value.
  2. no se pasa ninguna instancia simulada a la función de prueba.,

esto se debe a que estamos modificando una variable y no una función directa o’ callable’, por lo que no hay necesidad de pasar un simulacro a la función de prueba (si desea cambiar el valor unas cuantas veces dentro de la prueba en sí, entonces se burlaría de la variable pero no asignaría inmediatamente un valor en el decorador).

Mock Instance Method

Hay varias maneras de lograr la simulación de un método de instancia., Un enfoque común es usar mock.patch.object, así:

otro enfoque es burlarse del método como lo haría con una función normal, pero hace referencia al método a través del nombre de clase:

otro enfoque (aunque más pesado) para burlarse de un método de instancia de clase es aprovechar el hecho de que un Mock devolverá una nueva instancia mock cuando se llame:

@mock.patch("foo.bar.SomeClass")def test_stuff(mock_class): mock_class.return_value.made_up_function.return_value = "123"

nota: en el ejemplo anterior nos burlamos de toda la clase, que podría no ser lo que quieres. Si no, utilice el ejemplo anterior mock.patch.object en su lugar.,

la razón por la que el ejemplo anterior funciona es porque estamos configurando return_value en nuestro mock. Debido a que este es un MagicMock cada atributo referenciado devuelve una nueva instancia mock (una función o propiedad que llame en un mock no tiene que existir) y por lo tanto llamamos made_up_function en el mock devuelto, y en ese mock recién creado establecemos el final return_value a 123.,

pero como se mencionó en la nota anterior, este enfoque podría ser un poco demasiado brusco dependiendo de cuáles sean sus necesidades (ya sea que le importe si tiene una clase funcional o no).

Mock Class Method

para simular un método de clase es un enfoque similar a la burla de un método de instancia.,

un enfoque podría ser que te burlas de toda la clase (pero ahora tienes una menos return_value para asignar a):

mock_class.ClassMethodName.return_value = "123"

o mejor aún, deberías burlarte de ella como lo harías con cualquier función normal, pero solo haz referencia al método a través de la clase:

simula toda la clase

class necesitarás establecer el return_value para que sea una nueva instancia de la clase.,

vea otros consejos de burla relacionados con la clase aquí

llamadas asíncronas Mock

burlarse del código asíncrono es probablemente el aspecto más confuso de la burla. Mi’ ir a ‘ solución voy a explicar primero, pero después de que voy a compartir algunos métodos alternativos que he visto y probado en el pasado.,

primero considere este código asincrónico dentro de un módulo app.foo:

import app.stuffasync def do_thing(x): return await app.stuff.some_concurrent_function(x)

si necesitamos simular la corrutina app.stuff.some_concurrent_function, entonces podemos resolver esto creando una función que actúe como una corrutina y puede configurarse para diferentes tipos de respuestas:

nota: el ejemplo utiliza tornado para ejecutar una prueba asíncrona.,e alternatives

AsyncMock

Nota: Esto utiliza el paquetepytest-asyncio para ayudar con las pruebas de código asyncio

comencemos con el código p>

import asyncioasync def sum(x, y): await asyncio.sleep(1) return x + y

ahora aquí es cómo nos burlaríamos de él

Monkey Patch

subclase magicmock

función inline async

tipos de instancias mock

al burlarse de un objeto, encontrará que el simulacro reemplaza todo el objeto y, por lo tanto, puede causar que las pruebas pasen (o fallen) de maneras inesperadas.,

Es decir, si necesita hacer un mock más como la interfaz concreta, entonces hay dos maneras de hacerlo:

  1. spec
  2. wrap

Podemos usar mock’s spec Característica para imitar todos los métodos / atributos del objeto que se está burlando. Esto asegura que sus mock tengan la misma api que los objetos que están reemplazando.

Nota: hay más estrictas spec_set que se producirá un AttributeError.,

esto se demuestra mejor con un ejemplo:

el parámetro wrap por otro lado le permite ‘espiar’ la implementación, así como afectar su comportamiento.,en uno de los anteriores que se burla de todo el objeto, no solo de un único método:

mock builtin open function

la biblioteca mock de Python proporciona una abstracción para burlarse de la función builtin open function a lot simpler simpler

The create=True param set on mock.patch significa que el mock.MagicMock devuelto creará automáticamente cualquier atributo que se invoque en el mock (esto se debe a que la función open intentará acceder a muchas cosas diferentes y es más fácil para mock burlarse de todo eso para usted).,

conclusión

ahí terminaremos. Esperemos que esta lista de técnicas de burla será capaz de ver a través de incluso el más complejo de código que necesita ser probado. Déjame saber lo que piensas en twitter.