El libro "El camino de Python. Cinturón negro para desarrollo, escalado, prueba y despliegue ”

imagen Hola habrozhiteli! Python Path le permite perfeccionar sus habilidades profesionales y aprender lo más posible sobre las capacidades del lenguaje de programación más popular. Aprenderá a escribir código efectivo, crear los mejores programas en un tiempo mínimo y evitar errores comunes. Es hora de familiarizarse con la informática y la memorización de subprocesos múltiples, obtener asesoramiento experto en el campo del diseño de API y bases de datos, y también mirar dentro de Python para ampliar su comprensión del lenguaje. Debe comenzar un proyecto, trabajar con versiones, organizar pruebas automáticas y elegir un estilo de programación para una tarea específica. Luego, continuará estudiando declaraciones efectivas de funciones, seleccionará estructuras de datos y bibliotecas adecuadas, creará programas, paquetes y optimizará los programas a nivel de bytecode.

Extracto Ejecutando pruebas en paralelo


Ejecutar suites de prueba puede llevar mucho tiempo. Esto es una ocurrencia común en proyectos grandes cuando un conjunto de pruebas tarda minutos en completarse. Por defecto, pytest ejecuta pruebas secuencialmente, en un orden específico.

Dado que la mayoría de las computadoras tienen procesadores de múltiples núcleos, puede acelerar si separa las pruebas para ejecutarlas en múltiples núcleos.

Para esto, pytest tiene un complemento pytest-xdist que se puede instalar usando pip. Este complemento extiende la línea de comando pytest con el argumento ––numprocesses (abreviado –n), que toma el número de núcleos utilizados como argumento. El lanzamiento de pytest –n 4 ejecutará el conjunto de pruebas en cuatro procesos paralelos, manteniendo un equilibrio entre la carga de núcleos disponibles.

Debido al hecho de que la cantidad de núcleos puede variar, el complemento también acepta la palabra clave automática como valor. En este caso, la cantidad de núcleos disponibles se devolverá automáticamente.

Crear objetos usados ​​en pruebas usando accesorios


En las pruebas unitarias, a menudo es necesario realizar un conjunto de operaciones estándar antes y después de ejecutar la prueba, y estas instrucciones involucran ciertos componentes. Por ejemplo, puede necesitar un objeto que exprese el estado de configuración de la aplicación, y debe inicializarse antes de cada prueba, y luego restablecerse a sus valores iniciales después de la ejecución. Del mismo modo, si la prueba depende de un archivo temporal, este archivo debe crearse antes de la prueba y eliminarse después. Dichos componentes se llaman accesorios . Se instalan antes de probar y desaparecen después de su ejecución.

En pytest, los accesorios se declaran como funciones simples. La función de dispositivo debe devolver el objeto deseado para que, al probar dónde se usa, se pueda usar este objeto.

Aquí hay un ejemplo de un accesorio simple:

import pytest @pytest.fixture def database(): return <some database connection> def test_insert(database): database.insert(123) 

El dispositivo de base de datos es utilizado automáticamente por cualquier prueba que tenga el argumento de la base de datos en su lista. La función test_insert () recibirá el resultado de la función database () como primer argumento y utilizará este resultado como lo considere conveniente. Con este uso de accesorios, no necesita repetir el código de inicialización de la base de datos varias veces.

Otra característica común de las pruebas de código es la capacidad de eliminar lo superfluo después de la operación del dispositivo. Por ejemplo, cierre la conexión de la base de datos. La implementación de un dispositivo como generador agregará funcionalidad para limpiar objetos verificados (Listado 6.5).

Listado 6.5. Borrar un objeto verificado


 import pytest @pytest.fixture def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123) 
Como usamos la palabra clave de rendimiento e hicimos un generador de la base de datos, el código después de la declaración de rendimiento se ejecuta solo al final de la prueba. Este código cerrará la conexión de la base de datos al final de la prueba.

Cerrar la conexión de la base de datos para cada prueba puede ocasionar un desperdicio injustificado de potencia informática, ya que otras pruebas pueden usar una conexión ya abierta. En este caso, puede pasar el argumento de alcance al decorador de dispositivos, especificando su alcance:

 import pytest @pytest.fixture(scope="module") def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123) 

Al especificar el parámetro scope = "module", usted inicializó el dispositivo una vez para todo el módulo, y ahora estará disponible una conexión de base de datos abierta para todas las funciones de prueba que lo soliciten.

Puede ejecutar un código general antes o después de la prueba, definiendo los dispositivos como usados ​​automáticamente con la palabra clave autouse, en lugar de especificarlos como un argumento para cada función de prueba. La concreción de la función pytest.fixture () utilizando el argumento True, la palabra clave autouse, asegura que los dispositivos se invoquen cada vez antes de ejecutar la prueba en el módulo o clase donde se declara.

 import os import pytest @pytest.fixture(autouse=True) def change_user_env(): curuser = os.environ.get("USER") os.environ["USER"] = "foobar" yield os.environ["USER"] = curuser def test_user(): assert os.getenv("USER") == "foobar"</source     .  ,    :     ,      ,       . <h3>  </h3>           ,    ,   ,         .          Gnocchi,    . Gnocchi      <i>storage API</i>.    Python          .       ,      API   .        ,      (    storage API),  ,       .   ,   <i> </i>,     ,        .  6.6          ,    :    mysql,   —  postgresql. <blockquote><h4> 6.6.      </h4> <source lang="python">import pytest import myapp @pytest.fixture(params=["mysql", "postgresql"]) def database(request): d = myapp.driver(request.param) d.start() yield d d.stop() def test_insert(database): database.insert("somedata") 
El dispositivo del controlador recibe dos valores diferentes como parámetro: los nombres de los controladores de la base de datos que admite la aplicación. test_insert se ejecuta dos veces: una para la base de datos MySQL y la segunda para la base de datos PostgreSQL. Esto facilita volver a tomar la misma prueba, pero con diferentes escenarios, sin agregar nuevas líneas de código.

Pruebas gestionadas con objetos ficticios


Los objetos ficticios (o trozos, objetos simulados) son objetos que imitan el comportamiento de los objetos de aplicaciones reales, pero en un estado especial y controlado. Son más útiles para crear entornos que describan minuciosamente las condiciones para la prueba. Puede reemplazar todos los objetos, excepto el probado, con objetos ficticios y aislarlo, así como crear un entorno para la prueba de código.

Un caso de uso es crear un cliente HTTP. Es casi imposible (o más bien, increíblemente difícil) crear un servidor HTTP en el que pueda ejecutar todas las situaciones y escenarios para cada valor posible. Los clientes HTTP son especialmente difíciles de probar para escenarios de error.

La biblioteca estándar tiene un comando simulado para crear un objeto ficticio. Comenzando con Python 3.3, simulacro se ha integrado con la biblioteca unittest.mock. Por lo tanto, puede usar el fragmento de código a continuación para proporcionar compatibilidad con versiones anteriores entre Python 3.3 y anteriores:

 try: from unittest import mock except ImportError: import mock 

La biblioteca simulada es muy fácil de usar. Cualquier atributo disponible para el objeto mock.Mock se crea dinámicamente en tiempo de ejecución. A cualquier atributo se le puede asignar cualquier valor. En el Listado 6.7, simulacro se usa para crear un objeto ficticio para el atributo ficticio.

Listado 6.7. Accediendo al atributo mock.Mock


 >>> from unittest import mock >>> m = mock.Mock() >>> m.some_attribute = "hello world" >>> m.some_attribute "hello world" 
También puede crear dinámicamente un método para un objeto mutable, como en el Listado 6.8, donde crea un método ficticio que siempre devuelve 42 y toma lo que quiera como argumento.

Listado 6.8. Crear un método para el objeto ficticio simulado.


 >>> from unittest import mock >>> m = mock.Mock() >>> m.some_method.return_value = 42 >>> m.some_method() 42 >>> m.some_method("with", "arguments") 42 
Solo un par de líneas, y el objeto mock.Mock ahora tiene un método some_method (), que devuelve 42. Toma cualquier tipo de argumento, mientras que no hay verificación de cuál es el argumento.

Los métodos generados dinámicamente también pueden tener efectos secundarios (intencionales). Para no ser solo métodos repetitivos que devuelven un valor, se pueden definir para ejecutar código útil.

El listado 6.9 crea un método ficticio que tiene un efecto secundario: muestra la cadena "hola mundo".

Listado 6.9. Crear un método para un simulacro. Objeto simulado con un efecto secundario


  >>> from unittest import mock >>> m = mock.Mock() >>> def print_hello(): ... print("hello world!") ... return 43 ... ❶ >>> m.some_method.side_effect = print_hello >>> m.some_method() hello world! 43 ❷ >>> m.some_method.call_count 1 
Asignamos una función completa al atributo some_method ❶. Técnicamente, esto le permite implementar un escenario más complejo en la prueba, debido al hecho de que puede incluir cualquier código necesario para la prueba en el objeto ficticio. A continuación, debe pasar este objeto a la función que lo espera.

El atributo ❷ call_count es una manera fácil de verificar la cantidad de veces que se ha llamado a un método.

La biblioteca simulada utiliza el patrón de verificación de acción: esto significa que después de la prueba debe asegurarse de que las acciones reemplazadas por dummies se realizaron correctamente. El listado 6.10 aplica el método afirmar () a objetos ficticios para realizar estas comprobaciones.

Listado 6.10. Métodos de verificación de llamadas


  >>> from unittest import mock >>> m = mock.Mock() ❶ >>> m.some_method('foo', 'bar') <Mock name='mock.some_method()' id='26144272'> ❷ >>> m.some_method.assert_called_once_with('foo', 'bar') >>> m.some_method.assert_called_once_with('foo', ❸mock.ANY) >>> m.some_method.assert_called_once_with('foo', 'baz') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in assert_cal led_once_with return self.assert_called_with(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in assert_cal led_with raise AssertionError(msg) AssertionError: Expected call: some_method('foo', 'baz') Actual call: some_method('foo', 'bar') 
Creamos métodos con argumentos foo y bar como pruebas llamando al método ❶. Una manera fácil de probar las llamadas a objetos ficticios es utilizar los métodos afirmar_callados (), como aseguir_callados_con_con () ❷. Para estos métodos, debe pasar los valores que espera usar al llamar al método ficticio. Si los valores pasados ​​difieren de los utilizados, simulacro genera una excepción AssertionError. Si no sabe qué argumentos se pueden pasar, use mock.ANY como el valor de ❸; reemplazará cualquier argumento pasado al método ficticio.

La biblioteca simulada también se puede utilizar para reemplazar una función, método u objeto de un módulo externo. En el Listado 6.11, reemplazamos la función os.unlink () con nuestra propia función ficticia.

Listado 6.11. Usando mock.patch


 >>> from unittest import mock >>> import os >>> def fake_os_unlink(path): ... raise IOError("Testing!") ... >>> with mock.patch('os.unlink', fake_os_unlink): ... os.unlink('foobar') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in fake_os_unlink IOError: Testing! 
Cuando se usa como administrador de contexto, mock.patch () reemplaza la función de destino con la que seleccionamos. Esto es necesario para que el código ejecutado dentro del contexto utilice el método corregido. Usando el método mock.patch (), puede modificar cualquier parte del código externo, forzándolo a comportarse de manera tal que pruebe todas las condiciones de la aplicación (Listado 6.12).

Listado 6.12. Usando mock.patch () para probar muchos comportamientos


  from unittest import mock import pytest import requests class WhereIsPythonError(Exception): passdef is_python_still_a_programming_language(): try: r = requests.get("http://python.org") except IOError: pass else: if r.status_code == 200: return 'Python is a programming language' in r.content raise WhereIsPythonError("Something bad happened") def get_fake_get(status_code, content): m = mock.Mock() m.status_code = status_code m.content = content def fake_get(url): return m return fake_get def raise_get(url): raise IOError("Unable to fetch url %s" % url) ❷ @mock.patch('requests.get', get_fake_get( 200, 'Python is a programming language for sure')) def test_python_is(): assert is_python_still_a_programming_language() is True @mock.patch('requests.get', get_fake_get( 200, 'Python is no more a programming language')) def test_python_is_not(): assert is_python_still_a_programming_language() is False @mock.patch('requests.get', get_fake_get(404, 'Whatever')) def test_bad_status_code(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() @mock.patch('requests.get', raise_get) def test_ioerror(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() 


El Listado 6.12 implementa un caso de prueba que busca todas las instancias de Python es una cadena de lenguaje de programación en python.org ❶. No hay ninguna opción en la que la prueba no encuentre ninguna línea determinada en la página web seleccionada. Para obtener un resultado negativo, debe cambiar la página, pero esto no se puede hacer. Pero con la ayuda de simulacro, puede ir al truco y cambiar el comportamiento de la solicitud para que devuelva una respuesta ficticia con una página ficticia que no contiene una cadena dada. Esto le permitirá probar un escenario negativo en el que python.org no contiene una cadena determinada y asegurarse de que el programa maneja dicho caso correctamente.

Este ejemplo usa la versión de decorador mock.patch () . El comportamiento del objeto ficticio no cambia, y fue más fácil establecer un ejemplo en el contexto de una función de prueba.

El uso de un objeto ficticio ayudará a simular cualquier problema: el servidor devuelve un error 404, un error de E / S o un error de retraso de la red. Podemos asegurarnos de que el código devuelva los valores correctos o arroje la excepción correcta en cada caso, lo que garantiza el comportamiento esperado del código.

Identificar código no probado con cobertura


Una gran adición a las pruebas unitarias es la herramienta de cobertura. [La cobertura del código es una medida utilizada en las pruebas. Muestra el porcentaje del código fuente del programa que se ejecutó durante el proceso de prueba ed. ], que encuentra piezas de código no probadas. Utiliza análisis de código y herramientas de seguimiento para identificar las líneas que se ejecutaron. En las pruebas unitarias, puede revelar qué partes del código se reutilizaron y cuáles no se utilizaron en absoluto. Es necesario crear pruebas, y la capacidad de descubrir qué parte del código que olvidó cubrir con las pruebas hace que este proceso sea más agradable.

Instale el módulo de cobertura a través de pip para poder usarlo a través de su shell.

NOTA


El comando también se puede llamar cobertura de python si el módulo se instala a través del instalador de su sistema operativo. Un ejemplo de esto es el sistema operativo Debian.


Usar cobertura fuera de línea es bastante simple. Muestra aquellas partes del programa que nunca se inician y se convierten en un "peso muerto", un código que no se puede eliminar sin cambiar la funcionalidad del programa. Todas las herramientas de prueba que se discutieron anteriormente en el capítulo están integradas con la cobertura.

Cuando use pytest, instale el complemento pytest-cov a través de pip install pytest-pycov y agregue algunos interruptores para generar una salida detallada del código no probado (Listado 6.13).

Listado 6.13. Usando pytest y cobertura


 $ pytest --cov=gnocchiclient gnocchiclient/tests/unit ---------- coverage: platform darwin, python 3.6.4-final-0 ----------- Name Stmts Miss Branch BrPart Cover --------------------------- gnocchiclient/__init__.py 0 0 0 0 100% gnocchiclient/auth.py 51 23 6 0 49% gnocchiclient/benchmark.py 175 175 36 0 0% --snip-- --------------------------- TOTAL 2040 1868 424 6 8% === passed in 5.00 seconds === 
La opción --cov habilita la salida del informe de cobertura al final de la prueba. Debe pasar el nombre del paquete como argumento para que el complemento filtre correctamente el informe. La salida contendrá líneas de código que no se han ejecutado, lo que significa que no se han probado. Todo lo que le queda es abrir el editor y escribir una prueba para este código.

El módulo de cobertura es aún mejor: le permite generar informes claros en formato HTML. Simplemente agregue -–cov-report-html y las páginas HTML aparecerán en el directorio htmlcov desde donde ejecuta el comando. Cada página mostrará qué partes del código fuente se estaban ejecutando o no.

Si desea ir más allá, use –-cover-fail-under-COVER_MIN_PERCENTAGE, lo que hará que el conjunto de pruebas falle si no cubre el porcentaje mínimo de código. Aunque un gran porcentaje de cobertura es un buen objetivo, y las herramientas de prueba son útiles para obtener información sobre el estado de la cobertura de prueba, el porcentaje en sí mismo no es muy informativo. La Figura 6.1 muestra un ejemplo de informe de cobertura que muestra el porcentaje de cobertura.

Por ejemplo, cubrir el código con pruebas al 100% es un objetivo digno, pero esto no significa necesariamente que el código se haya probado completamente. Este valor solo muestra que se cumplen todas las líneas de código del programa, pero no indica que se hayan probado todas las condiciones.

Vale la pena usar la información de cobertura para expandir el conjunto de pruebas y crearlos para el código que no se ejecuta. Esto simplifica el soporte del proyecto y mejora la calidad general del código.

imagen


Sobre el autor


Julien Danju ha estado pirateando software gratuito durante unos veinte años, y ha estado desarrollando programas Python durante casi doce años. Actualmente lidera el equipo de diseño de la plataforma de nube distribuida basada en OpenStack, que posee la mayor base de datos de código abierto existente de Python, con aproximadamente dos millones y medio de líneas de código. Antes de desarrollar servicios en la nube, Julien creó el administrador de ventanas y contribuyó al desarrollo de muchos proyectos, como Debian y GNU Emacs.

Sobre el editor de ciencias


Mike Driscoll ha estado programando en Python por más de una década. Durante mucho tiempo, escribió sobre Python en The Mouse vs. El pitón . Autor de varios libros de Python: Python 101, Python Interviews e ReportLab: Procesamiento de PDF con Python. Puedes encontrar a Mike en Twitter y en GitHub: @driscollis.

»Se puede encontrar más información sobre el libro en el sitio web del editor
» Contenidos
» Extracto

Cupón de 25% de descuento para vendedores ambulantes - Python

Tras el pago de la versión en papel del libro, se envía un libro electrónico por correo electrónico.

Source: https://habr.com/ru/post/466027/


All Articles