Guias

Teste de carga no GitHub Actions: guia prático

7 min de leitura

Para fazer teste de carga no GitHub Actions basta um arquivo de workflow: instalar a ferramenta, rodar um teste curto contra uma URL de staging e deixar o código de saída da ferramenta reprovar o job quando um threshold não for atingido. Envie os resultados como artefato para que cada execução deixe registro. Mantenha o teste pequeno, porque um runner padrão hospedado pelo GitHub é uma única máquina virtual com poucos núcleos de CPU.

Os dados sobre as ferramentas e sobre o GitHub vêm da documentação de cada fornecedor, conferidos em 9 de outubro de 2026, e os workflows seguem esse uso documentado.

FerramentaInstalada porO que reprova o jobResultados para guardar
k6setup-k6-action da GrafanaUm threshold não atingidoSaída em JSON
Artilleryartilleryio/action-cliUma verificação ensure que falhoureport.json
Locustpip install locustQualquer requisição com falha, ou uma regra suaArquivos CSV e HTML
JMeterO arquivo da versãoNada embutido: adicione um passo de verificaçãoArquivo .jtl e relatório HTML

Do que um workflow de teste de carga precisa?

De um gatilho, um runner, um passo que instala a ferramenta, um passo que roda o teste com thresholds e um passo que envia os resultados. O resto é opcional.

Guarde o script de teste no repositório, por exemplo em tests/load/. Leia a URL de staging de uma variável do repositório pelo contexto vars e defina timeout-minutes no job: o padrão é de 360 minutos (conferido em 9 de outubro de 2026).

Quais eventos devem disparar um teste de carga?

Rode um teste curto nos pull requests e um mais longo por agendamento ou manualmente.

Use pull_request para pegar uma regressão antes do merge, workflow_dispatch para execuções manuais e schedule para execuções cron (em UTC, a menos que você defina um fuso horário). A referência de eventos do GitHub traz alguns limites (conferido em 9 de outubro de 2026): agendamentos rodam só na branch padrão, no máximo a cada cinco minutos, e podem atrasar no começo de cada hora. Pull requests vindos de forks não recebem segredos além do GITHUB_TOKEN.

Como rodar o k6 no GitHub Actions?

Use as duas actions oficiais da Grafana, uma para instalar o k6 e outra para rodar o script. O passo falha sempre que o k6 termina com código de saída diferente de zero, que é o que um threshold não atingido provoca.

Tanto a setup-k6-action quanto a run-k6-action estão na v1 (conferido em 9 de outubro de 2026):

name: load-test
on:
  pull_request:
  workflow_dispatch:

jobs:
  k6:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    env:
      BASE_URL: ${{ vars.STAGING_URL }}
    steps:
      - uses: actions/checkout@v7
      - uses: grafana/setup-k6-action@v1
      - uses: grafana/run-k6-action@v1
        with:
          path: tests/load/smoke.js
          flags: --out json=results.json
      - uses: actions/upload-artifact@v7
        if: always()
        with:
          name: k6-results
          path: results.json
          retention-days: 14

Os thresholds ficam no script, que lê BASE_URL do ambiente:

import http from "k6/http";

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

export default function () {
  http.get(`${__ENV.BASE_URL}/health`);
}

A documentação do k6 deixa claro que os checks não afetam o código de saída, então a decisão de aprovar ou reprovar precisa vir dos thresholds.

Como rodar Artillery, Locust ou JMeter no lugar?

Troque os passos de instalação e execução, mantenha o resto do workflow e use o mecanismo de cada ferramenta para falhar quando uma meta não for atingida.

Artillery tem uma GitHub Action oficial cujo README diz que a @v2 acompanha a versão 2.x mais recente (conferido em 9 de outubro de 2026). O passo é uses: artilleryio/action-cli@v2 com command: run --target ${{ vars.STAGING_URL }} tests/load/api.yml --output report.json, e o plugin ensure faz o Artillery sair com código diferente de zero quando uma meta não é atingida. A seção config do script:

config:
  target: "https://staging.example.com"
  phases:
    - duration: 60
      arrivalRate: 5
  plugins:
    ensure:
      thresholds:
        - "http.response_time.p95": 500

Locust sai com código 1 quando alguma requisição falhou, e para regras de latência você define environment.process_exit_code num listener do evento quitting, como mostra a mesma página (conferido em 9 de outubro de 2026). Depois de actions/setup-python@v7 com python-version: "3.13":

      - run: pip install locust
      - run: >
          locust -f tests/load/locustfile.py --headless
          -u 20 -r 5 -t 2m -H "$BASE_URL"
          --csv results --html report.html

JMeter roda sem interface gráfica com -n, e as opções de linha de comando do manual não incluem nenhum threshold de aprovação (conferido em 9 de outubro de 2026). Um passo curto pode ler o arquivo de resultados, em CSV com uma coluna success por padrão, e reprovar o job por conta própria:

      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: "21"
      - name: Install JMeter 5.6.3
        env:
          JM_BASE: https://archive.apache.org/dist/jmeter/binaries
        run: curl -sSL "$JM_BASE/apache-jmeter-5.6.3.tgz" | tar -xzf -
      - name: Run the test plan
        run: >
          apache-jmeter-5.6.3/bin/jmeter -n -t tests/load/plan.jmx
          -l results.jtl -e -o report
      - name: Fail above 1% errors
        run: |
          python3 - <<'EOF'
          import csv, sys
          rows = list(csv.DictReader(open("results.jtl")))
          bad = sum(r["success"] != "true" for r in rows)
          print(f"{bad} of {len(rows)} samples failed")
          sys.exit(1 if not rows or bad > 0.01 * len(rows) else 0)
          EOF

Como fazer o job falhar quando o teste não atinge uma meta?

Escreva as metas no teste como thresholds e deixe o código de saída fazer o resto. O GitHub marca um passo como falho quando o comando dele termina com código diferente de zero, e o job falha junto.

Comece com uma taxa de erro rígida e um percentil de latência folgado, e aperte o limite de latência quando souber o quanto seus números variam de uma execução para outra. O guia passo a passo de teste de carga mostra como escolher essas metas.

Onde devem ficar os resultados de cada execução?

Envie-os como artefato do workflow, com if: always() para que um teste reprovado ainda guarde o relatório. Os artefatos ficam guardados por 90 dias por padrão, e retention-days encurta esse prazo.

O README da upload-artifact documenta os 90 dias padrão (conferido em 9 de outubro de 2026). Defina if-no-files-found: error para que um relatório ausente faça o passo falhar de forma visível.

Quanta carga um runner hospedado pelo GitHub consegue gerar?

O suficiente para um smoke test ou uma pequena checagem de regressão. Um runner Linux padrão tem 4 CPUs e 16 GB de RAM em repositórios públicos, 2 CPUs e 8 GB em privados, e um job para depois de 6 horas.

Esses números vêm da referência de runners e dos limites do Actions do GitHub (conferido em 9 de outubro de 2026), que não trazem nenhum número de largura de banda de rede.

Um job roda numa única máquina, então é um único gerador de carga: se a CPU dele satura, os tempos de resposta incluem o tempo de espera no próprio runner. Toda requisição também sai dessa máquina, então rate limiters que contam por endereço de cliente enxergam um único cliente muito ativo. O GitHub desaconselha colocar em allowlists as faixas de IP do Azure usadas pelos runners Ubuntu; runners maiores (larger runners) nos planos Team e Enterprise Cloud trazem mais CPU, mais memória e endereços IP estáticos.

Quando a carga deve vir de outro lugar?

Quando você precisa de mais carga do que uma máquina produz, de tráfego de várias regiões ou de execuções mais longas que o limite de um job.

Com k6, a run-k6-action roda o teste no Grafana Cloud k6 quando você define cloud-run-locally: false e passa K6_CLOUD_TOKEN, K6_CLOUD_PROJECT_ID e, com o k6 v2, K6_CLOUD_STACK_ID. Uma execução na nuvem ignora por padrão o ambiente do runner, então adicione -e BASE_URL=${{ vars.STAGING_URL }} a flags (conferido em 10 de outubro de 2026). A action do Artillery pode chamar run-fargate ou run-lambda para gerar a carga a partir da sua própria conta AWS (ambos conferidos em 9 de outubro de 2026). O comparativo k6 vs Artillery cobre os dois.

Para JMeter, a LoadFocus documenta uma integração com o GitHub Actions baseada no seu cliente npm. Você salva o teste na sua conta LoadFocus, guarda a chave de API e o ID do time como segredos e chama run-test. As flags abaixo seguem a ajuda do cliente:

      - run: npm install -g @loadfocus/loadfocus-api-client
      - env:
          LF_KEY: ${{ secrets.LOADFOCUS_API_KEY }}
          LF_TEAM: ${{ secrets.LOADFOCUS_TEAM_ID }}
        run: |
          test -n "$LF_KEY"
          test -n "$LF_TEAM"
          loadfocus-api config set \
            --apikey "$LF_KEY" --teamid "$LF_TEAM"
          loadfocus-api jmeter run-test --name checkout-api \
            --thresholds "avgresponse<=200,errors==0,p95<=250"

O cliente espera até 1.800 segundos por padrão (--wait-timeout muda isso), então dê ao job um timeout-minutes acima de 30, como 40. Ele sai com código 1 quando um threshold não é atingido e com 0 quando falta a chave de API ou o ID do time, por isso as linhas test -n fazem o passo falhar antes quando um segredo está vazio. A carga vem da LoadFocus, que roda scripts JMeter a partir de 25 regiões na nuvem.

FAQ

Quanto tempo deve durar um teste de carga num pull request? Alguns minutos: o bastante para passar da rampa de subida e obter um percentil estável, e pouco o bastante para ninguém ficar esperando.

É permitido fazer teste de carga a partir de runners hospedados pelo GitHub? Os termos do GitHub para o Actions proíbem perturbar qualquer serviço e limitam os runners hospedados ao trabalho no próprio projeto do repositório (conferido em 9 de outubro de 2026). Testar o ambiente de staging desse mesmo projeto, sem sobrecarregar os servidores do GitHub, é o tipo de teste que esses termos descrevem.

Posso usar um runner auto-hospedado no lugar? Sim. Ele roda no seu próprio hardware e na sua rede, e a página de limites do GitHub permite que um job dure até 5 dias nele. Ele continua sendo um único gerador de carga.

Leituras relacionadas


As afirmações técnicas deste artigo não foram revisadas de forma independente.

LoadFocus team
Escrito pela equipe da LoadFocus, as pessoas por trás de um serviço de teste de carga na nuvem.