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 ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or 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: // 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 }; } 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); } 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), }; } 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 }; 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 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 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); } 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.
toolsutilitiessecurity toolstemplatetestesescolhiestruturaprojeto