Pular para o conteúdo

    Capítulo 50, Avançado

    Qualidade e automação

    O que não é verificado por uma máquina é esquecido por um humano. Este capítulo monta o conjunto mínimo de verificações que eu coloco em qualquer projeto.

    Código deste capítulo: avancado/cap50_qualidade.py

    Lint e formatação com ruff

    O ruff une linter e formatador em uma ferramenta única e muito rápida. O linter acha erros e padrões suspeitos. O formatador padroniza o estilo, o que acaba as discussões sobre vírgulas em revisão de código. Veja um código com problemas reais:

    exemplos/qualidade/antes.py
    import os, sys
    
    
    def adicionar(item, lista=[]):
        try:
            lista.append(item)
        except:
            pass
        return lista
    
    Terminal
    uv run ruff check exemplos/qualidade/antes.py --output-format concise
    
    Saída
    exemplos/qualidade/antes.py:1:1: E401 [*] Multiple imports on one line
    exemplos/qualidade/antes.py:1:1: I001 [*] Import block is un-sorted or un-formatted
    exemplos/qualidade/antes.py:1:8: F401 [*] `os` imported but unused
    exemplos/qualidade/antes.py:1:12: F401 [*] `sys` imported but unused
    exemplos/qualidade/antes.py:4:27: B006 Do not use mutable data structures for argument defaults
    exemplos/qualidade/antes.py:7:5: E722 Do not use bare `except`
    Found 6 errors.
    [*] 4 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
    

    O ruff apontou seis achados: duas importações não usadas, imports na mesma linha e fora de ordem, um valor padrão mutável (o bug do capítulo 18) e um except que engole qualquer erro. Os quatro marcados com [*] ele corrige sozinho com --fix. Os outros dois, o valor mutável e o except vazio, exigem uma decisão sua, porque mudam o comportamento. A versão corrigida:

    avancado/cap50_qualidade.pylinhas 10 a 19
    def adicionar(item, lista=None):
        if lista is None:
            lista = []
        lista.append(item)
        return lista
    
    
    assert adicionar(1) == [1]
    assert adicionar(2) == [2]
    print("sem os problemas apontados")
    
    Saída
    sem os problemas apontados
    

    Configuração em um só lugar

    Eu escolho explicitamente os grupos de regras, para que a configuração seja uma decisão e não o padrão da ferramenta. O mypy fica no mesmo arquivo:

    exemplos/qualidade/pyproject.toml
    [tool.ruff]
    line-length = 100
    target-version = "py312"
    
    [tool.ruff.lint]
    select = ["E", "F", "I", "B", "UP"]
    
    [tool.mypy]
    python_version = "3.12"
    strict = true
    files = ["src"]
    
    GrupoO que verifica
    EEstilo e erros de sintaxe (pycodestyle)
    FErros lógicos simples, como nomes não usados (pyflakes)
    IOrdem dos imports
    BArmadilhas prováveis de bug (flake8-bugbear)
    UPSintaxe antiga que pode ser modernizada

    Antes do commit: pre-commit

    O pre-commit roda as verificações no momento do commit, e impede que o problema chegue ao repositório. Instale com uv tool install pre-commit e ative com pre-commit install:

    exemplos/qualidade/.pre-commit-config.yaml
    repos:
      - repo: https://github.com/astral-sh/ruff-pre-commit
        rev: v0.12.0   # atualize com: pre-commit autoupdate
        hooks:
          - id: ruff-check
            args: [--fix]
          - id: ruff-format
    

    Depois do push: integração contínua

    O pre-commit é uma conveniência local e pode ser pulado. A garantia é o CI, que roda em um ambiente limpo a cada push e pull request. Este fluxo do GitHub Actions usa uv e testa três versões do Python:

    exemplos/qualidade/ci.yml
    name: ci
    
    on:
      push:
        branches: [main]
      pull_request:
    
    jobs:
      qualidade:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            python: ["3.12", "3.13", "3.14"]
        steps:
          - uses: actions/checkout@v4
          - uses: astral-sh/setup-uv@v6
            with:
              python-version: ${{ matrix.python }}
          - run: uv sync --locked
          - run: uv run ruff check .
          - run: uv run ruff format --check .
          - run: uv run mypy
          - run: uv run pytest -q
    

    Confira as versões das actions

    As versões das actions (actions/checkout, astral-sh/setup-uv) evoluem. Antes de copiar este arquivo, confira a versão mais recente de cada uma na página da própria action. O formato do fluxo muda pouco, mas as tags mudam.

    Cobertura

    A cobertura mede quais linhas os testes executam. Ela diz o que não está testado, e não que o que está testado esteja correto. Eu a uso para descobrir buracos, e não como meta numérica:

    Terminal
    uv add --dev pytest-cov
    uv run pytest --cov=calc_notes --cov-report=term-missing
    

    Exercício 1

    Teste de regressão

    Para a função adicionar corrigida, escreva um teste que falharia na versão com valor padrão mutável.