Você só sabe se sua API aguenta 1.000 usuários simultâneos quando você testa com 1.000 usuários simultâneos — de preferência antes de entrar em produção. k6 é a ferramenta de teste de carga mais moderna disponível: scripts em JavaScript, execução em Go (extremamente eficiente), saída para Grafana e integração nativa com CI/CD. Este guia vai do primeiro script a um benchmark completo de API .NET.
Instalação e primeiro teste
# macOS: brew install k6Linux:
sudo apt-key adv —keyserver hkp://keyserver.ubuntu.com:80 —recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69 echo “deb 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 (sem instalar):
docker run —rm -i grafana/k6 run - <script.js
// smoke-test.js — teste rápido para verificar se a API está respondendo:
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 1, // 1 usuário virtual
duration: '10s', // por 10 segundos
};
export default function () {
const res = http.get('http://localhost:5000/api/produtos');
check(res, {
'status 200': r => r.status === 200,
'tempo < 200ms': r => r.timings.duration < 200,
'tem dados': r => JSON.parse(r.body).length > 0,
});
sleep(1); // 1 segundo entre requests (simula usuário real)
}
Tipos de teste e quando usar cada um
// 1. LOAD TEST — carga normal esperada (verificar comportamento em produção)
export const options = {
stages: [
{ duration: '2m', target: 100 }, // sobe para 100 VUs em 2 min
{ duration: '5m', target: 100 }, // mantém 100 VUs por 5 min
{ duration: '2m', target: 0 }, // volta a 0 VUs
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95% das requests < 500ms
http_req_failed: ['rate<0.01'], // menos de 1% de erros
},
};
// 2. STRESS TEST — encontrar o limite da API
export const options = {
stages: [
{ duration: '2m', target: 100 },
{ duration: '5m', target: 200 },
{ duration: '2m', target: 300 },
{ duration: '5m', target: 300 }, // sustenta para ver degradação
{ duration: '2m', target: 0 },
],
};
// 3. SPIKE TEST — pico repentino (ex: Black Friday, email marketing)
export const options = {
stages: [
{ duration: '30s', target: 10 }, // linha de base
{ duration: '1m', target: 500 }, // pico súbito
{ duration: '30s', target: 10 }, // volta ao normal
],
};
// 4. SOAK TEST — carga moderada por longo período (detecta memory leaks)
export const options = {
stages: [
{ duration: '5m', target: 100 },
{ duration: '4h', target: 100 }, // 4 horas — detecta memory leak, conexões não fechadas
{ duration: '5m', target: 0 },
],
};
Script completo para API .NET com autenticação JWT
import http from 'k6/http'; import { check, group, sleep } from 'k6'; import { Rate, Trend, Counter } from 'k6/metrics';// Métricas customizadas: const errosCriacao = new Rate(‘erros_criacao_produto’); const latenciaBusca = new Trend(‘latencia_busca_produto’); const totalPedidos = new Counter(‘total_pedidos_criados’);
export const options = { stages: [ { duration: ‘1m’, target: 50 }, { duration: ‘3m’, target: 50 }, { duration: ‘1m’, target: 100 }, { duration: ‘3m’, target: 100 }, { duration: ‘2m’, target: 0 }, ], thresholds: { http_req_duration: [‘p(50)<100’, ‘p(95)<500’, ‘p(99)<1000’], http_req_failed: [‘rate<0.005’], // < 0.5% de erros latencia_busca_produto: [‘p(95)<300’], erros_criacao_produto: [‘rate<0.01’], }, };
// Obtém token JWT antes dos testes (setup roda 1x): export function setup() { const res = http.post(‘http://localhost:5000/auth/token’, JSON.stringify({ username: ‘teste@neryx.com.br’, password: ‘SenhaTestek6!’, }), { headers: { ‘Content-Type’: ‘application/json’ } });
check(res, { 'login ok': r => r.status === 200 }); return { token: JSON.parse(res.body).accessToken };}
// Função principal executada por cada VU: export default function (data) { const headers = { ‘Authorization’:
Bearer ${data.token}, ‘Content-Type’: ‘application/json’, };group('Listar produtos', () => { const start = Date.now(); const res = http.get('http://localhost:5000/api/produtos?pageSize=20', { headers }); latenciaBusca.add(Date.now() - start); check(res, { 'status 200': r => r.status === 200, 'tem produtos': r => JSON.parse(r.body).items?.length > 0, }); }); sleep(0.5); group('Buscar produto por ID', () => { const produtos = JSON.parse(http.get( 'http://localhost:5000/api/produtos?pageSize=5', { headers }).body).items; if (produtos?.length > 0) { const id = produtos[Math.floor(Math.random() * produtos.length)].id; const res = http.get(`http://localhost:5000/api/produtos/${id}`, { headers }); check(res, { 'produto encontrado': r => r.status === 200 }); } }); sleep(0.3); group('Criar pedido', () => { const res = http.post( 'http://localhost:5000/api/pedidos', JSON.stringify({ clienteId: '11111111-1111-1111-1111-111111111111', itens: [{ produtoId: '22222222-2222-2222-2222-222222222222', quantidade: 1 }] }), { headers } ); errosCriacao.add(res.status !== 201); if (res.status === 201) totalPedidos.add(1); check(res, { 'pedido criado': r => r.status === 201 }); }); sleep(1);}
// Teardown — limpeza após os testes: export function teardown(data) { console.log(‘Teste finalizado. Token usado:’, data.token.substring(0, 20) + ’…’); }
Thresholds: definindo critérios de aprovação
// Thresholds fazem o k6 retornar exit code != 0 quando violados // Perfeito para CI/CD: pipeline falha se a performance degradouexport const options = { thresholds: { // Latência: ‘http_req_duration’: [‘p(95)<500’], // geral ‘http_req_duration{name:listagem}’: [‘p(95)<200’], // endpoint específico ‘http_req_duration{name:criacao}’: [‘p(95)<800’], // endpoint de escrita
// Erros: 'http_req_failed': ['rate<0.01'], // menos de 1% de falhas // Throughput mínimo: 'http_reqs': ['rate>100'], // pelo menos 100 req/s // Métricas customizadas: 'latencia_busca_produto': ['p(95)<300', 'avg<150'], },};
// Tagging de requests para thresholds por endpoint: http.get(‘http://localhost:5000/api/produtos’, { tags: { name: ‘listagem’ } }); http.post(‘http://localhost:5000/api/produtos’, payload, { tags: { name: ‘criacao’ } });
Integração com GitHub Actions
# .github/workflows/load-test.yml: name: Load Teston: push: branches: [main] schedule: - cron: ‘0 3 * * 1’ # toda segunda-feira às 3h (ambiente de staging)
jobs: load-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Subir API em Docker run: docker compose -f docker-compose.test.yml up -d --wait timeout-minutes: 2 - name: Aguardar API inicializar run: | for i in {1..30}; do curl -sf http://localhost:5000/health && break sleep 2 done - name: Executar k6 load test uses: grafana/k6-action@v0.3.1 with: filename: tests/load/api-load-test.js flags: --out json=results.json env: K6_BASE_URL: http://localhost:5000 - name: Publicar relatório if: always() uses: actions/upload-artifact@v4 with: name: k6-results-${{ github.sha }} path: results.json - name: Derrubar ambiente if: always() run: docker compose -f docker-compose.test.yml down
Visualização em Grafana com InfluxDB
# docker-compose para ambiente de monitoramento de carga: version: '3.8' services: influxdb: image: influxdb:1.8 ports: ['8086:8086'] environment: INFLUXDB_DB: k6grafana: image: grafana/grafana:latest ports: [‘3000:3000’] environment: GF_SECURITY_ADMIN_PASSWORD: admin
Executar k6 enviando para InfluxDB em tempo real:
k6 run —out influxdb=http://localhost:8086/k6 api-load-test.js
No Grafana, importe o dashboard oficial do k6 (ID: 2587)
Você vê em tempo real: RPS, p95, p99, erros, VUs ativos
Interpretando os resultados
As métricas mais importantes no relatório do k6:
- http_req_duration p(95): 95% das requests completaram abaixo deste valor — é o número que importa para SLA
- http_req_failed: taxa de erros — qualquer coisa acima de 1% em produção é crítico
- http_reqs: throughput total em requests/segundo — seu "teto" de capacidade
- vus_max: pico de usuários virtuais ativos — confirma que o teste chegou ao alvo
- data_received / data_sent: volume de dados — detecta respostas desnecessariamente grandes
- Queda no throughput com aumento de VUs = gargalo identificado (CPU? banco? pool de conexões?)
Conclusão
k6 elimina o "achismo" sobre performance: você sabe exatamente qual é o p95 de latência e o throughput máximo da sua API antes de ela ir para produção. Integrado ao CI/CD, o pipeline falha automaticamente se um PR degradar a performance — prevenindo regressões antes de chegarem aos usuários.
Se você precisa fazer um benchmark da sua API .NET, identificar gargalos de performance ou definir um processo de testes de carga no CI/CD, a Neryx pode ajudar. Consultoria inicial gratuita.
Leitura complementar: