Pular para o conteúdo

CI - GitHub Actions

  • Workflow — arquivo YAML em .github/workflows/ que descreve um processo automatizado. Um repo pode ter vários.
  • Evento / gatilho — o que dispara o workflow: push, pull_request, schedule (cron), workflow_dispatch (manual), entre outros.
  • Job — unidade de execução que roda em um runner; por padrão jobs correm em paralelo, mas podem depender uns dos outros com needs.
  • Step — passo dentro de um job; executa um comando (run) ou uma action.
  • Action — unidade reutilizável e versionada (ex.: actions/checkout@v4, actions/setup-java@v4).
  • Runner — máquina que executa o job (hospedada pelo GitHub, ex.: ubuntu-latest, ou self-hosted).
graph TD
    A[Evento: push / pull_request] --> B[Workflow]
    B --> C[Job: build-and-test]
    subgraph "Runner: ubuntu-latest"
        C --> D[step: checkout]
        D --> E[step: setup-java + cache]
        E --> F[step: mvn verify - build + test]
        F --> G{Testes passaram?}
    end
    G -- não --> H[Pipeline vermelho:<br/>bloqueia merge]
    G -- sim --> I[Job: build-image - needs build-and-test]
    I --> J[Push para registry]

Sem cache, cada execução rebaixa todo o ~/.m2 (ou ~/.gradle) da internet — lento e caro. O actions/setup-java já integra cache do Maven/Gradle; a chave do cache é derivada dos arquivos de build, então só é invalidada quando as dependências mudam. Isso é central para o feedback rápido que a CI promete.

Executa o mesmo job em várias combinações (ex.: Java 17, 21 e 24) em paralelo, cada uma em seu runner. Útil para validar compatibilidade sem duplicar o YAML.

Credenciais (token do registry, senha de deploy) nunca vão no YAML em texto puro. Ficam em Secrets do repositório/organização e são injetadas como ${{ secrets.NOME }} em runtime, mascaradas nos logs.

name: CI
on:
push:
branches: [ main ]
pull_request:
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '21', '24' ] # matriz: valida em duas versões de JDK
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven # cacheia ~/.m2 automaticamente
# 'verify' compila, roda testes unitários e de integração
# (Testcontainers usa o Docker já presente no runner ubuntu)
- name: Build e testes
run: ./mvnw --batch-mode verify
- name: Publica relatório de testes
if: always() # roda mesmo se os testes falharem
uses: actions/upload-artifact@v4
with:
name: surefire-reports-${{ matrix.java }}
path: target/surefire-reports/
build-image:
runs-on: ubuntu-latest
needs: build-and-test # só roda se os testes passaram
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write # o GITHUB_TOKEN é read-only por padrão
steps:
- uses: actions/checkout@v4
- name: Login no registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # secret, nunca em texto puro
- name: Build e push da imagem
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

Note a divisão em dois jobs: build-image declara needs: build-and-test, então a imagem só é construída se a suíte passar — a rede de segurança dos testes barra artefatos quebrados.

A CI só entrega valor se houver testes de verdade rodando nela. A relação com Testes e TDD é direta: a suíte automatizada é a rede de segurança do pipeline. Um push que quebra o comportamento faz os testes falharem, o job fica vermelho e o merge é bloqueado antes de a regressão chegar ao main.

  • A pirâmide de Tipos de Teste - Unit, Integration e E2E guia o que roda no CI: muitos testes unitários rápidos na base, integração no meio (com Testcontainers sobre Docker), poucos E2E no topo.
  • O F de “Fast” importa aqui: testes rápidos + cache de dependências = feedback em minutos. Uma suíte lenta corrói a prática — desenvolvedores param de esperar o CI e a integração deixa de ser contínua de fato.

Não confunda os dois:

  • CI (Continuous Integration) — integrar e validar cada mudança automaticamente: build + testes a cada push. Termina com um artefato validado (ex.: imagem construída).
  • CD — duas leituras: Continuous Delivery (o artefato fica sempre pronto para deploy, com a promoção final acionada por um humano) e Continuous Deployment (todo artefato validado vai automaticamente para produção, sem intervenção manual).

Em suma: CI garante que o código está bom; CD cuida de levá-lo até o ambiente. O exemplo acima é CI (com um passo de empacotamento); adicionar um job que promove a imagem para um ambiente seria a fronteira do CD.

  • Pipeline verde sem testes reais. Compila mas não exercita a lógica; dá falsa confiança.
  • Sem cache de dependências. Cada execução baixa tudo de novo, feedback lento, prática abandonada.
  • Segredos em texto puro no YAML. Credenciais expostas no repositório e nos logs; use Secrets.
  • Suíte lenta no caminho crítico. Viola o Princípio FIRST; empurra devs a ignorar o CI ou desligar testes.
  • Testes flaky tolerados. Falhas intermitentes fazem o time ignorar o vermelho — o pipeline perde autoridade.
  • needs ausente entre jobs. Construir/publicar a imagem sem depender do job de testes deixa artefatos quebrados escaparem.
  • Rodar todo o pipeline em um único job gigante. Impede paralelismo e reuso; prefira jobs coesos com dependências explícitas.

1. Diferencie workflow, job, step, runner e action no GitHub Actions.

Resposta

Workflow é o processo automatizado descrito em YAML; job é uma unidade que roda em um runner (jobs paralelizam por padrão, encadeáveis com needs); step é um passo dentro do job (um run ou uma action); runner é a máquina que executa o job (ex.: ubuntu-latest); action é uma unidade reutilizável e versionada, como actions/checkout@v4.

2. Por que o cache de dependências é decisivo para a CI e como sua chave costuma ser invalidada?

Resposta

Sem cache, cada execução rebaixa todo o ~/.m2/~/.gradle, tornando o pipeline lento e caro e minando o feedback rápido. A chave do cache é derivada dos arquivos de build (ex.: pom.xml), então só é invalidada quando as dependências efetivamente mudam — mantendo builds rápidos na maioria dos pushes.

3. Em que sentido a suíte de testes é a “rede de segurança” do pipeline, e como isso se liga ao Princípio FIRST?

Resposta

A suíte automatizada (Testes e TDD) roda a cada push; se uma mudança quebra o comportamento, os testes falham, o job fica vermelho e o merge é bloqueado antes da regressão chegar ao main. O F de Fast garante que essa rede dê feedback em minutos — testes lentos corroem a prática porque ninguém espera pelo CI.

4. Qual a diferença entre CI e CD?

Resposta

CI (Continuous Integration) valida cada mudança automaticamente (build + testes a cada push), terminando com um artefato validado. CD é Continuous Delivery (artefato sempre pronto para deploy, promoção final acionada por humano) ou Continuous Deployment (todo artefato validado vai automaticamente a produção). Resumindo: CI garante que o código está bom; CD cuida de levá-lo ao ambiente.