Template de testes de API com K6
Marcos Vilela

Marcos Vilela @marcos_vile

About: 🇧🇷 Conteúdo em português! 🚀 Platform Engineer com experiência em Linux, Docker e IaC (Ansible/Terraform), automação, observabilidade, Nginx, WordPress e bash scripts! 💻 SRE | DevOps

Location:
Montes Claros, MG
Joined:
Oct 31, 2024

Template de testes de API com K6

Publish Date: Feb 24
5 0

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.

Comments 0 total

Не удалось загрузить комментарии.
Add comment