.NET k6 Performance Testes DevOps

Testes de carga em APIs .NET com k6: do script básico ao benchmark de produção

Como usar k6 para testar a performance de APIs .NET: scripts em JavaScript, cenários de carga gradual, stress test, spike test, thresholds.

N
Neryx Digital Architects
26 de fevereiro de 2026
12 min de leitura
200 profissionais leram
Categoria: .NET Público: Times de plataforma e operação Etapa: Decisão

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 k6

Linux:

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 &gt; 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 &gt; 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 degradou

export 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&lt;0.01'],   // menos de 1% de falhas

    // Throughput mínimo:
    'http_reqs': ['rate&gt;100'],          // pelo menos 100 req/s

    // Métricas customizadas:
    'latencia_busca_produto': ['p(95)&lt;300', 'avg&lt;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 Test

on: 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: k6

grafana: 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:

Quer sair do modo reativo e priorizar o que mais importa?

O diagnóstico de maturidade ajuda a transformar sintomas operacionais em um plano mais claro de evolução.

Avaliar maturidade

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.