Gerenciamento de Software Ágil

Aula: 05 - Governança de código e qualidade (critérios de aceite e testes)

  • Objetivos: Caracterizar governança de código e critérios de qualidade e testes.
  • Curso: CST em Análise e Desenvolvimento de Sistemas
  • Série: 4º Semestre
  • Disciplina: Gerenciamento de Projetos Ágil

5.1 Governança de código

5.1.1 Conceito e princípios

Governança de código é o conjunto de políticas, processos e práticas que orientam como o código é produzido, revisado, integrado e mantido. Em ambientes ágeis, a governança deve ser leve, automatizada e centrada em feedback rápido, garantindo qualidade sem criar burocracia excessiva.

Princípios orientadores: - Clareza: políticas simples, visíveis no repositório. - Automação: regra que pode ser automatizada não deve depender apenas de disciplina humana. - Ownership: responsabilidades explícitas sobre áreas do código. - Conformidade mínima necessária: proteções suficientes para reduzir risco, sem travar o fluxo.

5.1.2 Modelos de branching e suas implicações

  • Trunk-Based Development: commits frequentes na principal, feature flags para mudanças parciais, favorece integração contínua e lead time curto.
  • GitHub Flow: branches curtos a partir da principal, Pull Request (PR) obrigatório, deploy contínuo.
  • GitFlow: branches longa-vida (develop, release, hotfix). Útil para cadência de releases; aumenta complexidade.

Diretrizes práticas: - Nomes de branch padronizados: feature/-, fix/-. - Vida curta para branches de feature (dias, não semanas). - Merge via PR com squash para histórico limpo (ou convencional conforme necessidade de traceabilidade).

5.1.3 Proteções e permissões

  • Proteção de branch principal: exigir PR, revisões obrigatórias, checks de CI verdes, proibir force-push e commits diretos.
  • CODEOWNERS: define responsáveis por pastas/arquivos.
  • Revisões obrigatórias: número mínimo de revisores, requerer aprovação dos code owners.
  • Assinatura/validação de commits (DCO/GPG) quando exigências de compliance forem necessárias.

Exemplo de CODEOWNERS:

# .github/CODEOWNERS
*                       @org/engineering
/docs/**                @org/product @tech-writers
/infra/**               @devops-team
/src/backend/**         @backend-team
/src/frontend/**        @frontend-team

5.1.4 Padrões de commit e versionamento

  • Conventional Commits: padroniza mensagens para automação de changelog, versionamento semântico e detecção de breaking changes.
  • Semantic Versioning (SemVer): MAJOR.MINOR.PATCH.

Exemplos:

feat(auth): adiciona login via JWT
fix(api): trata nulos no endpoint /users (#123)
perf(cache)!: altera chave de cache (breaking change)

Configuração básica do commitlint:

// commitlint.config.cjs
module.exports = { extends: ['@commitlint/config-conventional'] };

5.1.5 Revisão de código eficaz

Objetivos: detectar defeitos, melhorar design, disseminar conhecimento e manter padrões.

Checklist sugerido: - Correção: a mudança resolve o problema/estória? Há testes cobrindo cenários críticos? - Simplicidade: há código morto, duplicado ou complexidade desnecessária? - Segurança: segredos expostos, validações, entradas confiáveis? - Performance: impactos em hot paths, alocações, N+1 queries? - Manutenibilidade: nomes claros, modularização, docs atualizados. - Conformidade: estilo (lint), padrões de commit, DoD.

Template de Pull Request:

<!-- .github/pull_request_template.md -->
## Contexto
Descreva o problema e o porquê da mudança.

## O que foi feito
- [ ] Item 1
- [ ] Item 2

## Como testar
Passos para validar localmente.

## Critérios de aceite
- [ ] CA1
- [ ] CA2

## Riscos e rollback
Riscos identificados e plano de reversão.

Resolves #<id>

5.1.6 Automatização de políticas (CI)

Automatize lint, testes, cobertura mínima, análise estática e verificação de mensagens de commit.

Exemplo de pipeline no GitHub Actions:

# .github/workflows/ci.yml
name: ci
on:
  pull_request:
    branches: [ "main" ]
jobs:
  build-test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run lint
      - run: npm test -- --coverage
      - name: Quality Gate (coverage >= 80%)
        shell: bash
        run: |
          COVERAGE=$(node -p "require('./coverage/coverage-summary.json').total.lines.pct")
          echo "Coverage: $COVERAGE%"
          THRESHOLD=80
          COVERAGE_INT=$(printf "%.0f" "$COVERAGE")
          if [ "$COVERAGE_INT" -lt "$THRESHOLD" ]; then
            echo "Coverage abaixo de $THRESHOLD%"; exit 1; fi

5.1.7 Documentação do repositório

  • README: visão geral, setup, execução, deploy.
  • CONTRIBUTING: fluxo de contribuição, padrões de commit, como abrir PRs.
  • CODE_OF_CONDUCT: normas de convivência.
  • Arquivos de configuração visíveis (.editorconfig, lint, formatador).

Exemplo simples de .editorconfig:

# .editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

5.1.8 Fluxo de governança (visão geral)

Diagrama 1

5.2 Qualidade: critérios de aceite e testes

5.2.1 Critérios de aceite (CA) bem definidos

Critérios de aceite descrevem comportamentos verificáveis que, quando satisfeitos, demonstram que a estória atende às necessidades. Boas práticas: - Clareza e testabilidade (Given-When-Then auxiliam). - Cobrir caminhos felizes e erros relevantes. - Evitar detalhes de implementação.

Exemplo em Gherkin:

Feature: Autenticação
  Scenario: Login válido
    Given que o usuário "ana@exemplo.com" existe e a senha é "Secreta!123"
    When ele envia credenciais válidas para "/api/login"
    Then a API retorna status 200
    And o corpo contém um token JWT válido com expiração em 15 minutos

  Scenario: Login inválido
    Given que o usuário "ana@exemplo.com" existe
    When ele envia senha incorreta para "/api/login"
    Then a API retorna status 401
    And a resposta não contém token

Relacionando CA com DoR/DoD: - DoR: estória pronta para planejar quando tem CA claros, dados de teste e dependências identificadas. - DoD: estória concluída quando CA passam, testes automatizados atualizados, revisão feita e documentação mínima entregue.

Exemplo de DoD focado em qualidade:

- Código revisado e aprovado (>= 1 owner).
- Lint sem erros; formatação aplicada.
- Testes unitários e integração cobrindo casos críticos.
- Cobertura de linhas >= 80% e mutação >= 60% (quando aplicável).
- Sem vulnerabilidades críticas abertas.
- Logs e métricas adicionados conforme necessidade.
- Documentação atualizada (README/CHANGELOG).

5.2.2 Pirâmide de testes e estratégia

  • Unidade: rápida, isolada, maior volume.
  • Integração: valida módulos/serviços se comunicando.
  • Contrato/API: garante compatibilidade entre serviços.
  • Funcional/E2E: valida jornada do usuário de ponta a ponta.
  • Não funcionais: performance, segurança, acessibilidade, confiabilidade.

Visualização: Diagrama 2

5.2.3 Testes unitários: exemplo

// src/sum.js
export function sum(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') throw new TypeError('invalid');
  return a + b;
}
// __tests__/sum.test.js
import { sum } from '../src/sum';

describe('sum', () => {
  it('soma números', () => {
    expect(sum(2, 3)).toBe(5);
  });

  it('lança erro para entradas inválidas', () => {
    expect(() => sum('2', 3)).toThrow('invalid');
  });
});

Configuração básica de cobertura no Jest:

// jest.config.cjs
module.exports = {
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageThreshold: { global: { lines: 80, branches: 70, functions: 80, statements: 80 } },
};

5.2.4 Testes de integração/contrato: exemplo

// __tests__/users.int.test.js
import request from 'supertest';
import app from '../src/app';

describe('GET /api/users/:id', () => {
  it('retorna 200 e payload esperado', async () => {
    const res = await request(app).get('/api/users/123').expect(200);
    expect(res.body).toMatchObject({ id: '123', name: expect.any(String) });
  });
});

Contrato OpenAPI como fonte de verdade:

# openapi.yaml (trecho)
paths:
  /api/users/{id}:
    get:
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

5.2.5 Testes funcionais (E2E) e flakiness

  • Use dados determinísticos e ambientes efêmeros quando possível.
  • Sincronize por condições de tela/estado, não por sleeps fixos.
  • Marque testes instáveis e trate-os como dívida.

Exemplo com Playwright:

// e2e/login.spec.ts
import { test, expect } from '@playwright/test';

test('login sucesso', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('ana@exemplo.com');
  await page.getByLabel('Senha').fill('Secreta!123');
  await page.getByRole('button', { name: 'Entrar' }).click();
  await expect(page.getByText('Bem-vinda, Ana')).toBeVisible();
});

5.2.6 Testes não funcionais

  • Performance: metas de latência/p95 e throughput; perfis por cenário de carga.
  • Acessibilidade: WCAG AA como referência.
  • Segurança: SAST/DAST/SCA integrados à pipeline.

Exemplos de ferramentas: - Performance: k6, JMeter. - Acessibilidade: axe-core, pa11y. - Segurança: Semgrep (SAST), OWASP ZAP (DAST), scanners de dependência.

Script k6 simples:

// perf/login.k6.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = { vus: 20, duration: '1m', thresholds: { http_req_duration: ['p(95)<500'] } };

export default function () {
  const res = http.post('https://api.exemplo.com/login', { user: 'ana', pass: 'Secreta!123' });
  check(res, { 'status 200': (r) => r.status === 200 });
  sleep(1);
}

5.2.7 Qualidade automatizada e gates

  • Análise estática: code smells, complexidade, duplicações.
  • Cobertura mínima: linhas/branches.
  • Debt ratio: acompanhar evolução da dívida técnica.

Exemplo de configuração SonarQube:

# sonar-project.properties
sonar.projectKey=org.exemplo.app
sonar.sources=src
sonar.tests=__tests__
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.qualitygate.wait=true

5.2.8 Integração de qualidade no fluxo

Integre critérios de aceite e testes desde o refinamento: - Durante o refinamento: escrever CA com o time, identificar riscos e dados de teste. - Durante a implementação: TDD/BDD quando aplicável; criar testes a partir dos CA. - Na revisão/CI: checks automatizados verificam padrões, cobertura e análise estática. - Após merge: monitorar métricas em produção (erros, latência) e alimentar melhoria contínua.

Diagrama 3

5.2.9 Indicadores úteis

  • Defeitos escapados por release.
  • Taxa de aprovação de PR na primeira tentativa.
  • Tempo médio de revisão de PR.
  • Cobertura e mutação ao longo do tempo.
  • DORA (lead time, frequência de deploy, taxa de falha de mudança, MTTR) como visão holística.

5.2.10 Exemplo de mapeamento CA → testes

  • CA: “Retornar 401 para login inválido” → Teste de integração validando status e corpo.
  • CA: “Exibir mensagem de boas-vindas após login” → Teste E2E validando UI.
  • CA: “Tempo p95 < 500ms no login” → Teste de performance com threshold.
  • CA: “Sem segredos no repositório” → Check automatizado (secret scanning) na CI.

Exercícios propostos

  • Exercício 1: Crie um CODEOWNERS adequado à estrutura de um projeto de exemplo e justifique as escolhas.
  • Exercício 2: Escreva critérios de aceite em Gherkin para a funcionalidade “Recuperar senha”.
  • Exercício 3: Configure um pipeline CI mínimo com lint, testes e gate de cobertura de 80%.
  • Exercício 4: Elabore um checklist de revisão de código de até 10 itens alinhado ao contexto do seu time.
  • Exercício 5: Defina um DoD focado em qualidade para um microserviço HTTP simples.

Conclusão

Nesta aula, estruturamos a governança de código como um conjunto de políticas leves e automatizadas que protegem o fluxo de desenvolvimento sem engessá-lo. Vimos como padrões de commit, proteção de branches, revisão por pares e documentação do repositório sustentam a qualidade. Conectamos critérios de aceite claros à pirâmide de testes e aos gates automatizados da pipeline, cobrindo testes unitários, integração, E2E e não funcionais. Por fim, relacionamos métricas de qualidade e práticas de integração contínua que promovem previsibilidade e confiança nas entregas.

Próxima aula

Dependências e vulnerabilidades; operação e gerenciamento de incidentes. Veremos como identificar e mitigar riscos em bibliotecas de terceiros (SCA), aplicar varreduras de segurança (SAST/DAST), e organizar resposta a incidentes para manter confiabilidade operacional.