Categorías
Python

Entrevista comunidad Python con Mike Driscoll

 

Tabla de Contenidos

  • Introducción
  • Prueba del Mock API
  • Prueba

  • un servicio que llega a las API Pruebas
  • Skipping que afectó a la real API
  • próximos pasos

A pesar de ser tan útil, APIs externas pueden ser un dolor de prueba. Al llegar a una API real, las pruebas están a merced del servidor externo, lo que puede dar lugar a los siguientes puntos de dolor:

  • El ciclo de solicitud-respuesta puede tardar varios segundos. Esto puede no parecer mucho al principio, pero los compuestos de tiempo con cada prueba. Imagínese llamar a una API 10, 50, o incluso 100 veces cuando se prueba toda su aplicación.
  • La API puede haber límites de ritmo establecido.
  • El servidor API puede ser inalcanzable. Puede que el servidor está en mantenimiento? Tal vez con un error y el equipo de desarrollo está trabajando para conseguir de nuevo funcional> ¿Realmente desea el éxito de sus pruebas a depender de la salud de un servidor que no controlas?

Sus pruebas no deben evaluar si un servidor API se está ejecutando; deberían probar si su código está funcionando como se esperaba.

En el tutorial anterior, se introdujo el concepto de objetos simulados, demostró cómo se podía utilizar para el código de prueba que interactúa con las API externas. Este tutorial se basa en los mismos temas, pero aquí se puede caminar a través de la forma de construir realmente un servidor simulacro en lugar de burlarse de las API. Con un servidor maqueta en su lugar, se pueden realizar pruebas de extremo a extremo. Puede utilizar la aplicación y obtener retroalimentación real desde el servidor simulacro en tiempo real.

Cuando termine de trabajar a través de los siguientes ejemplos, se han programado un simulacro de servidor básico y dos pruebas – uno que utiliza el servidor API real y uno que utiliza el servidor simulado. Ambas pruebas tendrán acceso al mismo servicio, una API que recupera una lista de usuarios.

NOTA: Este tutorial usa Python v3.5.1.

Introducción

Inicio siguiendo la sección de primeros pasos de la entrada anterior. O agarrar el código desde el repositorio. Asegúrese de que los pases de prueba antes de pasar:

$ nosetests --verbosity=2 project
test_todos.test_request_response ... ok

----------------------------------------------------------------------
Ran 1 test in 1.029s

OK
Prueba

el Mock API

Con la puesta en marcha completa, puede programar su servidor simulado. Escribir una prueba que describe el comportamiento: Proyecto

/ pruebas / test_mock_server.py

# Third-party imports...
from nose.tools import assert_true
import requests

def test_request_response():
url = 'http://localhost:{port}/users'.format(port=mock_server_port)

# Send a request to the mock API server and store the response.
response = requests.get(url)

# Confirm that the request-response cycle completed successfully.
assert_true(response.ok)

Aviso que comienza mirando casi idéntica a la prueba API real. La URL ha cambiado y ahora está apuntando a un punto final de la API en localhost donde el servidor simulacro se ejecutará.

Aquí es cómo crear un servidor de simulacro en Python: Proyecto

/ pruebas / test_mock_server.py

# Standard library imports...
from http.server import BaseHTTPRequestHandler, HTTPServer
import socket
from threading import Thread

# Third-party imports...
from nose.tools import assert_true
import requests

class MockServerRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
# Process an HTTP GET request and return a response with an HTTP 200 status.
self.send_response(requests.codes.ok)
self.end_headers()
return

def get_free_port():
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
s.bind(('localhost', 0))
address, port = s.getsockname()
s.close()
return port

class TestMockServer(object):
@classmethod
def setup_class(cls):
# Configure mock server.
cls.mock_server_port = get_free_port()
cls.mock_server = HTTPServer(('localhost', cls.mock_server_port), MockServerRequestHandler)

# Start running mock server in a separate thread.
# Daemon threads automatically shut down when the main process exits.
cls.mock_server_thread = Thread(target=cls.mock_server.serve_forever)
cls.mock_server_thread.setDaemon(True)
cls.mock_server_thread.start()

def test_request_response(self):
url = 'http://localhost:{port}/users'.format(port=self.mock_server_port)

# Send a request to the mock API server and store the response.
response = requests.get(url)

# Confirm that the request-response cycle completed successfully.
print(response)
assert_true(response.ok)

En primer lugar, crear una subclase de BaseHTTPRequestHandler. Esta clase captura la petición y construye la respuesta al cambio. Reemplazar la función do_GET () para elaborar la respuesta para una solicitud HTTP GET. En este caso, basta con devolver un estado OK. A continuación, escribir una función para obtener un número de puerto disponible para el servidor simulacro de uso.

El siguiente bloque de código en realidad configura el servidor. Aviso cómo el código crea una instancia HTTPServer y lo pasa un número de puerto y un controlador. A continuación, crear un hilo, para que el servidor se puede ejecutar de forma asincrónica y el hilo principal del programa puede comunicarse con él. Hacer que el hilo de un demonio, que le dice al hilo de parada cuando las principales salidas del programa. Por último, iniciar el hilo para servir al servidor de simulacro para siempre (hasta que terminen las pruebas).

Crear una clase de prueba y mover la función de prueba a la misma. Debe añadir un método adicional para garantizar que el servidor simulacro se puso en marcha antes que cualquiera de las pruebas realizadas. Tenga en cuenta que este nuevo código vive dentro de una función especial a nivel de clase, setup_class ().

Ejecute las pruebas y verlos pasar:

$ nosetests --verbosity=2 project

a probar un servicio que llega a la API

Es posible que desee llamar a más de un punto final de la API en su código. A medida que el diseño de su aplicación, es probable que crear funciones de servicio para enviar peticiones a una API y luego procesar las respuestas de alguna manera. Tal vez va a almacenar los datos de respuesta en una base de datos. O se pasa los datos a una interfaz de usuario.

Refactor su código para tirar de la URL base API hardcoded en una constante. Añadir esta variable a un archivo constants.py : Proyecto

/ constants.py

BASE_URL = 'http://jsonplaceholder.typicode.com'

A continuación, encapsulan la lógica para recuperar los usuarios de la API en una función. Aviso cómo las nuevas direcciones URL puede ser creado por unirse a una ruta URL a la base.

proyecto / services.py

# Standard library imports...
from urllib.parse import urljoin

# Third-party imports...
import requests

# Local imports...
from project.constants import BASE_URL

USERS_URL = urljoin(BASE_URL, 'users')

def get_users():
response = requests.get(USERS_URL)
if response.ok:
return response
else:
return None

Mover el código del servidor maqueta del archivo de función para un nuevo archivo de Python, por lo que fácilmente se puede reutilizar. Añadir lógica condicional para el controlador de solicitudes para comprobar qué API punto final de la petición HTTP es la orientación. Reforzar la respuesta mediante la adición de un poco de información de cabecera y una carga útil sencilla respuesta básica. La creación del servidor y puntapié inicial de código se pueden encapsular en un método de conveniencia, start_mock_server (). proyecto

/ pruebas / mocks.py

# Standard library imports...
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import re
import socket
from threading import Thread

# Third-party imports...
import requests

class MockServerRequestHandler(BaseHTTPRequestHandler):
USERS_PATTERN = re.compile(r'/users')

def do_GET(self):
if re.search(self.USERS_PATTERN, self.path):
# Add response status code.
self.send_response(requests.codes.ok)

# Add response headers.
self.send_header('Content-Type', 'application/json; charset=utf-8')
self.end_headers()

# Add response content.
response_content = json.dumps([])
self.wfile.write(response_content.encode('utf-8'))
return

def get_free_port():
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
s.bind(('localhost', 0))
address, port = s.getsockname()
s.close()
return port

def start_mock_server(port):
mock_server = HTTPServer(('localhost', port), MockServerRequestHandler)
mock_server_thread = Thread(target=mock_server.serve_forever)
mock_server_thread.setDaemon(True)
mock_server_thread.start()

Con los cambios en la lógica completado, alteran las pruebas a utilizar la nueva función de servicio. Actualizar las pruebas para comprobar el aumento de la información que se pasa de nuevo desde el servidor. proyecto

/ pruebas / test_real_server.py proyecto

# Third-party imports...
from nose.tools import assert_dict_contains_subset, assert_is_instance, assert_true

# Local imports...
from project.services import get_users

def test_request_response():
response = get_users()

assert_dict_contains_subset({'Content-Type': 'application/json; charset=utf-8'}, response.headers)
assert_true(response.ok)
assert_is_instance(response.json(), list)

/ pruebas / test_mock_server.py

# Third-party imports...
from unittest.mock import patch
from nose.tools import assert_dict_contains_subset, assert_list_equal, assert_true

# Local imports...
from project.services import get_users
from project.tests.mocks import get_free_port, start_mock_server

class TestMockServer(object):
@classmethod
def setup_class(cls):
cls.mock_server_port = get_free_port()
start_mock_server(cls.mock_server_port)

def test_request_response(self):
mock_users_url = 'http://localhost:{port}/users'.format(port=self.mock_server_port)

# Patch USERS_URL so that the service uses the mock server URL instead of the real URL.
with patch.dict('project.services.__dict__', {'USERS_URL': mock_users_url}):
response = get_users()

assert_dict_contains_subset({'Content-Type': 'application/json; charset=utf-8'}, response.headers)
assert_true(response.ok)
assert_list_equal(response.json(), [])

Aviso una nueva técnica que se utiliza en el test_ simulacro _server.py código. La línea de respuesta = get_users () se envuelve con una función patch.dict () de la biblioteca mock .

¿Qué hace esta afirmación?

Recuerde, ha movido la función requests.get () de la lógica de función para los get_users función de servicio (). Internamente, get_users () llama requests.get () utilizando la variable USERS_URL. La función patch.dict () reemplaza temporalmente el valor de la variable USERS_URL. De hecho, lo hace sólo en el ámbito de la sentencia with. Después de que las carreras de código, la variable USERS_URL se restaura a su valor original. Este código parches la URL a utilizar la dirección del servidor simulado.

Ejecutar las pruebas y los ven pasar.

$ nosetests --verbosity=2
test_mock_server.TestMockServer.test_request_response ... 127.0.0.1 - - [05/Jul/2016 20:45:30] "GET /users HTTP/1.1" 200 -
ok
test_real_server.test_request_response ... ok
test_todos.test_request_response ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.871s

OK

Saltarse Pruebas de que afectó a la API real

Comenzamos este tutorial que describe los méritos de la prueba de un servidor de simulacro en lugar de uno real, sin embargo, su código prueba actualmente ambos. ¿Cómo se configura las pruebas de ignorar el servidor real? La biblioteca de Python ‘unittest’ ofrece varias funciones que le permiten saltarse las pruebas. Puede utilizar la función de salto condicional ‘skipIf’ junto con una variable de entorno para alternar las pruebas de servidores reales dentro y fuera. En el siguiente ejemplo, se pasa un nombre de etiqueta que debe ser ignorado:

$ export SKIP_TAGS=real

proyecto / constants.py proyecto

# Standard-library imports...
import os

BASE_URL = 'http://jsonplaceholder.typicode.com'
SKIP_TAGS = os.getenv('SKIP_TAGS', '').split()

/ pruebas / test_real_server.py

# Standard library imports...
from unittest import skipIf

# Third-party imports...
from nose.tools import assert_dict_contains_subset, assert_is_instance, assert_true

# Local imports...
from project.constants import SKIP_TAGS
from project.services import get_users

@skipIf('real' in SKIP_TAGS, 'Skipping tests that hit the real API server.')
def test_request_response():
response = get_users()

assert_dict_contains_subset({'Content-Type': 'application/json; charset=utf-8'}, response.headers)
assert_true(response.ok)
assert_is_instance(response.json(), list)

ejecutar las pruebas y prestar atención a cómo el verdadero prueba del servidor se ignora:

$ nosetests --verbosity=2 project
test_mock_server.TestMockServer.test_request_response ... 127.0.0.1 - - [05/Jul/2016 20:52:18] "GET /users HTTP/1.1" 200 -
ok
test_real_server.test_request_response ... SKIP: Skipping tests that hit the real API server.
test_todos.test_request_response ... ok

----------------------------------------------------------------------
Ran 3 tests in 1.196s

OK (SKIP=1)

próximos pasos

Ahora que ha creado un servidor de simulacro para probar sus llamadas a la API externos, puede aplicar este conocimiento a sus propios proyectos. Construir sobre las pruebas simples creadas aquí. Ampliar la funcionalidad del controlador para imitar el comportamiento de la API reales más de cerca.

Pruebe los siguientes ejercicios para subir de nivel:

  • devolver una respuesta con un estado de HTTP 404 (no encontrado) si se envía una petición con una ruta desconocida.
  • devolver una respuesta con un estado de HTTP 405 (Método no permitido) si se envía una solicitud con un método que no está permitido (POST, DELETE, UPDATE). datos de usuario real
  • cambio de una solicitud válida a / usuarios.
  • escribir pruebas para capturar esos escenarios.

Coge el código desde el repositorio.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *