DEV Community

Cover image for Template de testes de API com K6
Marcos Vilela
Marcos Vilela

Posted on

Template de testes de API com K6

Escrevi um template para servir de modelo para escrita de testes de performance para testar APIs REST com k6, integrando Prometheus e Grafana localmente e adicionando utilitários reutilizáveis para autenticação, checks, thresholds e geração de relatórios. A seguir descrevo as decisões que tomei, exemplos de código e como rodar localmente e no CI.

Por que escolhi o k6

Escolhi o k6 por ser leve, confiável e por permitir escrever testes em JavaScript com boa legibilidade. Ele integra bem com observability (Prometheus/Grafana) e funciona tanto via CLI quanto em container — ideal para rodar localmente ou em pipelines de CI.

Estrutura do projeto

Organizei o repositório com foco em reutilização e clareza:

  • tests/ — scripts organizados por tipo (smoke, load, stress, soak).
  • lib/ — utilitários e helpers:
    • lib/common/auth.js, checks.js, generators.js, thresholds.js
    • lib/config/loader.js — carregador de configurações (.env, env vars e arquivos JSON)
    • lib/utils/logger.js, reporter.js
  • docker-compose.yml — stack local com k6, prometheus e grafana
  • .env.example — exemplo mínimo de variáveis locais

Essa divisão mantém os testes pequenos e delega lógica compartilhada para lib/.

Configuração e precedência de variáveis

Implementei uma lógica simples de carregamento de configuração em lib/config/loader.js:

  1. Variáveis passadas via CLI/CI (__ENV) têm prioridade;
  2. Em seguida, eu leio o .env local;
  3. Por fim, valores padrões vindos de config/staging.json ou config/production.json.

Trecho simplificado:

// lib/config/loader.js (trecho)
const currentEnv = __ENV.ENVIRONMENT || dotEnv.ENVIRONMENT || 'staging';
const configFile = JSON.parse(open(`../../config/${currentEnv}.json`));

const config = Object.assign({}, configFile, {
  BASE_URL: __ENV.BASE_URL || dotEnv.BASE_URL || configFile.BASE_URL,
  USERNAME: __ENV.USERNAME || dotEnv.USERNAME || configFile.USERNAME,
  PASSWORD: __ENV.PASSWORD || dotEnv.PASSWORD || configFile.PASSWORD,
  AUTH_TOKEN: __ENV.AUTH_TOKEN || dotEnv.AUTH_TOKEN || configFile.AUTH_TOKEN,
});
Enter fullscreen mode Exit fullscreen mode

Nota: __ENV é a forma padrão do k6 para acessar variáveis de ambiente passadas na execução.

Autenticação reutilizável

Para endpoints protegidos criei lib/common/auth.js. A função authenticate() tenta:

  1. Retornar AUTH_TOKEN se presente (útil para debug);
  2. Caso contrário, fazer login com USERNAME/PASSWORD e retornar o token.

Uso típico em um teste:

import { authenticate } from '../../lib/common/auth.js';

export function setup() {
  const token = authenticate();
  return { token };
}
Enter fullscreen mode Exit fullscreen mode

A função valida a resposta (checks) e chama fail() em caso de erro para interromper o teste quando a autenticação falha.

Checks e thresholds

Criei helpers para checks (lib/common/checks.js) e thresholds (lib/common/thresholds.js) para padronizar asserts e SLOs:

// Exemplo de test (smoke)
import http from 'k6/http';
import config from '../../lib/config/loader.js';
import { standardChecks } from '../../lib/common/checks.js';

export const options = {
  vus: 1,
  duration: '10s',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<500'],
  },
};

export default function () {
  const res = http.get(`${config.BASE_URL}/health`);
  standardChecks.is200(res);
}
Enter fullscreen mode Exit fullscreen mode

Os thresholds possibilitam que o job do CI falhe automaticamente quando os SLOs não são atendidos.

Relatórios

Integrei o k6-reporter para gerar summary.html e summary.json. No lib/utils/reporter.js exporto handleSummary:

import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";

export function handleSummary(data) {
  return {
    "summary.html": htmlReport(data),
    "summary.json": JSON.stringify(data),
  };
}
Enter fullscreen mode Exit fullscreen mode

E nos testes eu apenas faço:

import { handleSummary } from '../../lib/utils/reporter.js';
export { handleSummary };
Enter fullscreen mode Exit fullscreen mode

Observability: Prometheus + Grafana

Para observability local usei docker-compose.yml com k6, Prometheus e Grafana. Configurei o k6 para enviar métricas via remote write para o Prometheus, e ativei remote-write-receiver no contêiner do Prometheus.

Execução local recomendada:

docker-compose up -d
# rodar um teste (container k6 já monta o repositório):
docker-compose run --rm k6 run tests/smoke/01-health-check.js
Enter fullscreen mode Exit fullscreen mode

Após subir a stack você pode abrir Grafana (http://localhost:3000) e Prometheus (http://localhost:9090).

Integração com CI (GitHub Actions)

Usei a grafana/k6-action para executar scripts no CI. Exemplo de step:

- name: Run K6 Smoke Test
  uses: grafana/k6-action@v0.2.0
  with:
    filename: tests/smoke/01-health-check.js
  env:
    BASE_URL: ${{ secrets.BASE_URL }}
    ENVIRONMENT: staging
Enter fullscreen mode Exit fullscreen mode

Recomendo rodar smoke em PRs e load/stress em jobs separados ou agendados para evitar impacto em ambientes de produção.

Boas práticas que segui

  • Não commitar segredos (.env está no .gitignore).
  • Reutilizar helpers em lib/ para diminuir duplicação.
  • Definir thresholds para transformar métricas em critérios de sucesso/falha.
  • Manter testes idempotentes para que possam ser executados várias vezes sem efeitos colaterais.

Problemas que enfrentei e como resolvi

  • Leitura de .env: implementei parsing que aceita aspas e ignora comentários.
  • Autenticação: incluí suporte a AUTH_TOKEN estático para facilitar debug sem chamadas de login.
  • Observability: configurei Prometheus para aceitar remote write do k6 no ambiente local.

Exemplo mínimo pronto para copiar

// tests/smoke/01-health-check.js
import http from 'k6/http';
import config from '../../lib/config/loader.js';
import { standardChecks } from '../../lib/common/checks.js';

export const options = {
  vus: 1,
  duration: '10s',
};

export default function () {
  const res = http.get(`${config.BASE_URL}/health`);
  standardChecks.is200(res);
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Criar este template me ajudou a padronizar a forma como escrevemos e executamos testes de carga: fica mais fácil rodar localmente com observability, sendo assim para cada projeto, inicio um novo repositório a partir desse template, posso utilizar os testes padrões para validação e escrever novos testes logicamente, tendo assim mais flexibilidade, robustez, escala e padronização, além de poder, integrar/utilizar o K6 nas pipelines de CI/CD.

Top comments (0)