Trilha de aprendizado · Nível 15 · Tutorial 9

Automatizar as verificações do projeto com Nox

Criar uma rotina reproduzível que execute formatação, lint, tipagem, testes e validação da distribuição, sinalizando falhas antes da entrega.

  • Nível: Avançado
  • Duração: 25 min
  • 8 passos
Automatizar as verificações do projeto com Nox

O que você vai percorrer

  1. Criar a primeira sessão Nox Crie um noxfile.py mínimo e execute uma sessão isolada que instala o Ruff e consulta sua versão. 3 min
  2. Declarar dependências e centralizar configurações Separe ferramentas de verificação das dependências da aplicação e mantenha cada configuração no lugar responsável por ela. 3 min
  3. Verificar formatação e lint sem corrigir arquivos Crie sessões Ruff de validação que detectam desvios, preservam os arquivos-fonte e sinalizam falhas pelo código de saída. 3 min
  4. Executar tipagem e testes com dependências explícitas Adicione sessões independentes para verificar tipos e executar a suíte unitária sem depender das ferramentas ou do projeto instalados no seu ambiente de desenvolvimento. 3 min
  5. Encadear a construção e a validação da distribuição Monte uma sessão de entrega que cria artefatos novos e valida a wheel exata que acabou de ser construída. 4 min
  6. Validar as versões de Python selecionadas Parametrize as sessões que executam a aplicação e transforme a ausência de um interpretador exigido em falha explícita. 3 min
  7. Definir um comando único de validação Configure a execução padrão do Nox para validar a entrega inteira com um único comando reproduzível. 3 min
  8. Consolidar e testar a rotina completa Reúna a configuração, execute a validação completa, provoque uma falha de formatação controlada e confirme que a rotina se recupera sem alterar o código automaticamente. 4 min

O que você vai aprender

  • Definir sessões Nox com dependências e comandos explícitos.
  • Automatizar verificações de Ruff, mypy e pytest sem alterar silenciosamente os arquivos analisados.
  • Encadear a construção do artefato com seus testes de instalação e execução.
  • Executar a rotina nas versões de Python selecionadas para a compatibilidade declarada.

Antes de começar

  • Testar a aplicação instalada em um ambiente limpo
  • Padronizar a formatação do código com Ruff
  • Analisar estilo e possíveis defeitos com Ruff
  • Verificar anotações com mypy
  • Recriar ambientes a partir de requirements.txt

Passo 1 de 8

Criar a primeira sessão Nox

Crie um noxfile.py mínimo e execute uma sessão isolada que instala o Ruff e consulta sua versão.

Nox coordena; as ferramentas verificam

Uma camada de orquestração

O Nox lê um arquivo chamado noxfile.py e executa as sessões declaradas nele. Cada sessão prepara um ambiente isolado, instala o que precisa e roda comandos.

Ele não substitui as ferramentas: Ruff continua formatando e analisando código; mypy verifica tipos; pytest executa testes; build cria artefatos. O papel do Nox é tornar essa sequência explícita e reproduzível.

Ambientes separados, projeto compartilhado

A sessão usa seu próprio ambiente, mas trabalha sobre os mesmos arquivos do projeto.

Diagrama com a pasta de um projeto Python compartilhada entre um ambiente local que executa Nox e um ambiente isolado de uma sessão contendo Ruff.

O Nox é iniciado no seu ambiente local; cada sessão recebe um ambiente isolado e acessa a árvore compartilhada do projeto.

Prepare uma base local mínima

Crie estes arquivos na mesma pasta

No computador, crie uma pasta vazia para a prática. Dentro dela, crie pyproject.toml e src/saudacao/__init__.py com os conteúdos abaixo. Esta base é instalável, mas nesta primeira sessão ainda não vamos instalar nem testar o pacote.

pyproject.toml

Metadados mínimos para identificar o projeto.

toml
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "saudacao-nox"
version = "0.1.0"
requires-python = ">=3.11"

[tool.setuptools.packages.find]
where = ["src"]

src/saudacao/__init__.py

Um módulo mínimo compartilhado pela futura rotina de verificações.

python
def mensagem(nome: str) -> str:
    return f"Olá, {nome}!"

Dica

Onde executar os comandos

Abra o terminal na raiz do projeto: a pasta que contém pyproject.toml. Os próximos arquivos e comandos pressupõem essa localização.

Declare e execute a primeira sessão

Três ações explícitas

No noxfile.py, o decorador @nox.session registra uma função como sessão. Dentro dela, session.install(...) instala dependências no ambiente da sessão. Já session.run(...) executa um comando nesse ambiente.

Vamos apenas consultar a versão do Ruff. Isso confirma o isolamento e a instalação sem iniciar as verificações de qualidade ainda.

noxfile.py

Crie este arquivo na raiz do projeto, ao lado de pyproject.toml.

python
import nox


@nox.session
def ruff_version(session: nox.Session) -> None:
    session.install("ruff==0.9.10")
    session.run("ruff", "--version")

Instalar Nox e chamar a sessão

Crie e ative, se desejar, um ambiente virtual dedicado ao Nox. Então instale uma versão definida e execute os comandos a partir da raiz do projeto.

bash
python -m venv .venv-nox

# macOS/Linux
source .venv-nox/bin/activate

# Windows (PowerShell)
# .venv-nox\Scripts\Activate.ps1

python -m pip install "nox==2025.2.9"
python -m nox -l
python -m nox -s ruff_version

Dica

O que observar

A listagem deve mostrar ruff_version. Na execução, o Nox cria ou reutiliza o ambiente da sessão, instala a versão fixada do Ruff e exibe a versão retornada pelo comando. Nenhum arquivo-fonte do projeto deve ser alterado por essa sessão.

Associe cada parte à responsabilidade

Componentes da primeira sessão

Relacione cada elemento à sua função.

Toque em um item e depois no par correspondente.

Passo 2 de 8

Declarar dependências e centralizar configurações

Separe ferramentas de verificação das dependências da aplicação e mantenha cada configuração no lugar responsável por ela.

O que pertence a cada lugar

Execução não é verificação

As dependências em [project.dependencies] são necessárias para quem instala e usa sua aplicação. Já Nox, Ruff, mypy e pytest servem para desenvolver e validar o projeto; mantenha suas versões em arquivos de requisitos de verificação, separados.

Fixar uma versão direta, como ruff==0.9.10, torna a ferramenta escolhida explícita. O próprio Nox também deve ter uma versão controlada no ambiente local que o executa.

Separação de responsabilidades

Diagrama com três áreas: dependências da aplicação no pyproject.toml, arquivos de requisitos de verificação com Ruff, mypy e pytest, e o Nox coordenando ambientes isolados por sessão.

A aplicação declara o que precisa para rodar; cada sessão instala apenas a ferramenta necessária para sua verificação.

Dica

Instalação mínima por sessão

Uma sessão de lint não precisa instalar pytest, e uma sessão de testes não precisa instalar mypy. Declare somente o que o comando daquela sessão realmente usa; isso reduz acoplamento e torna falhas de preparação mais fáceis de diagnosticar.

Arquivos explícitos e configuração central

requirements de verificação

Crie estes arquivos no projeto. As versões são exemplos: escolha e atualize versões de acordo com a política do seu projeto.

text
# requirements/nox.txt
nox==2025.2.9

# requirements/lint.txt
ruff==0.9.10

# requirements/typecheck.txt
mypy==1.15.0

# requirements/test.txt
pytest==8.3.5

pyproject.toml: aplicação e opções das ferramentas

Mantenha as opções das ferramentas no pyproject.toml. O Nox coordena instalações e comandos, mas não deve virar um segundo arquivo de configuração do Ruff, mypy ou pytest.

toml
[project]
name = "tarefas-cli"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "platformdirs>=4.0,<5",
]

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I"]

[tool.mypy]
python_version = "3.11"
files = ["src"]
strict = true

[tool.pytest.ini_options]
testpaths = ["tests/unit"]
addopts = "-q"

noxfile.py: apenas a orquestração

Cada sessão instala seu requisito direto. Os nomes dos alvos ficam explícitos; a implementação dos comandos de verificação será acrescentada nos próximos steps.

python
import nox


@nox.session
def format_check(session: nox.Session) -> None:
    session.install("-r", "requirements/lint.txt")
    # A verificação de formatação será incluída no próximo step.


@nox.session
def lint(session: nox.Session) -> None:
    session.install("-r", "requirements/lint.txt")
    # O comando de lint será incluído no próximo step.


@nox.session
def typecheck(session: nox.Session) -> None:
    session.install("-r", "requirements/typecheck.txt")
    # O comando de tipagem será incluído em seguida.


@nox.session
def test(session: nox.Session) -> None:
    session.install("-r", "requirements/test.txt")
    session.install(".")
    # Os testes unitários serão incluídos em seguida.


@nox.session
def package(session: nox.Session) -> None:
    session.install("-r", "requirements/test.txt")
    # Construção e testes de aceitação da wheel serão incluídos depois.

Limites e separação das suítes

Não misture os testes de aceitação aos unitários

Configure a sessão test para a suíte unitária, por exemplo em tests/unit. Mantenha os testes de aceitação da distribuição em outro alvo, como tests/acceptance, reservado à futura sessão package.

Esses testes dependem de uma wheel construída na própria execução; portanto, executá-los junto dos unitários poderia tentar validá-los antes de existir um artefato.

Atenção

Reprodução tem fronteiras

Ambientes isolados e versões diretas fixadas melhoram a repetição, mas não congelam tudo: dependências transitivas, interpretadores disponíveis, sistema operacional e o ambiente isolado usado pelo backend de construção continuam influenciando o resultado. Não conclua compatibilidade total apenas porque uma sessão foi aprovada em uma máquina.

Prática: classifique a declaração

Complete os destinos

Complete com os três destinos abaixo, separados por vírgulas e nesta ordem: [project.dependencies], requirements/lint.txt, [tool.ruff].

A dependência de execução platformdirs pertence a _; a versão ruff==0.9.10 pertence a _; e line-length = 88 pertence a ___.

Passo 3 de 8

Verificar formatação e lint sem corrigir arquivos

Crie sessões Ruff de validação que detectam desvios, preservam os arquivos-fonte e sinalizam falhas pelo código de saída.

Duas verificações, nenhuma correção automática

Validação não é correção

Mantenha duas sessões: uma confere a formatação e outra executa o lint. Na rotina de entrega, use ruff format --check, que apenas compara o arquivo com a formatação esperada, e ruff check, sem --fix, que apenas relata problemas.

Assim, um desvio produz uma falha visível em vez de uma alteração silenciosa no código analisado.

O que a sessão verifica

Cada sessão cria seu próprio ambiente, executa o Ruff e lê os arquivos compartilhados do projeto. Os comandos de verificação não reescrevem esses arquivos.

Diagrama mostrando arquivos-fonte compartilhados no centro, duas sessões isoladas de Nox verificando formatação e lint, e uma falha retornando sem uma seta de escrita para os arquivos.

Ambientes e caches podem ser criados pelo Nox ou pelas ferramentas; isso é diferente de modificar o código-fonte.

Dica

Separe os modos de uso

Durante o desenvolvimento, você pode rodar deliberadamente ruff format . ou ruff check --fix .. Não coloque esses comandos nas sessões que validam uma entrega: elas precisam revelar o desvio, não escondê-lo.

Sessões de formatação e lint

Declare comandos explícitos

No noxfile.py, use o arquivo de requisitos de verificação que você organizou no step anterior. Abaixo, ele está em requirements/verificacao.txt; ajuste somente esse caminho se o seu projeto adotou outro nome.

noxfile.py

Adicione estas sessões ao arquivo. Elas instalam somente o Ruff e executam verificações sem opções de correção.

python
import nox


@nox.session
def format_check(session):
    session.install("-r", "requirements/verificacao.txt")
    session.run("ruff", "format", "--check", ".")


@nox.session
def lint(session):
    session.install("-r", "requirements/verificacao.txt")
    session.run("ruff", "check", ".")

Exemplo

Quando uma verificação falha

Se ruff format --check . encontrar um arquivo fora do padrão, o Ruff retorna código diferente de zero. Como session.run aceita apenas sucesso por padrão, ele interrompe a sessão format_check naquele ponto e o Nox a marca como falha. O arquivo permanece como estava: não há --fix nem execução de ruff format ..

Observe uma falha sem reescrita

Experimento controlado

Crie temporariamente o arquivo formatacao_demo.py na raiz do projeto com o conteúdo abaixo. Em seguida, execute python -m nox -s format_check. Compare o arquivo com o conteúdo exibido: a sessão deve falhar, mas o arquivo deve continuar idêntico ao que você criou.

formatacao_demo.py antes da verificação

Este arquivo está propositalmente fora da formatação esperada.

python
def somar(valores:list[int])->int:
 return sum(valores)

Comandos para verificar e restaurar

Depois de observar a falha, restaure o arquivo manualmente para a versão formatada abaixo — ou apague o arquivo temporário — antes de continuar o projeto.

bash
python -m nox -s format_check

# Versão formatada, se quiser manter o arquivo:
# def somar(valores: list[int]) -> int:
#     return sum(valores)

Relate o resultado

Após executar a sessão com o arquivo propositalmente mal formatado, qual foi o resultado esperado para a sessão e para o conteúdo de formatacao_demo.py?

Escreva pelo menos 40 caracteres (0/40).

Escolha o comando de validação

Formatação somente para conferência

Qual comando deve estar na sessão que valida a formatação sem modificar os arquivos?

Resumo

Essencial deste step

  • Use sessões distintas para formatação e lint, com dependências instaladas explicitamente.
  • A validação usa ruff format --check e ruff check sem --fix.
  • Um código de saída diferente de zero em session.run faz a sessão falhar; não corrige o arquivo analisado.
  • Ambientes isolados e caches podem existir, mas isso não equivale a reescrever o código-fonte.

Passo 4 de 8

Executar tipagem e testes com dependências explícitas

Adicione sessões independentes para verificar tipos e executar a suíte unitária sem depender das ferramentas ou do projeto instalados no seu ambiente de desenvolvimento.

Dois ambientes, duas responsabilidades

Isole a preparação de cada verificação

Uma sessão Nox deve instalar apenas o que precisa para seu trabalho. A sessão de mypy verifica anotações nos módulos escolhidos. A sessão de pytest testa comportamentos; se os testes fazem import do seu pacote, ela também instala o projeto no ambiente isolado.

Não inclua aqui os testes de aceitação da distribuição: eles dependem de um artefato construído e serão encadeados em outra sessão.

Fluxo das sessões

Cada sessão prepara seu próprio ambiente antes de executar seu comando.

Diagrama com dois ambientes Nox isolados. O ambiente mypy recebe mypy, possíveis stubs e dependências de análise, então executa análise sobre src. O ambiente pytest recebe pytest e o projeto local instalado, então executa testes unitários em tests.

Mypy analisa alvos definidos; pytest executa a suíte unitária contra o pacote instalado no ambiente da sessão.

Declare instalações e alvos no noxfile

Sessões explícitas

Partindo dos requisitos de verificação organizados anteriormente, acrescente sessões como estas ao seu noxfile.py. Ajuste src e tests/unit se os diretórios do seu projeto tiverem outros nomes.

A instalação session.install(".") usa o projeto local no ambiente da sessão. Ela é necessária quando os testes importam o pacote, especialmente em layout src.

noxfile.py

python
import nox


@nox.session
def typecheck(session: nox.Session) -> None:
    session.install("-r", "requirements/typecheck.txt")
    session.run("mypy", "src")


@nox.session
def tests(session: nox.Session) -> None:
    session.install("-r", "requirements/tests.txt")
    session.install(".")
    session.run("pytest", "tests/unit")

Exemplo

Requisitos separados

Exemplo de divisão mínima:

requirements/typecheck.txt

mypy==1.13.0
types-requests==2.32.0.20241016

requirements/tests.txt

pytest==8.3.3

Inclua stubs ou bibliotecas adicionais em requirements/typecheck.txt somente se o mypy precisar deles para analisar seus imports. As opções das ferramentas continuam no pyproject.toml; o noxfile apenas prepara e aciona cada verificação.

Execute e localize a origem da falha

Rode sessões individualmente durante o desenvolvimento

Na raiz do projeto, execute:

python -m nox -s typecheck
python -m nox -s tests

Se uma chamada de session.install(...) falhar, o problema ocorreu na preparação do ambiente: requisito inexistente, versão incompatível ou erro ao instalar o projeto. Se mypy ou pytest iniciar e retornar código não zero, a falha pertence à verificação indicada no diagnóstico. Nox preserva esse resultado como falha da sessão; não o oculte.

Leia o ponto de falha

Considere este resumo:

nox > Running session tests
nox > python -m pip install .
ERROR: Package 'agenda-cli' requires a different Python
nox > Command python -m pip install . failed with exit code 1

Explique se a falha é de preparação ou de teste e qual seria sua primeira investigação.

Escreva pelo menos 80 caracteres (0/80).

Fixe a fronteira de cada sessão

Resumo

O que esta etapa adiciona

  • typecheck instala mypy e os stubs ou dependências necessários, depois analisa os alvos definidos.
  • tests instala pytest e o projeto local quando a suíte importa o pacote.
  • A suíte unitária fica separada dos testes que validam uma distribuição instalada.
  • O comando que falhou diferencia erro de instalação, erro de tipagem e falha de teste; não suprima seu código de saída.

Planeje sua execução local

No seu projeto, os testes em tests/unit importam módulos do pacote localizado em src. Descreva os dois comandos para executar as sessões e justifique a instalação explícita do projeto na sessão tests.

Escreva pelo menos 100 caracteres (0/100).

Passo 5 de 8

Encadear a construção e a validação da distribuição

Monte uma sessão de entrega que cria artefatos novos e valida a wheel exata que acabou de ser construída.

Uma cadeia de entrega, não sessões soltas

A ordem cria a dependência

A sessão delivery deve conter toda a cadeia: instalar as ferramentas de verificação, construir a sdist e a wheel em uma pasta nova, localizar uma única wheel e executar os testes de aceitação apontando para ela.

Como session.run() interrompe a sessão quando um comando retorna código diferente de zero, uma falha no build impede automaticamente os testes seguintes. Isso é mais confiável do que supor uma ordem entre sessões independentes.

Fluxo da sessão de entrega

O ambiente da sessão contém build e pytest; o ambiente temporário criado pelo teste contém somente a aplicação instalada e suas dependências de execução.

Diagrama mostrando fontes do projeto, um ambiente da sessão Nox que constrói uma wheel, uma variável WHEEL_PATH apontando para a wheel e um ambiente limpo separado que instala e executa a aplicação.

A wheel recém-criada atravessa a fronteira entre o ambiente de validação e o ambiente limpo da aplicação.

Atenção

Não reutilize dist/

Não construa em uma pasta persistente como dist/ e depois escolha “a primeira” wheel encontrada. Um artefato antigo pode ser selecionado e dar uma aprovação enganosa. Crie um diretório temporário de saída por execução e exija exatamente uma wheel.

Sessão Nox para a entrega

Ferramentas explícitas e caminho absoluto

Crie um arquivo de requisitos específico para a entrega. Ele controla as versões de build e pytest usadas no ambiente da sessão, sem adicioná-las às dependências normais da aplicação.

A sessão cria uma pasta temporária, constrói nela e transforma o caminho da wheel em caminho absoluto antes de passá-lo como WHEEL_PATH.

requirements/delivery.txt

Crie este arquivo no projeto.

text
build==1.2.2.post1
pytest==8.3.4

Sessão delivery no noxfile.py

Adicione esta sessão ao noxfile.py. Ajuste somente o caminho de tests/acceptance se sua suíte estiver em outro local.

python
from pathlib import Path

import nox


@nox.session
def delivery(session: nox.Session) -> None:
    """Constrói a distribuição e testa a wheel recém-gerada."""
    session.install("-r", "requirements/delivery.txt")

    output_dir = Path(session.create_tmp()) / "artifacts"
    output_dir.mkdir()

    session.run(
        "python",
        "-m",
        "build",
        "--outdir",
        str(output_dir),
    )

    wheels = list(output_dir.glob("*.whl"))
    if len(wheels) != 1:
        session.error(
            f"Esperava exatamente uma wheel em {output_dir}, "
            f"mas encontrei {len(wheels)}: {wheels}"
        )

    wheel_path = str(wheels[0].resolve())
    session.run(
        "pytest",
        "tests/acceptance",
        env={"WHEEL_PATH": wheel_path},
    )

O teste consumidor aceita somente a wheel indicada

Contrato da suíte de aceitação

O teste lê WHEEL_PATH, rejeita um valor ausente ou que não aponte para uma wheel existente e instala exatamente esse arquivo. O diretório de trabalho fica fora da árvore do projeto e PYTHONPATH é removido: assim, as fontes locais não podem substituir a distribuição instalada.

O exemplo usa o comando tarefas, definido anteriormente em [project.scripts]. Se o seu projeto usa outro ponto de entrada, substitua apenas esse nome e os argumentos esperados.

tests/acceptance/test_installed_wheel.py

Este teste é autocontido: ele cria o ambiente limpo, instala a wheel recebida e executa o comando instalado.

python
import os
import subprocess
import sys
import venv
from pathlib import Path

import pytest


def executable_in(venv_dir: Path, name: str) -> Path:
    directory = "Scripts" if os.name == "nt" else "bin"
    suffix = ".exe" if os.name == "nt" and name != "python" else ""
    return venv_dir / directory / f"{name}{suffix}"


def test_wheel_instalada_exibe_ajuda(tmp_path: Path) -> None:
    wheel_value = os.environ.get("WHEEL_PATH")
    if not wheel_value:
        pytest.fail("WHEEL_PATH não foi definida pela sessão de entrega")

    wheel_path = Path(wheel_value)
    if not wheel_path.is_absolute() or not wheel_path.is_file():
        pytest.fail(f"WHEEL_PATH deve apontar para uma wheel existente: {wheel_path}")
    if wheel_path.suffix != ".whl":
        pytest.fail(f"O artefato informado não é uma wheel: {wheel_path}")

    app_environment = tmp_path / "app-env"
    venv.EnvBuilder(with_pip=True).create(app_environment)
    python = executable_in(app_environment, "python")
    command = executable_in(app_environment, "tarefas")

    clean_environment = os.environ.copy()
    clean_environment.pop("PYTHONPATH", None)

    subprocess.run(
        [str(python), "-m", "pip", "install", str(wheel_path)],
        cwd=tmp_path,
        env=clean_environment,
        check=True,
        text=True,
        capture_output=True,
        timeout=60,
    )
    result = subprocess.run(
        [str(command), "--help"],
        cwd=tmp_path,
        env=clean_environment,
        text=True,
        capture_output=True,
        timeout=30,
    )

    assert result.returncode == 0, result.stderr
    assert "uso:" in result.stdout.lower()

Verifique a cadeia e suas evidências

Ordene a sessão de entrega

Coloque as ações na ordem em que devem ocorrer dentro da sessão delivery.

  1. Localizar e validar que há exatamente uma wheel
  2. Construir sdist e wheel em um diretório temporário novo
  3. Instalar build e pytest no ambiente isolado da sessão
  4. Passar o caminho absoluto em WHEEL_PATH e executar a suíte de aceitação

Evidência de validação real

Quais duas evidências mostrariam que sua suíte validou a wheel recém-construída, e não as fontes locais ou uma wheel antiga?

Escreva pelo menos 80 caracteres (0/80).

Passo 6 de 8

Validar as versões de Python selecionadas

Parametrize as sessões que executam a aplicação e transforme a ausência de um interpretador exigido em falha explícita.

Escolha uma matriz verificável

Compatibilidade declarada não é compatibilidade testada

Considere um projeto com requires-python = ">=3.10". A rotina deve incluir obrigatoriamente o Python 3.10, pois ele é o limite mínimo prometido. Selecione também versões concretas que você realmente consegue executar, por exemplo 3.10 e 3.12.

O Python que inicia o Nox pode ser diferente dos interpretadores das sessões. Ferramentas estáticas, como Ruff e mypy, podem usar um único interpretador de referência — aqui, 3.12. Já os testes e a validação da distribuição devem rodar na matriz escolhida, porque executam a aplicação.

Três papéis para interpretadores

Diagrama com um interpretador iniciando Nox, uma sessão estática isolada baseada em Python 3.12 e duas ramificações de testes e entrega baseadas em Python 3.10 e 3.12.

O interpretador do Nox, o interpretador de referência estático e os interpretadores que executam a aplicação têm responsabilidades distintas.

Parametrize as sessões que exercitam a aplicação

Uma sessão por versão

O argumento python de @nox.session expande uma função em sessões independentes. Com a matriz abaixo, python -m nox -l mostrará, entre outras, tests-3.10, tests-3.12, delivery-3.10 e delivery-3.12.

A sessão delivery continua construindo e validando a wheel como no passo anterior. Como o pytest dessa sessão é executado no ambiente Nox parametrizado, o teste de aceitação deve criar o ambiente limpo da aplicação a partir do interpretador atual dele — por exemplo, usando sys.executable ou venv.EnvBuilder dentro do teste. Assim, uma entrega em delivery-3.10 é instalada e executada com Python 3.10, não apenas analisada por outra versão.

Trecho de noxfile.py

Ajuste os comandos e os arquivos de requisitos aos que você já criou nas sessões anteriores.

python
from pathlib import Path

import nox

PYTHONS = ["3.10", "3.12"]
REFERENCE_PYTHON = "3.12"

# Uma versão selecionada, mas indisponível, não pode ser ignorada.
nox.options.error_on_missing_interpreters = True


@nox.session(python=REFERENCE_PYTHON)
def format_check(session: nox.Session) -> None:
    session.install("-r", "requirements/quality.txt")
    session.run("ruff", "format", "--check", "src", "tests")


@nox.session(python=REFERENCE_PYTHON)
def lint(session: nox.Session) -> None:
    session.install("-r", "requirements/quality.txt")
    session.run("ruff", "check", "src", "tests")


@nox.session(python=REFERENCE_PYTHON)
def typecheck(session: nox.Session) -> None:
    session.install("-r", "requirements/typecheck.txt")
    session.run("mypy", "src")


@nox.session(python=PYTHONS)
def tests(session: nox.Session) -> None:
    session.install("-r", "requirements/test.txt")
    session.install(".")
    session.run("pytest", "tests/unit")


@nox.session(python=PYTHONS)
def delivery(session: nox.Session) -> None:
    session.install("build", "pytest")
    output = Path(session.create_tmp()) / "dist"
    output.mkdir()
    session.run("python", "-m", "build", "--outdir", str(output))

    wheels = list(output.glob("*.whl"))
    if len(wheels) != 1:
        session.error("A construção atual deve produzir exatamente uma wheel.")

    session.run(
        "pytest",
        "tests/acceptance",
        env={"WHEEL_PATH": str(wheels[0].resolve())},
    )

Dica

Exija os interpretadores

Você pode definir a política no arquivo, com nox.options.error_on_missing_interpreters = True, ou aplicá-la na chamada: python -m nox --error-on-missing-interpreters. Sem isso, uma sessão de uma versão ausente pode ser ignorada; ela não vira evidência de compatibilidade.

Diagnostique uma matriz incompleta

Versão mínima obrigatória

O projeto declara requires-python = ">=3.10", a matriz contém ["3.10", "3.12"] e o computador não possui Python 3.10. Qual conclusão é correta ao executar a rotina com erro para interpretadores ausentes?

Planeje sua execução

Sua matriz e sua evidência

Para um projeto que declara requires-python = ">=3.10", proponha uma matriz concreta, diga quais sessões devem ser parametrizadas e explique por que um interpretador ausente impede a aprovação da rotina.

Escreva pelo menos 120 caracteres (0/120).

Passo 7 de 8

Definir um comando único de validação

Configure a execução padrão do Nox para validar a entrega inteira com um único comando reproduzível.

Um padrão para a validação completa

Sessões padrão do Nox

Defina em noxfile.py quais sessões formam a validação obrigatória. Ao executar Nox sem -s, ele usará nox.options.sessions.

Inclua as verificações estáticas uma vez, no interpretador de referência, e as sessões parametrizadas de teste e entrega para cada versão que você decidiu cobrir. Os nomes com parênteses correspondem às expansões da matriz.

Conjunto padrão de sessões

No início do noxfile.py, depois de importar nox:

python
import nox

nox.options.sessions = (
    "format",
    "lint",
    "typecheck",
    "tests-3.11",
    "tests-3.12",
    "delivery-3.11",
    "delivery-3.12",
)

nox.options.error_on_missing_interpreters = True

O que o comando padrão cobre

As verificações estáticas não precisam se repetir em cada Python, mas os testes e a validação da wheel precisam usar cada interpretador selecionado.

Diagrama mostrando um comando Nox ramificando para format, lint e typecheck em uma trilha única, e para tests e delivery nas trilhas Python 3.11 e Python 3.12.

Uma execução padrão reúne sessões estáticas e as expansões relevantes da matriz de Python.

O comando canônico

Da raiz do projeto

Com o ambiente que contém Nox ativado, execute este comando na raiz — onde estão noxfile.py e pyproject.toml:

bash
python -m nox --error-on-missing-interpreters

Exemplo

Por que passar a opção também na linha de comando?

nox.options.error_on_missing_interpreters = True registra a política no projeto. A opção --error-on-missing-interpreters a torna explícita no comando canônico. Em ambos os casos, faltar um interpretador obrigatório é falha, não evidência de compatibilidade.

Uma automação só precisa preparar os mesmos interpretadores selecionados e o ambiente que executa Nox; depois, chama exatamente esse comando. Não é necessário criar aqui uma configuração específica de provedor.

Dica

Atalho não é validação de entrega

Durante o desenvolvimento, python -m nox -s lint ou python -m nox -s tests-3.12 é útil para feedback rápido. Porém, isso não substitui o comando canônico: sessões não executadas não foram aprovadas.

Falha da sessão × política de parada

O que acontece após uma falha

Se um comando chamado por session.run termina com código diferente de zero, aquela sessão falha. Ao final, basta uma sessão obrigatória falhar para o processo global do Nox terminar com código diferente de zero.

Sem uma política adicional, Nox pode continuar executando outras sessões independentes e mostrar mais diagnósticos. Isso não transforma a execução em sucesso.

Parar no primeiro erro é opcional

Use esta variação apenas quando quiser privilegiar rapidez em vez de coletar todas as falhas da mesma execução:

bash
python -m nox --error-on-missing-interpreters --stop-on-first-error

Atenção

Não confunda interrupção com aprovação

--stop-on-first-error encerra a execução global após a primeira sessão que falhar. Ele é uma política de execução, não um requisito para validar corretamente. Sem essa opção, uma sessão posterior aprovada não compensa a falha anterior.

Ler o resumo como evidência

Exemplo

Resumo hipotético

nox > Session format was successful.
nox > Session lint was successful.
nox > Session typecheck was successful.
nox > Session tests-3.11 was successful.
nox > Session tests-3.12 failed.
nox > Session delivery-3.11 was successful.
nox > Session delivery-3.12 was successful.
nox > Ran 7 sessions: 6 successful, 1 failed.

Apesar de seis aprovações, a validação completa falhou: tests-3.12 era obrigatória. O código de saída global deve sinalizar falha ao ambiente que chamou Nox.

Interprete o resultado

No resumo exibido, a entrega pode ser considerada validada porque as sessões de delivery foram aprovadas.

Passo 8 de 8

Consolidar e testar a rotina completa

Reúna a configuração, execute a validação completa, provoque uma falha de formatação controlada e confirme que a rotina se recupera sem alterar o código automaticamente.

Uma rotina, responsabilidades separadas

O que deve estar conectado

Ao final, a validação completa é acionada da raiz do projeto com:

python -m nox --error-on-missing-interpreters

Cada arquivo tem uma responsabilidade: pyproject.toml reúne metadados e opções das ferramentas; os arquivos em requirements/ fixam as versões das ferramentas; e noxfile.py cria ambientes isolados e coordena os comandos. A suíte de aceitação criada anteriormente continua responsável por instalar e exercitar a wheel em um ambiente limpo.

Fluxo da validação completa

A execução padrão percorre verificações estáticas, testes em mais de uma versão do Python e a entrega testada a partir da wheel recém-construída.

Diagrama mostrando pyproject.toml, arquivos requirements e noxfile.py alimentando sessões de formato, lint, tipagem, testes em Python 3.11 e 3.12, construção de wheel e testes de aceitação, terminando em um resultado global aprovado ou reprovado.

As configurações, versões e orquestração são separadas, mas o resultado final depende de todas as sessões obrigatórias.

Configurações e versões fixadas

Confirme ou ajuste estes trechos

Mantenha os metadados de seu projeto já existentes e confirme que as opções abaixo estão em pyproject.toml. Os diretórios src, tests/unit e tests/acceptance correspondem à estrutura usada na prática anterior.

pyproject.toml — opções das ferramentas

Acrescente estas tabelas ao seu pyproject.toml, preservando as demais tabelas de construção e de metadados do projeto.

toml
[project]
requires-python = ">=3.11"

[tool.ruff]
target-version = "py311"

[tool.ruff.lint]
select = ["E", "F", "I"]

[tool.mypy]
python_version = "3.11"
strict = true
files = ["src"]

[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests/unit"]

Arquivos de requisitos de verificação

Crie cada arquivo com o respectivo conteúdo. Essas dependências não pertencem a project.dependencies: elas existem para executar a validação.

text
# requirements/nox.txt
nox==2024.10.9

# requirements/quality.txt
ruff==0.9.10
mypy==1.15.0

# requirements/test.txt
pytest==8.3.5

# requirements/delivery.txt
build==1.2.2.post1
pytest==8.3.5

noxfile.py consolidado

A matriz e a entrega

Este arquivo torna Python 3.11 e 3.12 obrigatórios para os testes e para a validação da distribuição. As ferramentas estáticas usam Python 3.11 como interpretador de referência. A sessão delivery cria um diretório novo, exige exatamente uma wheel e entrega seu caminho absoluto à suíte de aceitação por PACKAGE_WHEEL.

noxfile.py

Substitua ou complete seu noxfile.py por esta configuração. Ela pressupõe que tests/acceptance já lê PACKAGE_WHEEL, rejeita um caminho ausente ou inválido e instala a wheel indicada em um ambiente limpo.

python
from pathlib import Path
import tempfile

import nox

PYTHONS = ["3.11", "3.12"]

nox.options.error_on_missing_interpreters = True
nox.options.sessions = [
    "format",
    "lint",
    "typecheck",
    "tests-3.11",
    "tests-3.12",
    "delivery-3.11",
    "delivery-3.12",
]


@nox.session(python="3.11")
def format(session: nox.Session) -> None:
    session.install("-r", "requirements/quality.txt")
    session.run("ruff", "format", "--check", ".")


@nox.session(python="3.11")
def lint(session: nox.Session) -> None:
    session.install("-r", "requirements/quality.txt")
    session.run("ruff", "check", ".")


@nox.session(python="3.11")
def typecheck(session: nox.Session) -> None:
    session.install("-r", "requirements/quality.txt")
    session.install(".")
    session.run("mypy", "src")


@nox.session(python=PYTHONS)
def tests(session: nox.Session) -> None:
    session.install("-r", "requirements/test.txt")
    session.install(".")
    session.run("pytest", "tests/unit")


@nox.session(python=PYTHONS)
def delivery(session: nox.Session) -> None:
    session.install("-r", "requirements/delivery.txt")

    output_dir = Path(tempfile.mkdtemp(prefix="nox-dist-")).resolve()
    session.run("python", "-m", "build", "--outdir", str(output_dir))

    wheels = list(output_dir.glob("*.whl"))
    if len(wheels) != 1:
        session.error(
            f"Esperada exatamente uma wheel em {output_dir}; encontradas: {wheels}"
        )

    session.run(
        "pytest",
        "tests/acceptance",
        env={"PACKAGE_WHEEL": str(wheels[0])},
    )

Validação final e recuperação

Execute, provoque a falha e recupere

  1. No ambiente local dedicado ao Nox, instale a versão fixada: python -m pip install -r requirements/nox.txt.
  2. Na raiz do projeto, execute python -m nox --error-on-missing-interpreters. Registre o resumo: todas as sessões devem passar e o processo deve terminar com código zero.
  3. Em um arquivo Python de src/, escolha uma atribuição simples, como valor = 1, e introduza deliberadamente um espaço extra: valor = 1.
  4. Execute o mesmo comando. A sessão format deve falhar. Compare o arquivo: ele ainda contém o espaço extra, pois ruff format --check apenas verifica; não corrige.
  5. Restaure exatamente a linha original e execute o comando completo mais uma vez.

A aprovação só é completa se as sessões obrigatórias, incluindo cada versão selecionada e cada delivery, forem aprovadas. Um interpretador ausente, uma sessão ignorada ou a execução de apenas uma sessão não comprovam a entrega.

Relate a evidência da sua rotina

Qual comando você executou? Qual sessão detectou a alteração de formatação? O arquivo foi modificado automaticamente? Após restaurá-lo, quais evidências permitem afirmar — ou negar — que a entrega foi aprovada por completo?

Escreva pelo menos 180 caracteres (0/180).

Resumo

Critério de conclusão

Use esta síntese para revisar a rotina antes de considerar a entrega validada.

  • pyproject.toml concentra opções de Ruff, mypy e pytest; requisitos fixados controlam as versões das ferramentas; noxfile.py coordena ambientes e comandos.
  • ruff format --check e ruff check validam sem aplicar correções deliberadamente; um código de saída não zero faz a sessão falhar.
  • A entrega constrói artefatos novos, identifica uma única wheel e passa seu caminho absoluto para os testes de aceitação.
  • A compatibilidade verificada é a das versões que realmente executaram sessões aprovadas. Interpretador obrigatório ausente é falha explícita.
  • A conclusão exige todas as sessões padrão aprovadas e código de saída global zero.

Rotina de validação consolidada

Parabéns! Você concluiu: Automatizar as verificações do projeto com Nox

Concluído! Agora sua entrega só é aprovada quando a validação completa termina com todas as sessões obrigatórias aprovadas e código de saída zero.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso