X
BLOG

Desarrolle el rendimiento de la API desde cero: uso de pruebas unitarias para comparar el rendimiento de los componentes de la API

Desarrolle el rendimiento de la API desde cero: uso de pruebas unitarias para comparar el rendimiento de los componentes de la API Tiempo de leer: 7 minutos
La prueba de rendimiento a nivel de unidad es una práctica poderosa que puede lograr fácilmente con una estrecha integración entre sus herramientas de prueba de rendimiento y de prueba de unidad, para que pueda comprender el rendimiento de los componentes que está integrando en su aplicación de destino.

Las API se concibieron originalmente como herramientas de integración básicas que permitían que aplicaciones dispares intercambiaran datos, pero se han convertido en un pegamento crítico que une múltiples procesos en una sola aplicación. Las aplicaciones modernas están agregando y consumiendo API a un ritmo asombroso para lograr los objetivos comerciales. De hecho, la lógica empresarial de la mayoría de las aplicaciones modernas ahora se basa en alguna combinación de API y bibliotecas de terceros, lo que significa que el rendimiento de las transacciones de un extremo a otro depende en gran medida del rendimiento de las API y los componentes que aprovecha la aplicación. .

Dada su capacidad para hacer o deshacer los objetivos de rendimiento, los componentes clave de la aplicación deben someterse a una rigurosa evaluación de rendimiento como parte integral del proceso de aceptación. Cuanto antes comprenda las capacidades de rendimiento de los componentes clave de una aplicación, más fácil será solucionar los problemas y más eficazmente podrá asegurarse de que la aplicación integrada cumplirá con sus requisitos de rendimiento.

Pruebas unitarias

El uso de pruebas unitarias para evaluar el rendimiento de los componentes ofrece una serie de ventajas, entre las que se incluyen las siguientes: 

  • Las pruebas unitarias ofrecen una forma flexible pero estandarizada de probar a nivel de componente.
  • Las pruebas unitarias se comprenden bien y se utilizan ampliamente en entornos de desarrollo.
  • Normalmente, las pruebas unitarias requieren solo una fracción de los recursos de hardware necesarios para probar toda la aplicación. Esto significa que puede probar los componentes a un nivel máximo de "estrés" proyectado (consulte la imagen a continuación) antes y con más frecuencia con los recursos de hardware disponibles en los entornos de desarrollo.

Fig.1: Proporciones aproximadas del nivel de carga y la duración de la prueba de escenarios típicos de prueba de rendimiento

A pesar de estos beneficios, las pruebas de rendimiento a nivel de unidad a menudo se pasan por alto porque la mayoría de las herramientas de prueba de unidad carecen de las capacidades que se encuentran comúnmente en las herramientas de prueba de rendimiento dedicadas (por ejemplo, la capacidad de configurar y ejecutar varias configuraciones de prueba de rendimiento, monitoreo de los recursos del sistema y de la aplicación durante la prueba, recopilación y análisis de resultados de pruebas de rendimiento, etc.).

Pero puede obtener lo mejor de ambos mundos ejecutando pruebas a nivel de unidad con herramientas tradicionales de prueba de rendimiento. A continuación, también le daré una estrategia para medir y comparar el rendimiento de los componentes que su equipo podría integrar en su aplicación de destino.

Establecer un flujo de trabajo de evaluación comparativa de componentes

La necesidad de realizar evaluaciones comparativas a nivel de componentes en entornos de desarrollo puede surgir en diferentes etapas del ciclo de vida del software y está impulsada por las siguientes preguntas:

  • ¿Cuál de los componentes de terceros disponibles no solo satisface los requisitos funcionales, sino que también tiene el mejor rendimiento? ¿Debo usar el componente A, B, C, etc. o implementar uno nuevo? Estas preguntas suelen surgir en las etapas de diseño y creación de prototipos.
  • ¿Cuál de las implementaciones de código alternativas es la más óptima desde la perspectiva del rendimiento? Estas preguntas suelen ocurrir durante la etapa de desarrollo y están relacionadas con el código desarrollado internamente.

Una evaluación comparativa de componentes correctamente configurada y ejecutada puede ayudar a responder estas preguntas. Un flujo de trabajo de referencia de componentes típico (que se muestra en la Fig.2 a continuación) consta de:

  1. Creación de pruebas unitarias para los componentes comparados.
  2. Selección de parámetros de rendimiento de referencia (son los mismos para todos los componentes).
  3. Ejecución de las pruebas de desempeño.
  4. Comparar los perfiles de rendimiento de diferentes componentes.

Esto se ilustra en la Figura 2 a continuación:

Fig.2: Flujo de trabajo típico de evaluación comparativa de componentes

Para ver un ejemplo concreto, veamos cómo comparar el rendimiento de cuatro componentes del analizador JSON: Jackson (transmisión), JsonSmart, Gson y FlexJson. Aquí, usaremos JUnit como el marco de prueba unitario y Parasoft Load Test como la aplicación de prueba de carga, que es parte de Parasoft SOAtest. La misma estrategia se puede aplicar con otros marcos de prueba unitarios y aplicaciones de prueba de carga.

Creación de pruebas unitarias para los componentes comparados

La prueba JUnit debería invocar el componente como lo haría la aplicación de destino. Esto se aplica a la configuración del componente, la elección de los métodos de la API del componente y los valores y rangos de los argumentos del método.

El nivel deseado de verificación de resultados depende de la naturaleza de las pruebas. Como regla general, haría una verificación de resultados de pruebas unitarias más extensa para las pruebas de confiabilidad, pero realizaría un nivel más básico de verificación para las pruebas de eficiencia (ya que la lógica de verificación de resultados 'pesados' puede sesgar la imagen del rendimiento). Por otro lado, es importante realizar al menos alguna verificación para garantizar que el componente se configure e invoque correctamente.

En nuestro punto de referencia, el contenido JSON se carga desde un archivo al comienzo de la prueba de rendimiento y se almacena en la memoria caché. El archivo JSON tiene un tamaño de 225 KB y contiene 215 objetos de descripción de cuenta de nivel superior. Cada objeto de cuenta tiene un par de nombre / valor de "saldo":

{

    "id": "54b98c2b7b3bd64aae699040",

    "índice": 214,

    "guid": "565c44b0-9e6d-4b8e-819c-48aa4dd9d7c2",

    "balance": "$ 3,809.46",

...

}

El nivel básico de verificación de la funcionalidad del componente se implementa de la siguiente manera: la prueba JUnit invoca la API del analizador para encontrar todos los elementos de "saldo" dentro del contenido JSON y calcular el saldo total de todos los objetos de la cuenta en el documento JSON. Luego, el saldo calculado se compara con el valor esperado:

público clase JacksonStreamParserTest Se extiende Caso de prueba {

    @Prueba

    público vacío testParser () tiros IOException {       

        flotar saldo = 0;

        JsonFactory jsonFactory = Un nuevo JsonFactory();

        String json = JsonContentProvider.getJsonContent();

        JsonParser jp = jsonFactory.createJsonParser(json);           

        mientras (jp.nextToken ()! = nulo) {               

            String fieldname = jp.getCurrentName ();

            if (nombre de campo! = nulo && nombre del campo.iguales("equilibrio")) {

                jp.nextToken ();                   

                balance + = TestUtil.parseCurrencyStr(jp.getText ());  

            }

        }

        TestUtil.AsertBalance(equilibrio);       

    }

}

Debido a que estamos comparando analizadores por eficiencia, usamos la verificación de recursos livianos para evitar distorsionar la imagen del rendimiento. Si opta por realizar una verificación importante de los resultados, puede compensarla de la misma manera que nosotros compensamos el consumo de recursos del marco de pruebas de rendimiento, que se describe a continuación. Sin embargo, esto requerirá una preparación más elaborada del escenario de referencia.

Compensación por el marco de prueba

Debido a que estamos comparando el rendimiento de los componentes que se ejecutan en el mismo proceso que la aplicación de prueba de rendimiento del contenedor, necesitamos separar la parte de los recursos de nivel de sistema y de aplicación consumidos por esta aplicación y el marco JUnit de la parte consumida por el componente en sí. Para estimar esta participación, podemos ejecutar un escenario de prueba de carga de referencia con una prueba JUnit vacía:

público clase Prueba de línea de base Se extiende Caso de prueba {

    @Prueba

    público vacío testBaseline () {               

    }

}

Si los niveles de utilización de recursos del escenario de referencia no son despreciables, debemos restar estos niveles de los niveles de las ejecuciones de referencia de componentes para compensar la proporción de recursos consumidos por el marco de prueba.

Seleccionar y configurar parámetros de rendimiento de referencia

La configuración del entorno de prueba debe emular los parámetros principales del entorno de destino, como el sistema operativo, la versión de JVM, las opciones de JVM como las opciones de GC, el modo de servidor, etc. Puede que no siempre sea posible o práctico reproducir todos los parámetros de implementación en el entorno de prueba; sin embargo, cuanto más cerca esté, menos posibilidades habrá de que el rendimiento del componente durante la prueba sea diferente al del entorno de destino.

Comprender la concurrencia, la intensidad y la duración de la prueba

Los principales parámetros de prueba de rendimiento que determinan las condiciones bajo las cuales se probarán los componentes son nivel de carga (definido como intensidad de carga y  simultaneidad de carga) y duración de la carga (vea la Fig. 3 a continuación).

Para dar forma a estos parámetros de prueba de rendimiento genéricos en formas concretas, comience por examinar los requisitos de rendimiento de la aplicación de destino en la que prevé utilizar este componente. Los requisitos de rendimiento a nivel de aplicación pueden proporcionar características concretas o aproximadas del nivel de carga al que debe someterse el componente. Sin embargo, traducir los requisitos de rendimiento de las aplicaciones en requisitos de rendimiento de los componentes presenta múltiples desafíos. Por ejemplo, si el requisito de rendimiento de la aplicación establece que debe responder en M milisegundos a un nivel de carga de N usuarios o solicitudes por segundo, ¿cómo se traduce esto en requisitos de rendimiento para un componente que forma parte de esta aplicación? ¿Cuántas solicitudes a un componente específico generará una solicitud a la aplicación?

Si hay una versión anterior de la aplicación, puede hacer una conjetura al rastrear algunas llamadas a nivel de aplicación o al examinar las estadísticas de rastreo de llamadas recopiladas por una herramienta APM (Application Performance Management). Si ninguna de estas opciones está disponible, la respuesta puede provenir de examinar el diseño de la aplicación.

Si los parámetros de carga de los componentes no se pueden deducir de las especificaciones de rendimiento de la aplicación de destino o si dicha especificación no está disponible, entonces una opción alternativa para un punto de referencia es ejecutar el nivel de carga máximo que se puede lograr en el hardware disponible para la prueba. Sin embargo, el riesgo involucrado en este enfoque, cuando se toma sin tener en cuenta los niveles de carga esperados, es que los resultados de la evaluación comparativa pueden no ser relevantes en el contexto de la aplicación de destino.

En cualquier caso, pero particularmente para los puntos de referencia donde los niveles de carga son impulsados ​​por los límites de rendimiento del hardware disponible para la prueba, tenga en cuenta cómo la sobreutilización de recursos afecta los resultados de la prueba. Por ejemplo, al comparar los tiempos de ejecución de los componentes, generalmente es recomendable permanecer por debajo del nivel de utilización de CPU del 75-80%. Aumentar la utilización por encima de ese nivel puede afectar negativamente la precisión de la medición del tiempo de ejecución de la prueba y puede distorsionar los resultados de la prueba.

A menudo, el parámetro de prueba de rendimiento agregado 'nivel de carga' no se separa explícitamente en sus partes principales: intensidad y simultaneidad (ver Fig. 3). Esto puede dar lugar a una configuración de prueba de rendimiento inadecuada.

Fig.3: Los principales parámetros de la prueba de rendimiento: intensidad, simultaneidad y duración

Intensidad de carga es la tasa de solicitudes o invocaciones de prueba a la que se probará el componente. En una aplicación de prueba de rendimiento, la intensidad de carga se puede configurar directamente estableciendo los niveles de acierto / prueba por segundo / minuto / hora de un escenario de prueba de rendimiento. También se puede configurar indirectamente como una combinación del número de usuarios virtuales y el tiempo de reflexión del usuario virtual.

Simultaneidad de carga (en nuestro contexto) puede describirse como el grado de paralelismo con el que se aplica una carga de una determinada intensidad. El nivel de simultaneidad se puede configurar por el número de usuarios virtuales o subprocesos en un escenario de prueba de carga. Establecer un nivel de simultaneidad apropiado es esencial cuando se prueban posibles condiciones de carrera y problemas de rendimiento debido a la contención de subprocesos y el acceso a recursos compartidos, así como para medir la huella de memoria de múltiples instancias del componente.

Duración del exámen para una prueba comparativa de componentes depende de los objetivos de la prueba. Cuando se aborda desde el punto de vista matemático, uno de los principales factores para determinar la duración de la prueba es la significación estadística del conjunto de datos de la prueba de carga. Sin embargo, este tipo de enfoque puede resultar demasiado complicado para fines prácticos cotidianos. 

Para leer el resto del blog, vea el documento técnico aquí.

Obtenga más información sobre las soluciones para automatizar las pruebas de API y las pruebas de rendimiento. 

Escrito por

Sergei Baranov

Sergei es un ingeniero de software principal en Parasoft, y se centra en las pruebas de carga y rendimiento dentro de Parasoft SOAtest.

Reciba las últimas noticias y recursos sobre pruebas de software en su bandeja de entrada.

Prueba Parasoft