Tools
Tools: Template de testes de API com K6
2026-02-24
0 views
admin
Por que escolhi o k6 ## Estrutura do projeto ## Configuração e precedência de variáveis ## Autenticação reutilizável ## Checks e thresholds ## Relatórios ## Observability: Prometheus + Grafana ## Integração com CI (GitHub Actions) ## Boas práticas que segui ## Problemas que enfrentei e como resolvi ## Exemplo mínimo pronto para copiar ## Conclusão 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. 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. Organizei o repositório com foco em reutilização e clareza: Essa divisão mantém os testes pequenos e delega lógica compartilhada para lib/. Implementei uma lógica simples de carregamento de configuração em lib/config/loader.js: Nota: __ENV é a forma padrão do k6 para acessar variáveis de ambiente passadas na execução. Para endpoints protegidos criei lib/common/auth.js. A função authenticate() tenta: Uso típico em um teste: A função valida a resposta (checks) e chama fail() em caso de erro para interromper o teste quando a autenticação falha. Criei helpers para checks (lib/common/checks.js) e thresholds (lib/common/thresholds.js) para padronizar asserts e SLOs: Os thresholds possibilitam que o job do CI falhe automaticamente quando os SLOs não são atendidos. Integrei o k6-reporter para gerar summary.html e summary.json. No lib/utils/reporter.js exporto handleSummary: E nos testes eu apenas faço: 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: Após subir a stack você pode abrir Grafana (http://localhost:3000) e Prometheus (http://localhost:9090). Usei a grafana/k6-action para executar scripts no CI. Exemplo de step: Recomendo rodar smoke em PRs e load/stress em jobs separados ou agendados para evitar impacto em ambientes de produçã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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
// 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 CODE_BLOCK:
// 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,
}); CODE_BLOCK:
// 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,
}); CODE_BLOCK:
import { authenticate } from '../../lib/common/auth.js'; export function setup() { const token = authenticate(); return { token };
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { authenticate } from '../../lib/common/auth.js'; export function setup() { const token = authenticate(); return { token };
} CODE_BLOCK:
import { authenticate } from '../../lib/common/auth.js'; export function setup() { const token = authenticate(); return { token };
} CODE_BLOCK:
// 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 CODE_BLOCK:
// 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);
} CODE_BLOCK:
// 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);
} CODE_BLOCK:
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 CODE_BLOCK:
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), };
} CODE_BLOCK:
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), };
} CODE_BLOCK:
import { handleSummary } from '../../lib/utils/reporter.js';
export { handleSummary }; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
import { handleSummary } from '../../lib/utils/reporter.js';
export { handleSummary }; CODE_BLOCK:
import { handleSummary } from '../../lib/utils/reporter.js';
export { handleSummary }; COMMAND_BLOCK:
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 COMMAND_BLOCK:
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 COMMAND_BLOCK:
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 CODE_BLOCK:
- name: Run K6 Smoke Test uses: grafana/[email protected] with: filename: tests/smoke/01-health-check.js env: BASE_URL: ${{ secrets.BASE_URL }} ENVIRONMENT: staging Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
- name: Run K6 Smoke Test uses: grafana/[email protected] with: filename: tests/smoke/01-health-check.js env: BASE_URL: ${{ secrets.BASE_URL }} ENVIRONMENT: staging CODE_BLOCK:
- name: Run K6 Smoke Test uses: grafana/[email protected] with: filename: tests/smoke/01-health-check.js env: BASE_URL: ${{ secrets.BASE_URL }} ENVIRONMENT: staging CODE_BLOCK:
// 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 CODE_BLOCK:
// 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);
} CODE_BLOCK:
// 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);
} - 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
- 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 - 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 - Variáveis passadas via CLI/CI (__ENV) têm prioridade;
- Em seguida, eu leio o .env local;
- Por fim, valores padrões vindos de config/staging.json ou config/production.json. - Retornar AUTH_TOKEN se presente (útil para debug);
- Caso contrário, fazer login com USERNAME/PASSWORD e retornar o token. - 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. - 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.
how-totutorialguidedev.toaimldockerjavascriptgitgithub