DEV Community

GERALD DEYBI ZEVALLOS PINTO
GERALD DEYBI ZEVALLOS PINTO

Posted on

Pruebas de carga de una API .NET con k6 y GitHub Actions

Introducción

Que una API responda bien a un usuario no garantiza que aguante a muchos al mismo tiempo. Para eso existen las pruebas de carga: simulan usuarios simultáneos y miden tiempos de respuesta y errores.

En este artículo uso k6, una herramienta open source de Grafana Labs, para hacer una prueba de carga a una API REST en .NET 8. La prueba se ejecuta sola en GitHub Actions en cada cambio y el reporte se publica en la nube con GitHub Pages.

¿Por qué k6?

  • Las pruebas se escriben en JavaScript, así que son fáciles de leer y versionar junto al código.
  • Es un solo binario, rápido y liviano: corre sin problema dentro de un runner de CI.
  • Permite definir thresholds (umbrales): si el rendimiento no cumple, k6 termina con error y el pipeline falla.
  • Genera un dashboard web en HTML con gráficos de la ejecución.

La API y el repositorio público

La aplicación es Banco API, una Minimal API en .NET 8 con endpoints para listar, crear, depositar, retirar y eliminar cuentas bancarias (datos en memoria).

Repositorio público del equipo: github.com/viccarretero/banco-api-pruebas. Las pruebas funcionales de la misma API se hacen con Newman; eso lo explica mi compañero Victor Carretero en su artículo.

El script de carga

El archivo tests/k6/carga.js define cómo crece la carga y qué se considera aceptable:

export const options = {
  stages: \[
    { duration: '30s', target: 20 }, // subida: de 0 a 20 usuarios
    { duration: '1m', target: 20 },  // carga sostenida: 20 usuarios
    { duration: '15s', target: 0 },  // bajada
  ],
  thresholds: {
    http\_req\_duration: \['p(95)<500'], // el 95 % de las peticiones en menos de 500 ms
    http\_req\_failed: \['rate<0.01'],   // menos del 1 % de errores
    checks: \['rate>0.99'],            // más del 99 % de validaciones correctas
  },
};
Enter fullscreen mode Exit fullscreen mode

Cada usuario virtual (VU) repite un flujo realista dividido en dos grupos: consultar cuentas y hacer operaciones. Con check se valida que cada respuesta sea correcta, no solo rápida:

group('Operaciones', () => {
  const nueva = http.post(`${BASE\_URL}/api/cuentas`,
    JSON.stringify({ titular: `Usuario ${\_\_VU}-${\_\_ITER}`, saldoInicial: 100 }), JSON\_HEADERS);
  check(nueva, { 'crear: status 201': (r) => r.status === 201 });
  const id = nueva.json('id');

  const deposito = http.post(`${BASE\_URL}/api/cuentas/${id}/depositos`, JSON.stringify({ monto: 50 }), JSON\_HEADERS);
  check(deposito, { 'depositar: saldo 150': (r) => r.json('saldo') === 150 });

  const retiro = http.post(`${BASE\_URL}/api/cuentas/${id}/retiros`, JSON.stringify({ monto: 30 }), JSON\_HEADERS);
  check(retiro, { 'retirar: saldo 120': (r) => r.json('saldo') === 120 });
});
sleep(1);
Enter fullscreen mode Exit fullscreen mode

Así cada usuario crea su propia cuenta, deposita 50 y retira 30, y se comprueba que el saldo final sea exacto aunque haya 20 usuarios operando a la vez.

Automatización con GitHub Actions

En el workflow .github/workflows/pruebas-api.yml, el job de k6 levanta la API dentro del runner, instala k6 con la acción oficial de Grafana y ejecuta el script exportando el dashboard HTML:

- name: Instalar k6
  uses: grafana/setup-k6-action@v1

- name: Ejecutar la prueba de carga
  env:
    BASE\_URL: http://localhost:5000
    K6\_WEB\_DASHBOARD: 'true'
    K6\_WEB\_DASHBOARD\_EXPORT: site/k6/index.html
  run: |
    mkdir -p site/k6
    k6 run --summary-export=site/k6/resumen.json tests/k6/carga.js
Enter fullscreen mode Exit fullscreen mode

El job de k6 corre en paralelo con el de Newman, y al final un último job publica ambos reportes en GitHub Pages.

Resultados y publicación en la nube

Reporte publicado: viccarretero.github.io/banco-api-pruebas/k6

En la primera ejecución (1 minuto 45 segundos de prueba):

Métrica Valor
Usuarios virtuales (máximo) 20
Peticiones HTTP 8 315 (78,9 por segundo)
Iteraciones completas 1 663
Tiempo de respuesta promedio 0,29 ms
Percentil 95 (p95) 0,95 ms
Tiempo máximo 23,1 ms
Peticiones fallidas 0 %
Checks 14 967 correctos, 0 fallidos

Los tres thresholds se cumplieron con mucho margen: el p95 quedó en menos de 1 ms frente al límite de 500 ms. Es un resultado esperado para una API con datos en memoria; con una base de datos real los tiempos serían mayores, y ahí es donde la prueba de carga se vuelve más útil para detectar cuellos de botella.

Conclusiones

k6 permite tratar el rendimiento como un requisito más: se escribe en código, se versiona y se valida automáticamente en cada cambio. Gracias a los thresholds, si un cambio vuelve lenta la API, el pipeline lo detecta antes de publicar. Junto con las pruebas funcionales de Newman, el proyecto cubre las dos preguntas clave: si la API hace lo correcto y si lo sigue haciendo bajo carga.

Enlaces

Gerald Zevallos — Calidad y Pruebas de Software, Universidad Privada de Tacna.

Top comments (0)