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:
import os, sys
def adicionar(item, lista=[]):
try:
lista.append(item)
except:
pass
return lista
uv run ruff check exemplos/qualidade/antes.py --output-format concise
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:
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")
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:
[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"]
| Grupo | O que verifica |
|---|---|
E | Estilo e erros de sintaxe (pycodestyle) |
F | Erros lógicos simples, como nomes não usados (pyflakes) |
I | Ordem dos imports |
B | Armadilhas prováveis de bug (flake8-bugbear) |
UP | Sintaxe 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:
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:
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:
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.