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 }, }; 

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); 

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 

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.