[Python] De multinúcleo síncrono a asíncrono: optimización del rendimiento del código de prueba, aceleración del desarrollo y la verificación de aplicaciones

Tabla de contenido

Capacidades de simulación de pila de prueba de uso común en el trabajo de prueba

Escenario de aplicación

talón de prueba simple

Extensión http.server: implemente un servidor de archivos estático con una línea de comando

Optimización del rendimiento: uso de respuestas asincrónicas

respuesta asíncrona

Se puede optimizar: aproveche los múltiples núcleos

gunicornio

instalar gunicorn

Iniciar el servicio con gunicorn

Optimización del rendimiento: use caché (functools.lru_cache).

simulacro en prueba unitaria

Prueba de unidad de Python.mock

Resumir

Método de adquisición de datos


Capacidades de simulación de pila de prueba de uso común en el trabajo de prueba

Durante nuestro trabajo de prueba, es posible que nos encontremos con escenarios en los que se completa el desarrollo del servicio front-end y el servicio dependiente todavía está en desarrollo; o necesitamos realizar una prueba de estrés de un determinado servicio y los componentes dependientes de este servicio (como el entorno de prueba) no puede admitir MQescenarios de acceso simultáneo. En este momento, es posible que necesitemos un servicio para reemplazar estos componentes o servicios dependientes del entorno de prueba, y este es el protagonista de este artículo: el stub de prueba .

Un stub de prueba puede entenderse como un proxy que puede usarse para simular dependencias externas en la aplicación, como bases de datos, servicios de red u otras API, que pueden ayudarnos a aislar diferentes partes de la aplicación durante el desarrollo y las pruebas, para que la prueba Más fiable y repetible.

Escenario de aplicación

Los talones de prueba se utilizan generalmente en los siguientes escenarios:

Escenas Razones y propósitos para usar talones de prueba
prueba de unidad Aísle la interacción del código bajo prueba con otros componentes o dependencias externas, de modo que el código bajo prueba pueda probarse sin considerar otras partes.
Pruebas de integración Cuando ciertos componentes no están implementados o no están disponibles, use fragmentos de prueba para simular estos componentes para que las pruebas de integración puedan continuar.
Pruebas de rendimiento Genere rápidamente una gran carga y una gran cantidad de solicitudes simultáneas, y evalúe el rendimiento del sistema en condiciones de alta carga.
Pruebas de inyección y recuperación de fallas Simule fallas (como fallas en la red, tiempo de inactividad del servicio, etc.) para verificar el comportamiento y la resiliencia del sistema cuando se encuentran fallas.
Pruebas de API Use stubs de prueba para simular la respuesta de la API para que el desarrollo y las pruebas del cliente puedan realizarse antes de que se complete la implementación de la API.
Pruebas de servicios de terceros Evite la interacción con servicios reales de terceros durante la fase de desarrollo y prueba, lo que reduce los costos adicionales y los resultados de prueba inestables. Los fragmentos de prueba se utilizan para simular estos servicios de terceros, lo que permite realizar pruebas sin afectar los servicios reales.

Este artículo seleccionará varios escenarios de uso común para presentar el desarrollo y la optimización de los stubs de prueba paso a paso.

talón de prueba simple

Si no es conveniente instalar otras bibliotecas en el entorno de prueba, podemos usar un http.servermódulo de módulo en la biblioteca estándar de Python para crear un código auxiliar de prueba de solicitud HTTP simple.

# simple_stub.py
# 测试桩接收GET请求并返回JSON数据。
import json
from http.server import BaseHTTPRequestHandler, HTTPServer

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        content = json.dumps({"message": "Hello, this is a test stub!"}).encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", f"{len(content)}")
        self.end_headers()
        self.wfile.write(content)


if __name__ == "__main__":
    server_address = ("", 8000)
    httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
    print("Test stub is running on port 8000")
    httpd.serve_forever()

Ejecute el código anterior, verá que el código auxiliar de prueba está escuchando en el puerto 8000. Puede usar un navegador o curlun comando para acceder  http://localhost:8000y recibirá  {'message': 'Hello, this is a test stub!'}una respuesta.

http.serverExtensión: implementar un servidor de archivos estático con una línea de comando

http.serverEl módulo se puede usar como un servidor de archivos estático simple para desarrollar y probar sitios web estáticos localmente. Para iniciar el servidor de archivos estáticos, ejecute el siguiente comando en la línea de comandos:

python3 -m http.server [port]

donde [puerto] es un número de puerto opcional, cuyo valor predeterminado es 8000 cuando no se pasa. El servidor servirá archivos estáticos en el directorio actual.

Si se ejecuta en la carpeta de registro python -m http.server, se puede acceder a los archivos de esta carpeta y al contenido de las subcarpetas en un navegador web:

imagen

Nota: http.server Utilizado principalmente para desarrollo y pruebas, el rendimiento y la seguridad no cumplen las condiciones para la implementación en entornos de producción

Optimización del rendimiento: uso de respuestas asincrónicas

Implementamos un trozo de prueba simple arriba, pero en aplicaciones reales, es posible que necesitemos un mayor rendimiento y funciones más complejas.

respuesta asíncrona

En el caso de solo los mismos recursos, un servicio de este tipo con E/S de red, utilizando un método asíncrono, sin duda puede hacer un uso más efectivo de los recursos del sistema.

Cuando se trata de marcos http asincrónicos, el más popular es que solo se necesitan dos pasos FastAPIpara FastAPIimplementar las funciones anteriores.

Primero, instale FastAPI y Uvicorn:

pip install fastapi uvicorn

A continuación, cree un fastapi_stub.pyarchivo llamado , con el siguiente contenido:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def get_request():
    return {"message": "Hello, this is an optimized test stub!"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Ejecute el código, y este código auxiliar de prueba también está escuchando en el puerto 8000. Podemos usar un navegador u otro cliente HTTP para iniciar una solicitud al trozo de prueba como antes.

Haga clic para ver la introducción a las ventajas de la programación asíncrona

Se puede optimizar: aproveche los múltiples núcleos

Aunque usamos un método asincrónico para mejorar el rendimiento del código de prueba, el código solo se ejecuta en un núcleo de CPU. Si queremos realizar pruebas de estrés de rendimiento, es posible que no pueda cumplir con nuestros requisitos de rendimiento. En este momento, podemos usar  gunicornla biblioteca para aprovechar las ventajas multinúcleo del servidor.

gunicornio

Características y beneficios clave de Gunicorn:

Características y ventajas ilustrar
fácil de usar Gunicorn es fácil de instalar y configurar, y puede integrarse perfectamente con muchos marcos web de Python (como Flask, Django, FastAPI, etc.).
multiprogreso Gunicorn utiliza un modo de trabajo previamente bifurcado, creando múltiples procesos secundarios para manejar solicitudes concurrentes. Esto ayuda a mejorar el rendimiento y la capacidad de respuesta de la aplicación.
compatibilidad Gunicorn sigue la especificación WSGI, lo que significa que se puede usar con cualquier aplicación web de Python que siga la especificación WSGI.
configurabilidad Gunicorn proporciona muchas opciones de configuración, como la cantidad de procesos de trabajo, el tipo de proceso de trabajo (sincrónico, asíncrono), la configuración del tiempo de espera, etc. Esto permite que Gunicorn se configure de manera flexible según las necesidades específicas.
fácil de implementar Gunicorn es muy popular en entornos de producción porque simplifica el proceso de implementación. Gunicorn se puede usar con otras herramientas como Nginx, Supervisor, etc. para administrar y escalar mejor las aplicaciones web.
instalar gunicorn
pip install gunicorn
Iniciar el servicio con gunicorn

Iniciar el servicio:

gunicorn -w 4  fastapi_stub:app 

Como puede ver, el comando anterior inicia 4 procesos de trabajo y también puede usar ps -efel comando para consultar el estado del proceso.

imagen

Algunos parámetros comunes de gunicorn:

parámetro ilustrar
-w, --trabajadores Establezca el número de procesos de trabajo. Ajuste según el número de núcleos de CPU del sistema y las características de carga de la aplicación. El valor predeterminado es 1.
-k, --clase-trabajador Establezca el tipo de proceso de trabajo. Puede ser sync(predeterminado), gevent, , eventletetc. Si utiliza procesos de trabajo asincrónicos, debe instalar la biblioteca correspondiente. Por ejemplo, para aplicaciones FastAPI, puede usar -k uvicorn.workers.UvicornWorker.
-b, --enlazar Establezca la dirección y el puerto al que se vincula el servidor. El formato es address:port. Por ejemplo: -b 0.0.0.0:8000. El valor predeterminado es 127.0.0.1:8000.
--se acabó el tiempo Establezca el tiempo de espera en segundos para los procesos de trabajo. Si el proceso de trabajo no completa la tarea dentro del tiempo especificado, se reiniciará. El valor predeterminado es 30 segundos.
--nivel de registro Establecer el nivel de registro. Puede ser debug, info, warningo error. criticalEl valor predeterminado es info.
--acceso-archivo de registro Establezca la ruta al archivo de registro de acceso. De forma predeterminada, los registros de acceso se enviarán al flujo de errores estándar. Para deshabilitar el registro de acceso, use -. Por ejemplo: --access-logfile -.
--archivo de registro de errores Establece la ruta al archivo de registro de errores. De forma predeterminada, los registros de errores se enviarán al flujo de errores estándar. Para deshabilitar el registro de errores, use -. Por ejemplo: --error-logfile -.
--recargar Utilice esta opción en un entorno de desarrollo y Gunicorn se recargará automáticamente cuando cambie el código de la aplicación. No se recomienda su uso en entornos de producción.
--demonio Utilice esta opción para ejecutar Gunicorn en modo daemon. En este modo, Gunicorn se ejecutará en segundo plano y se desconectará automáticamente al iniciarse.

Gunicorn ofrece muchas otras opciones de configuración que se pueden ajustar a necesidades específicas. Para obtener una lista completa de opciones, puede consultar la documentación oficial de Gunicorn: https://docs.gunicorn.org/en/stable/settings.html.

Optimización del rendimiento: use caché ( functools.lru_cache).

Cuando se trata de tareas repetitivas de cálculo o recuperación de datos. El uso de caché de memoria (como functools.lru_cache de Python) o caché externo (como Redis) para almacenar en caché datos de uso frecuente también puede mejorar en gran medida la eficiencia de los resguardos de prueba.

Suponiendo que nuestra pila de prueba necesita usar funciones que consumen mucho tiempo, como calcular la secuencia de Fibonacci, luego almacenar en caché los resultados y devolverlos directamente cuando se encuentre la misma solicitud la próxima vez en lugar de calcular y luego devolver mejorará en gran medida la tasa de uso de recursos , reduciendo el tiempo de espera para una respuesta .

Si solo devuelve los datos directamente y no hay un trozo de prueba para cálculos complejos, lru_cacheno tiene importancia práctica para su uso.

Aquí hay un ejemplo de un uso más apropiado lru_cache, donde realizaremos cálculos en la secuencia de Fibonacci y almacenaremos en caché los resultados:

from fastapi import FastAPI
from functools import lru_cache

app = FastAPI()

@lru_cache(maxsize=100)
def fibonacci(n: int):
    if n <= 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

@app.get("/fibonacci/{n}")
async def get_fibonacci(n: int):
    result = fibonacci(n)
    return {"result": result}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

En este ejemplo, creamos un código auxiliar de prueba de solicitud HTTP simple usando FastAPI. Definimos una fibonaccifunción llamada , que calcula la sucesión de Fibonacci. Para mejorar el rendimiento, almacenamos functools.lru_cacheen caché esta función.

En la ruta /fibonacci/{n}, llamamos fibonaccia la función y devolvemos el resultado. El acceso a los comandos está disponible  http://localhost:8000/fibonacci/{n}para la depuración.

Cabe señalar que  maxsizeel parámetro es functools.lru_cacheuna opción de configuración del decorador, que representa la capacidad máxima del caché. lru_cacheUsando un diccionario para almacenar entradas de caché, cuando se necesita almacenar en caché un nuevo resultado, verifica el tamaño de caché actual. Si la memoria caché está llena (es decir, alcanzada maxsize), la entrada de la memoria caché utilizada menos recientemente se elimina de acuerdo con la política de LRU. Si maxsizese establece en None, la memoria caché puede crecer sin límite, lo que puede provocar problemas de memoria.

simulacro en prueba unitaria

Prueba de unidad de Python.mock

En Python, el módulo unittest proporciona un submódulo llamado unittest.mock para crear objetos simulados. unittest.mock contiene una clase llamada Mock y un administrador/decorador de contexto llamado parche que se puede usar para reemplazar dependencias en el código bajo prueba.

import requests
from unittest import TestCase
from unittest.mock import patch

# 定义一个函数 get_user_name,它使用 requests.get 发起 HTTP 请求以获取用户名称
def get_user_name(user_id):
    response = requests.get(f"https://api.example.com/users/{user_id}")
    return response.json()["name"]

# 创建一个名为 TestGetUserName 的测试类,它继承自 unittest.TestCase
class TestGetUserName(TestCase):
    # 使用 unittest.mock.patch 装饰器替换 requests.get 函数
    @patch("requests.get")
    # 定义一个名为 test_get_user_name 的测试方法,它接受一个名为 mock_get 的参数
    def test_get_user_name(self, mock_get):
        # 配置 mock_get 的返回值,使其在调用 json 方法时返回一个包含 "name": "Alice" 的字典
        mock_get.return_value.json.return_value = {"name": "Alice"}

        # 调用 get_user_name 函数,并传入 user_id 参数
        user_name = get_user_name(1)

        # 使用 unittest.TestCase 的 assertEqual 方法检查 get_user_name 的返回值是否等于 "Alice"
        self.assertEqual(user_name, "Alice")

        # 使用 unittest.mock.Mock 的 assert_called_with 方法检查 mock_get 是否被正确调用
        mock_get.assert_called_with("https://api.example.com/users/1")

Resumir

Al desarrollar un código de prueba, debemos diseñar el comportamiento del código de prueba de acuerdo con las necesidades reales y las características del servicio de back-end, para que se acerque más al comportamiento del servicio de back-end real y garantizar que los resultados de la prueba tienen una mayor fiabilidad y precisión.

Puede haber otras soluciones de optimización, y las sugerencias son bienvenidas. Espero que este artículo pueda ayudar a todos en su trabajo.

Si crees que no está mal, solo dale me gusta en la esquina inferior derecha, ¡gracias!


Método de adquisición de datos

【Mensaje 777】

Amigos que quieran obtener el código fuente y otros materiales de tutoriales, por favor, dale me gusta + comentario + marcador , ¡triple!

Después de tres veces seguidas , te enviaré mensajes privados uno por uno en el área de comentarios~

Supongo que te gusta

Origin blog.csdn.net/GDYY3721/article/details/132063014
Recomendado
Clasificación