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 comk6,prometheusegrafana -
.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:
- Variáveis passadas via CLI/CI (
__ENV) têm prioridade; - Em seguida, eu leio o
.envlocal; - Por fim, valores padrões vindos de
config/staging.jsonouconfig/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,
});
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:
- Retornar
AUTH_TOKENse presente (útil para debug); - Caso contrário, fazer login com
USERNAME/PASSWORDe retornar o token.
Uso típico em um teste:
import { authenticate } from '../../lib/common/auth.js';
export function setup() {
const token = authenticate();
return { token };
}
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);
}
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),
};
}
E nos testes eu apenas faço:
import { handleSummary } from '../../lib/utils/reporter.js';
export { handleSummary };
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
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
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 (
.envestá 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_TOKENestá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);
}
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)