K6 es una herramienta moderna de load testing amigable para desarrolladores, diseñada para probar el rendimiento y confiabilidad de APIs, microservicios y sitios web. Construido con Go y scriptable en JavaScript, K6 lleva las pruebas de rendimiento al flujo de trabajo DevOps con integración perfecta de CI/CD y capacidades cloud-native.

Si buscas ampliar tus capacidades de testing de rendimiento, te recomendamos explorar nuestras guías sobre pruebas de rendimiento de APIs y optimización de pipelines CI/CD para equipos QA. Complementar K6 con pruebas de APIs funcionales y una estrategia de testing continuo maximiza la cobertura de calidad de tus servicios.

¿Por Qué K6 para Load Testing Moderno?

K6 aborda las limitaciones de las herramientas tradicionales de pruebas de rendimiento enfocándose en la experiencia del desarrollador y la infraestructura moderna:

  • API JavaScript: Scripting familiar para desarrolladores frontend y backend
  • Diseño CLI-first: Perfecto para automatización y pipelines CI/CD
  • Rendimiento: Uso eficiente de recursos, puede generar alta carga desde una sola máquina
  • Cloud & Local: Ejecutar localmente durante desarrollo, escalar a cloud para pruebas a nivel producción
  • Protocolos modernos: Soporte HTTP/1.1, HTTP/2, WebSockets, gRPC
  • Observabilidad: Métricas en tiempo real, integración Prometheus, dashboards Grafana

K6 vs Herramientas Tradicionales de Load Testing

CaracterísticaK6JMeterGatlingLocust
Lenguaje de ScriptingJavaScriptGUI/XMLScalaPython
Eficiencia de RecursosExcelenteMediaBuenaMedia
Integración CI/CDNativaPluginsBuenaBuena
Curva de AprendizajeBajaMediaAltaBaja
Soporte CloudNativo (K6 Cloud)PluginsGatling EnterpriseNo nativo
Soporte de ProtocolosHTTP/2, WS, gRPCExtensoHTTP/2, WSHTTP
Resultados en Tiempo RealExcelenteLimitadoBuenoBueno

Comenzando con K6

Instalación

# macOS (Homebrew)
brew install k6

# Windows (Chocolatey)
choco install k6

# Linux (Debian/Ubuntu)
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
  --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | \
  sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install k6

# Docker
docker pull grafana/k6

Tu Primera Prueba K6

// simple-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  vus: 10, // Virtual users (usuarios virtuales)
  duration: '30s', // Duración de la prueba
};

export default function () {
  const response = http.get('https://test-api.k6.io');

  check(response, {
    'status es 200': (r) => r.status === 200,
    'tiempo de respuesta < 500ms': (r) => r.timings.duration < 500,
  });

  sleep(1);
}

Ejecutar la prueba:

k6 run simple-test.js

Configuración de Pruebas y Perfiles de Carga

Usuarios Virtuales y Duración

export const options = {
  // Configuración simple
  vus: 50,
  duration: '5m',

  // O usar stages para ramping
  stages: [
    { duration: '2m', target: 100 }, // Subir a 100 usuarios
    { duration: '5m', target: 100 }, // Mantener 100 usuarios
    { duration: '2m', target: 0 },   // Bajar a 0 usuarios
  ],
};

Escenarios de Load Testing

export const options = {
  scenarios: {
    // Carga constante
    constant_load: {
      executor: 'constant-vus',
      vus: 50,
      duration: '5m',
    },

    // Ramping VUs
    ramping_load: {
      executor: 'ramping-vus',
      startVUs: 0,
      stages: [
        { duration: '1m', target: 50 },
        { duration: '3m', target: 50 },
        { duration: '1m', target: 0 },
      ],
    },

    // Tasa de solicitudes constante
    constant_rps: {
      executor: 'constant-arrival-rate',
      rate: 1000, // 1000 solicitudes por segundo
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,
      maxVUs: 200,
    },

    // Prueba de spike
    spike_test: {
      executor: 'ramping-arrival-rate',
      startRate: 100,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '30s', target: 100 },  // Carga normal
        { duration: '1m', target: 1000 },  // Spike
        { duration: '30s', target: 100 },  // Volver a normal
      ],
    },
  },
};

Thresholds para Criterios Pass/Fail

export const options = {
  thresholds: {
    // Errores HTTP deben ser menos del 1%
    http_req_failed: ['rate<0.01'],

    // 95% de solicitudes deben estar bajo 500ms
    http_req_duration: ['p(95)<500'],

    // Percentil 99 debe estar bajo 1s
    'http_req_duration{type:api}': ['p(99)<1000'],

    // Thresholds de métricas personalizadas
    checks: ['rate>0.99'], // 99% de checks deben pasar
  },
};

Pruebas HTTP Avanzadas

Métodos HTTP y Request Bodies

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  // Solicitud GET
  const getResponse = http.get('https://api.example.com/users');

  // POST con JSON
  const payload = JSON.stringify({
    name: 'John Doe',
    email: 'john@example.com',
  });

  const params = {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer token123',
    },
  };

  const postResponse = http.post(
    'https://api.example.com/users',
    payload,
    params
  );

  check(postResponse, {
    'creado exitosamente': (r) => r.status === 201,
    'tiene user id': (r) => r.json('id') !== undefined,
  });

  // Solicitud PUT
  const updatePayload = JSON.stringify({ name: 'Jane Doe' });
  http.put(`https://api.example.com/users/${postResponse.json('id')}`, updatePayload, params);

  // Solicitud DELETE
  http.del(`https://api.example.com/users/${postResponse.json('id')}`, null, params);
}

Batch Requests

import http from 'k6/http';

export default function () {
  // Ejecutar solicitudes en paralelo
  const responses = http.batch([
    ['GET', 'https://api.example.com/users'],
    ['GET', 'https://api.example.com/products'],
    ['GET', 'https://api.example.com/orders'],
  ]);

  // Verificar todas las respuestas
  responses.forEach((response, index) => {
    check(response, {
      [`solicitud ${index} status es 200`]: (r) => r.status === 200,
    });
  });
}

Autenticación y Gestión de Sesiones

import http from 'k6/http';
import { check } from 'k6';

export function setup() {
  // Login una vez por ejecución de prueba
  const loginResponse = http.post('https://api.example.com/auth/login', {
    username: 'testuser',
    password: 'testpass',
  });

  return { token: loginResponse.json('token') };
}

export default function (data) {
  // Usar token desde setup
  const params = {
    headers: {
      'Authorization': `Bearer ${data.token}`,
    },
  };

  const response = http.get('https://api.example.com/protected', params);

  check(response, {
    'autenticado': (r) => r.status === 200,
  });
}

Parametrización de Datos

Usando Datos CSV

import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

const csvData = new SharedArray('users', function () {
  return papaparse.parse(open('./users.csv'), { header: true }).data;
});

export default function () {
  // Obtener usuario aleatorio
  const user = csvData[Math.floor(Math.random() * csvData.length)];

  const response = http.post('https://api.example.com/login', {
    username: user.username,
    password: user.password,
  });

  check(response, {
    'login exitoso': (r) => r.status === 200,
  });
}

Usando Datos JSON

import { SharedArray } from 'k6/data';

const testData = new SharedArray('products', function () {
  return JSON.parse(open('./products.json'));
});

export default function () {
  const product = testData[__VU % testData.length]; // Selección round-robin

  http.post('https://api.example.com/cart/add', JSON.stringify({
    productId: product.id,
    quantity: 1,
  }), {
    headers: { 'Content-Type': 'application/json' },
  });
}

Métricas Personalizadas y Tags

Métricas Personalizadas

import http from 'k6/http';
import { Trend, Counter, Rate, Gauge } from 'k6/metrics';

const myTrend = new Trend('waiting_time');
const myCounter = new Counter('my_counter');
const myRate = new Rate('my_rate');
const myGauge = new Gauge('my_gauge');

export default function () {
  const response = http.get('https://api.example.com');

  // Agregar métricas personalizadas
  myTrend.add(response.timings.waiting);
  myCounter.add(1);
  myRate.add(response.status === 200);
  myGauge.add(response.timings.duration);
}

Tags para Organizar Métricas

import http from 'k6/http';
import { check } from 'k6';

export default function () {
  // Etiquetar solicitudes individuales
  const response = http.get('https://api.example.com/users', {
    tags: {
      type: 'api',
      endpoint: 'users',
    },
  });

  check(response, {
    'status 200': (r) => r.status === 200,
  }, { type: 'api' }); // Etiquetar checks
}

export const options = {
  thresholds: {
    // Threshold para tags específicos
    'http_req_duration{type:api}': ['p(95)<500'],
    'http_req_duration{endpoint:users}': ['p(99)<1000'],
  },
};

Escenarios de Prueba del Mundo Real

Flujo de Usuario E-commerce

import http from 'k6/http';
import { check, group, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },
    { duration: '5m', target: 50 },
    { duration: '2m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],
    'http_req_duration{page:home}': ['p(95)<300'],
    'http_req_duration{page:product}': ['p(95)<400'],
  },
};

export default function () {
  let response;

  group('Página de Inicio', function () {
    response = http.get('https://ecommerce.example.com', {
      tags: { page: 'home' },
    });
    check(response, { 'homepage cargada': (r) => r.status === 200 });
    sleep(1);
  });

  group('Buscar Productos', function () {
    response = http.get('https://ecommerce.example.com/search?q=laptop', {
      tags: { page: 'search' },
    });
    check(response, { 'resultados de búsqueda': (r) => r.status === 200 });
    sleep(2);
  });

  group('Ver Producto', function () {
    response = http.get('https://ecommerce.example.com/products/123', {
      tags: { page: 'product' },
    });
    check(response, { 'detalles del producto': (r) => r.status === 200 });
    sleep(3);
  });

  group('Agregar al Carrito', function () {
    response = http.post(
      'https://ecommerce.example.com/cart',
      JSON.stringify({ productId: 123, quantity: 1 }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { page: 'cart' },
      }
    );
    check(response, { 'agregado al carrito': (r) => r.status === 200 });
    sleep(1);
  });

  group('Checkout', function () {
    response = http.post(
      'https://ecommerce.example.com/checkout',
      JSON.stringify({
        payment: 'card',
        address: '123 Main St',
      }),
      {
        headers: { 'Content-Type': 'application/json' },
        tags: { page: 'checkout' },
      }
    );
    check(response, { 'checkout exitoso': (r) => r.status === 200 });
  });
}

Prueba de Carga API con Manejo de Errores

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const errorRate = new Rate('errors');

export const options = {
  scenarios: {
    stress_test: {
      executor: 'ramping-arrival-rate',
      startRate: 10,
      timeUnit: '1s',
      preAllocatedVUs: 50,
      maxVUs: 500,
      stages: [
        { duration: '1m', target: 10 },
        { duration: '2m', target: 50 },
        { duration: '2m', target: 100 },
        { duration: '1m', target: 10 },
      ],
    },
  },
  thresholds: {
    errors: ['rate<0.1'], // Tasa de error debe ser menor al 10%
    http_req_duration: ['p(95)<2000', 'p(99)<5000'],
  },
};

export default function () {
  const response = http.get('https://api.example.com/data');

  const success = check(response, {
    'status es 200': (r) => r.status === 200,
    'tiempo de respuesta OK': (r) => r.timings.duration < 1000,
    'sin errores en body': (r) => !r.body.includes('error'),
  });

  errorRate.add(!success);

  if (response.status !== 200) {
    console.error(`Solicitud falló: ${response.status} ${response.body}`);
  }

  sleep(1);
}

Integración CI/CD

GitHub Actions

name: Load Test

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *' # Diariamente a las 2 AM

jobs:
  k6_load_test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run K6 Load Test
        uses: grafana/k6-action@v0.3.1
        with:
          filename: tests/load-test.js
          cloud: true
          token: ${{ secrets.K6_CLOUD_TOKEN }}

      - name: Upload Results
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: k6-results
          path: results/

Jenkins Pipeline

pipeline {
    agent any

    stages {
        stage('Load Test') {
            steps {
                sh '''
                    k6 run --out json=results.json \
                           --out influxdb=http://influxdb:8086/k6 \
                           tests/load-test.js
                '''
            }
        }

        stage('Check Thresholds') {
            steps {
                script {
                    def exitCode = sh(
                        script: 'k6 run --quiet tests/load-test.js',
                        returnStatus: true
                    )
                    if (exitCode != 0) {
                        error("Los thresholds de load test fallaron")
                    }
                }
            }
        }
    }

    post {
        always {
            archiveArtifacts artifacts: 'results.json', fingerprint: true
        }
    }
}

GitLab CI

k6-test:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run --summary-export=summary.json tests/load-test.js
  artifacts:
    when: always
    paths:
      - summary.json
    reports:
      junit: summary.json

Observabilidad y Análisis de Resultados

Opciones de Output

# Salida JSON
k6 run --out json=results.json script.js

# Salida CSV
k6 run --out csv=results.csv script.js

# InfluxDB + Grafana
k6 run --out influxdb=http://localhost:8086/k6 script.js

# Prometheus Remote Write
k6 run --out experimental-prometheus-rw script.js

# K6 Cloud
k6 run --out cloud script.js

# Múltiples salidas
k6 run --out json=results.json --out influxdb=http://localhost:8086/k6 script.js

Lectura de Resultados

# Estadísticas resumen
k6 run script.js

# Salida detallada
k6 run --verbose script.js

# Modo silencioso (solo errores)
k6 run --quiet script.js

# Guardar resumen a archivo
k6 run --summary-export=summary.json script.js

Resumen Personalizado

import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";

export function handleSummary(data) {
  return {
    "summary.html": htmlReport(data),
    "summary.json": JSON.stringify(data),
    stdout: textSummary(data, { indent: " ", enableColors: true }),
  };
}

Mejores Prácticas

Eficiencia de Recursos

// ❌ Mal: Crea nuevo objeto cada iteración
export default function () {
  const headers = { 'Content-Type': 'application/json' };
  http.get('https://api.example.com', { headers });
}

// ✓ Bien: Reutilizar objetos
const params = {
  headers: { 'Content-Type': 'application/json' },
};

export default function () {
  http.get('https://api.example.com', params);
}

Think Time y Pacing

import { sleep } from 'k6';

export default function () {
  // Simular tiempo de pensamiento del usuario
  http.get('https://api.example.com/page1');
  sleep(Math.random() * 3 + 2); // 2-5 segundos

  http.get('https://api.example.com/page2');
  sleep(Math.random() * 2 + 1); // 1-3 segundos
}

Scripts de Prueba Modulares

// modules/auth.js
export function login(username, password) {
  const response = http.post('https://api.example.com/login', {
    username,
    password,
  });
  return response.json('token');
}

// modules/users.js
export function getUser(token, userId) {
  return http.get(`https://api.example.com/users/${userId}`, {
    headers: { Authorization: `Bearer ${token}` },
  });
}

// main-test.js
import { login } from './modules/auth.js';
import { getUser } from './modules/users.js';

export default function () {
  const token = login('user', 'pass');
  getUser(token, 123);
}

Conclusión

K6 representa un cambio de paradigma en pruebas de rendimiento, trayendo herramientas amigables para desarrolladores y prácticas modernas al load testing. Su API JavaScript, uso eficiente de recursos e integración perfecta con pipelines CI/CD lo hacen ideal para equipos DevOps que practican pruebas continuas.

Ventajas clave:

  • API JavaScript amigable para desarrolladores
  • Uso eficiente de recursos para generación de alta carga
  • Integración nativa CI/CD
  • Soporte de protocolos modernos (HTTP/2, WebSockets, gRPC)
  • Modos de ejecución flexibles (local, cloud, híbrido)
  • Rico ecosistema y opciones de observabilidad

Ya sea que estés probando microservicios, APIs o aplicaciones web completas, K6 proporciona las herramientas y flexibilidad para asegurar que tus sistemas funcionen confiablemente bajo carga. Al integrar pruebas de rendimiento temprano en el ciclo de desarrollo, los equipos pueden detectar problemas antes de que lleguen a producción y mantener aplicaciones de alta calidad y rendimiento.

Documentacion Relacionada