in

Cuando y como usar Python mock

0AYsmvOsJ6rXcyLZU
Foto de Suzanne D. Williams en Unsplash

Cuando y como usar Python mock

Reduzca el tiempo de ejecución de su prueba con simulacro

Akshar Raaj

Akshar Raaj

19 de agosto de 2019·4 min de lectura

Agenda

Esta publicación cubrirá cuándo y cómo usar unittest.mock Biblioteca.

Los documentos de Python describen adecuadamente la biblioteca simulada:

unittest.mock allows you to replace parts of your system under test with mock objects and make assertions about how they have been used.

Cuando usar la biblioteca simulada

La respuesta corta es «la mayoría de las veces». Los siguientes ejemplos aclararían esta afirmación:

Defina las siguientes tres funciones en shell.

In [1]: def cow():
...: print("cow")
...: return {'cow': 'moo'}
...:
In [2]: def dog():
...: print("dog")
...: return {'dog': 'bark'}
...:
In [3]: def animals():
...: data = cow()
...: data.update(dog())
...: data['pig'] = 'oink'
...: return data
...:

Vamos a ejecutar animals. Los veganos me matarían;)

In [4]: animals()
cow
dog
Out[4]: {'cow': 'moo', 'dog': 'bark', 'pig': 'oink'}

La salida confirma que cow y dog fueron invocados desde animals().

Escribamos una prueba para cow.

In [5]: def test_cow():
...: assert cow() == {'cow': 'moo'}
...:

Vamos a ejecutar test_cow para asegurar cow se está comportando como se esperaba.

In [6]: test_cow()
cow

Probemos de manera similar dog.

In [7]: def test_dog():
...: assert dog() == {'dog': 'bark'}
...:
In [8]: test_dog()
dog

Agreguemos una prueba para animals.

In [9]: def test_animals():
...: assert animals() == {'dog': 'bark', 'cow': 'moo', 'pig': 'oink'}
...:
In [10]: test_animals()
cow
dog

Como se desprende de las declaraciones impresas, cow() y dog() fueron ejecutados desde test_animals().

miXecuación de cow y dog son innecesarios durante la prueba animals porque cow y dog ya se han probado de forma aislada.

Mientras prueba animals, solo queremos estar seguros de que cow y dog sería ejecutado. No queremos que suceda la ejecución real.

cow y dog son funciones diminutas. Hay ejecución desde test_animals no es un gran problema actualmente. Tenía funciones cow y dog ha sido enorme, test_animals habría llevado mucho tiempo completarlo.

Este escenario se puede evitar si usamos unitest.mock.patch.

Vamos a modificar test_animals tener el siguiente aspecto:

In [17]: from unittest.mock import patchIn [18]: @patch('__main__.cow')
...: @patch('__main__.dog')
...: def test_animals(patched_dog, patched_cow):
...: data = animals()
...: assert patched_dog.called is True
...: assert patched_cow.called is True
...:

Ejecute `test_animals ()`.

In [19]: test_animals()

No podemos ver declaraciones impresas de cow y dog ya no. Esto confirma que cow y dog no fueron ejecutados.

Vamos a diseccionar test_animals.

test_animals ha sido decorado con @patch. Función dog se pasa como argumento a @patch.

Como test_animals ha sido decorado, por lo que en el contexto de test_animals, función real dog ha sido reemplazado por un unittest.mock.Mock ejemplo. Esta Mock instancia se está refiriendo como patched_dog.

Ya que animals() se ejecuta en el contexto de test_animals(), por lo que no se realiza ninguna llamada real a dog de animals. En lugar de patched_dog se llama.

Mock las instancias tienen un atributo llamado called que se establece en verdadero si se invoca una instancia de Mock desde una función bajo prueba. Afirmamos que la instancia simulada, patched_dog, se ha invocado.

Si llama a dog de animals es eliminado / comentado, luego test_animals fallaría.

In [20]: def animals():
...: data = cow()
...: #data.update(dog())
...: data['pig'] = 'oink'
...: return data
...:
In [21]: test_animals()
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-21-f8c77986484f> in <module>
----> 1 test_animals()
/usr/local/Cellar/python/3.6.5/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py in patched(*args, **keywargs)
1177
1178 args += tuple(extra_args)
-> 1179 return func(*args, **keywargs)
1180 except:
1181 if (patching not in entered_patchers and
<ipython-input-18-a243d0eea2e8> in test_animals(patched_dog, patched_cow)
3 def test_animals(patched_dog, patched_cow):
4 data = animals()
----> 5 assert patched_dog.called is True
6 assert patched_cow.called is True
7
AssertionError:

Esto confirma que la prueba proporciona una cobertura adecuada sin ejecutar realmente cow y dog.

Estableciendo return_value en una instancia de Mock

Descomente el código comentado.

Hay un código adicional en animals aparte de llamar cow y dog. animals agrega cerdo a los datos también.

Probemos el código adicional de animals.

In [43]: @patch('__main__.cow')
...: @patch('__main__.dog')
...: def test_animals(patched_dog, patched_cow):
...: patched_cow.return_value = {'c': 'm'}
...: patched_dog.return_value = {'d': 'b'}
...: data = animals()
...: assert patched_dog.called is True
...: assert patched_cow.called is True
...: assert 'pig' in data

Ejecutemos la prueba

In [45]: test_animals()

Todas nuestras afirmaciones pasaron porque no había AssertionError.

El valor de retorno predeterminado de llamar a Mock la función es otra Mock ejemplo. Así que cuando patched_dog se invoca desde animals en test_animals contexto, devuelve una instancia de Mock. No queremos que devuelva una instancia de Mock porque el código adicional de animals espera que sea un diccionario.

Establecemos un return_value sobre patched_cow ser un diccionario. Lo mismo es cierto para patched_dog.

Ahora código adicional de animals también está cubierto por la prueba.

Otro ejemplo

Definamos una función para probar si una URL es válida. Esto se basa en Python requests.

In [59]: import requestsIn [60]: def is_valid_url(url):
...: try:
...: response = requests.get(url)
...: except Exception:
...: return False
...: return response.status_code == 200

Agreguemos una prueba para is_valid_url.

In [69]: def test_is_valid_url():
...: assert is_valid_url('https://agiliq.com') is True
...: assert is_valid_url('https://agiliq.com/eerwweeee') is False # We want False in 404 pages too
...: assert is_valid_url('https://aeewererr.com') is False
In [70]: test_is_valid_url()

Habría notado lo lenta que es su prueba mientras realiza las llamadas de red.

Reparemos esto utilizando patch y Mock para hacer nuestras pruebas más rápidas.

In [71]: @patch('__main__.requests')
...: def test_is_valid_url(patched_requests):
...: patched_requests.get.return_value = Mock(status_code=200)
...: assert is_valid_url('https://agiliq.com') is True
...: patched_requests.get.return_value = Mock(status_code=404)
...: assert is_valid_url('https://agiliq.com/eerwweeee') is False # We want False in 404 pages too
...: patched_requests.get = Mock(side_effect=Exception())
...: assert is_valid_url('https://aeewererr.com') is False
...:
In [72]: test_is_valid_url()

Deberías haber notado el aumento de velocidad.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.

Como anadir contactos de la agenda a TikTok1

Cómo encontrar amigos de Instagram en Tiktok

Descargas de archivos de Java – Java SE 12