JohKemPo / Python-Packages-Guide

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Guia prático de Criação, Controle e distribuição de pacotes Python

[ EM PRODUÇÃO ]

  Este repositório fornece um guia sobre empacotamento de projetos python utilizando setuptools. Setuptools é uma biblioteca estável, com manutenção ativa e completa, projetada para facilitar o empacotamento de projetos Python.

S U M A R I O


  1. Introdução
  2. Instalação e configuração
    1. Instalação
  3. Estrutura Básica do Pacote
    1. Estrutura de diretórios
    2. init.py
    3. Organização de código-fonte e módulos.
  4. Configuração do setup.py
    1. O que são fixtures?
    2. Parametros dinâmicos
  5. Distribuição do Pacote
    1. Gerando relatórios de cobertura
  6. Segurança
    1. Subcapitulo
  7. Controle de Versão
    1. Subcapitulo
  8. Manifesto
    1. Subcapitulo
  9. Catalogação e Tags no Repositório Git
    1. Subcapitulo
  10. Automatização do Processo
    1. Subcapitulo
  11. Licenças
    1. Subcapitulo
  12. Apêndice
    1. Ambiente virtual:



Instalação do Pytest:

Instalação

  Para começar, certifique-se de ter o Python e o gerenciador de pacotes pip instalados em seu sistema. Em seguida, execute o seguinte comando no terminal para instalar o Pytest:

# requeriments.txt
pytest==x.x.x    
pytest-cov==x.x.x   
pytest-mock==x.x.x  
pytest-watch==x.x.x     
pytest-xdist==x.x.x     
  • Instalação das bibliotecas:
pip install -r requeriments.txt

Configurações de ambiente, acesse a seção Ambiente virtual.

Estrutura básica de um teste usando Pytest:

Documentação disponível em documentação pytest

Estrutura de diretórios

 Organize seu projeto em uma estrutura de diretórios. Por exemplo:

Projeto/
├── app/
│   ├── utils/
│   │   ├── __init__.py
│   │   └── util.py
│   ├── __init__.py
│   └── codigo.py
├── tests/
│   ├── utils/
│   │   └── test_util.py
│   └── test_codigo.py
└── requirements.txt

Descoberta de testes em Python

Pytest implementa a seguinte descoberta de teste padrão, os nomes dos testes devem seguir o modelo test_*.py or *_test.py, Mais informações na documentação.

Asserts e verificações de testes.:

Assertions:

Use as afirmações assert para verificar se o resultado esperado é igual ao resultado real. Se a afirmação for falsa, o Pytest relatará um erro.

Exemplo:

  • Código:
# meu_codigo.py
def multiplicar(a, b):
    return a * b
  • Teste:
# test/test_meu_codigo.py
import meu_codigo

def test_multiplicar():
    resultado = meu_codigo.multiplicar(3, 4)
    assert resultado == 12, f"Esperado: 12, Obtido: {resultado}"

Neste exemplo, o teste verifica se a função multiplicar produz o resultado esperado de 12 ao multiplicar 3 por 4. Se o resultado for diferente de 12, o assert falhará, e o Pytest relatará um erro, mostrando a mensagem opcional fornecida.

Evite Side Effects:

Evite realizar operações com efeitos colaterais dentro de uma instrução assert. O objetivo é validar o resultado, não modificar o estado.

Exemplo prático:

Caso queria executar os testes eles estão disponíveis em app_proj/.

  • Código:
# Caminho: app_proj\codes\codigo.py

from app_proj.utils.util import format_message, calculate_sum


class MyClass1:
    def __init__(self, name):
        self.name = name

    def greet(self, use_uppercase: bool, *args, **kwargs):
        message = "Hello, {}!"
        formatted_message = format_message(use_uppercase, message, self.name)
        print(formatted_message.format(*args, **kwargs))

    def calculate_square(self, num: int | float):
        result = num ** 2
        print(f"The square of {num} is {result}")

class MyClass2:
    def __init__(self, value: int | float):
        self.value = value

    def display_info(self, use_uppercase: bool, *args, **kwargs):
        message = "The value is {}."
        formatted_message = format_message(use_uppercase, message, self.value)
        print(formatted_message.format(*args, **kwargs))

    def calculate_cube(self, num: int | float):
        result = num ** 3
        print(f"The cube of {num} is {result}")
  • Teste:
# Caminho: tests\test_codigo.py

from app_proj.codes.codigo import MyClass1, MyClass2
import pytest

'''
    capsys é uma fixture do pytest que captura a saída do stdout e stderr.
    Fixtures serão mlehor explicadas no próximo capitulo.
'''

# Tests for main.py
def test_myclass1_greet(capsys):
    obj = MyClass1("John")
    obj.greet(True, "Have a nice day!")
    captured = capsys.readouterr()
    assert captured.out.strip() == "HELLO, JOHN!"

def test_myclass1_calculate_square(capsys):
    obj = MyClass1("John")
    obj.calculate_square(4)
    captured = capsys.readouterr()
    assert captured.out.strip() == "The square of 4 is 16"

def test_myclass2_display_info(capsys):
    obj = MyClass2(10)
    obj.display_info(False, "Additional info")
    captured = capsys.readouterr()
    assert captured.out.strip() == "The value is 10."

def test_myclass2_calculate_cube(capsys):
    obj = MyClass2(10)
    obj.calculate_cube(3)
    captured = capsys.readouterr()
    assert captured.out.strip() == "The cube of 3 is 27"
  • Código:
# Caminho: app_proj\utils\util.py
def format_message(use_uppercase: bool, message: str, *args, **kwargs):
    formatted_message = message.format(*args, **kwargs)
    return formatted_message.upper() if use_uppercase else formatted_message

def calculate_sum(numbers: list):
    return sum(numbers)
  • Teste:
# Caminho: tests\test_codigo.py
from app_proj.utils.util import format_message, calculate_sum

# Tests for utils.py
def test_format_message():
    assert format_message(True, "Hello, {}!", "John") == "HELLO, JOHN!"
    assert format_message(False, "Hi, {}!", "Alice") == "Hi, Alice!"

def test_calculate_sum():
    numbers = [1, 2, 3, 4, 5]
    assert calculate_sum(numbers) == 15
    assert calculate_sum([]) == 0

Executanto um teste:

> python -m pytest tests\test_codigo.py

Esse é o resultado da execução dos testes

=========================== test session starts ============================
platform win32 -- Python 3.12.0, pytest-7.4.4, pluggy-1.3.0
rootdir: C:\Users\japag\OneDrive\Documentos\GIT\Pytest-for-Unit-tests-Guide
collected 4 items

tests\test_codigo.py ....                                             [100%]

============================ 4 passed in 0.01s =============================

Número de Itens Coletados e Status da Execução:

collected 4 items
  • Mostra que o Pytest encontrou e coletou 4 itens de teste.

Execução dos Testes:

tests\test_codigo.py ....  
  • Mostra o progresso da execução dos testes. Cada ponto . representa um teste que foi executado com sucesso. O [100%]indica que todos os testes foram executados.

Fixtures e parametros dinâmicos:

O que são fixtures?

 Em Pytest, as fixtures são um recurso usados para configurar o estado inicial para testes e fornecer dados pré-definidos ou recursos compartilhados, como inputs, instancias de classes, e etc. Elas permitem que você configure ambientes específicos para testes, definindo recursos que podem ser usados por vários testes ou escopos específicos.

Exemplo básico:

# meu_codigo.py
def calcular(a, b):
    return a + b
  • Teste usando fixtures:
# test/test_meu_codigo.py
import pytest
from meu_codigo import calcular

# Fixture que fornece dois números para testar
@pytest.fixture
def numeros_para_teste():
    return 3, 4

# Teste utilizando a fixture
def test_calcular_soma(numeros_para_teste):
    a, b = numeros_para_teste
    resultado = calcular(a, b)
    assert resultado == 7

Neste exemplo:

numeros_para_teste é uma fixture que fornece os números 3 e 4. O teste test_calcular_soma aceita numeros_para_teste como um argumento. A fixture é automaticamente injetada pelo Pytest.

Escopos de Fixtures:

As fixtures podem ter diferentes escopos:

  • Função (padrão): A fixture é executada uma vez para cada função de teste.
  • Módulo: A fixture é executada uma vez para cada módulo de teste.
  • Classe: A fixture é executada uma vez para cada classe de teste.
  • Sessão: A fixture é executada uma vez para toda a sessão de teste.

Parâmetros Dinâmicos

Você pode parametrizar fixtures para fornecer dados diferentes a cada teste.

@pytest.fixture(params=[(3, 4), (5, 6)])
def numeros_para_teste(request):
    return request.param

Parametrização de fixtures é uma funcionalidade do Pytest que permite fornecer diferentes conjuntos de dados ou configurações para testes específicos. Isso é útil quando você tem um conjunto de testes que segue um padrão semelhante, mas precisa ser executado com diferentes conjuntos de entradas.

Exemplo básico:

import pytest
from meu_codigo import somar

# Fixture parametrizada
@pytest.fixture(params=[(2, 3), (5, 7), (10, 15)])
def numeros_para_teste(request):
    return request.param

# Teste usando a fixture parametrizada
def test_somar(numeros_para_teste):
    a, b = numeros_para_teste
    resultado = somar(a, b)
    assert resultado == a + b

Neste exemplo:

  • A fixture numeros_para_teste é parametrizada com uma lista de tuplas contendo diferentes pares de números.
  • A cada execução do teste, a fixture é chamada com um conjunto diferente de números.

Parametrização de Escopo Mais Amplo:

Você pode combinar a parametrização de fixtures com escopos diferentes. Por exemplo, se você quiser que a parametrização seja feita apenas uma vez por módulo, pode usar o escopo de módulo:

import pytest
from meu_codigo import somar

# Fixture parametrizada com escopo de módulo
@pytest.fixture(params=[(2, 3), (5, 7), (10, 15)], scope="module")
def numeros_para_teste(request):
    return request.param

# Teste usando a fixture parametrizada
def test_somar(numeros_para_teste):
    a, b = numeros_para_teste
    resultado = somar(a, b)
    assert resultado == a + b

Casos de Uso Comuns:

Testar com Diferentes Entradas:

  • Como ilustrado acima, você pode testar uma função com diferentes conjuntos de entradas.

Configurações com Dados Variados:

  • Parametrize fixtures para configurar ambientes de teste com diferentes conjuntos de dados ou configurações.

Avaliação de Comportamento com Dados Diversos:

  • Testar se uma função se comporta corretamente com diferentes tipos de entradas.

A marcação @pytest.mark.parametrize é uma maneira alternativa de parametrizar testes em Pytest, que pode ser usada diretamente em funções de teste, sem a necessidade de criar uma fixture separada para isso.

A marcação @pytest.mark.parametrize permite que você defina diferentes conjuntos de parâmetros para a mesma função de teste. Ela aceita um nome de parâmetro, uma lista de valores e gera automaticamente instâncias da função de teste para cada combinação de parâmetros.

Exemplo básico:

import pytest
from meu_codigo import somar

# Teste parametrizado usando a marcação pytest.mark.parametrize
@pytest.mark.parametrize(
        "a, b, esperado", [(2, 3, 5), # teste 1
                           (5, 7, 12), # teste 2
                           (10, 15, 25)]) # teste 3
def test_somar(a, b, esperado):
    resultado = somar(a, b)
    assert resultado == esperado
  • A marcação @pytest.mark.parametrize é aplicada à função de teste test_somar.

  • Os parâmetros "a", "b" e "esperado" são fornecidos como strings, seguidos por uma lista de tuplas contendo valores específicos para cada execução do teste.

  • O Pytest gerará automaticamente instâncias separadas da função test_somar para cada conjunto de parâmetros fornecido, resultando em três testes distintos.

Execução de relatórios dos testes:

Gerando relatórios de cobertura:

Gerando relatório de cobertura de um teste específico no terminal:

> python -m pytest tests\test_codigo.py --cov
============================== test session starts ===============================
platform win32 -- Python 3.11.4, pytest-7.2.1, pluggy-1.0.0
rootdir: ~\Pytest-for-Unit-tests-Guide
plugins: cov-4.0.0, mock-3.10.0, xdist-3.2.1
collected 4 items

tests\test_codigo.py ....                                                   [100%]

---------- coverage: platform win32, python 3.11.4-final-0 -----------
Name                         Stmts   Miss  Cover
------------------------------------------------
app_proj\__init__.py             0      0   100%
app_proj\codes\__init__.py       0      0   100%
app_proj\codes\codigo.py        33      9   100%
app_proj\utils\__init__.py       0      0   100%
app_proj\utils\util.py           5      1   100%
tests\test_codigo.py            22      0   100%
------------------------------------------------
TOTAL                           60     10    83%


=============================== 4 passed in 0.06s ================================ 
  • tests\test_codigo.py: Especifica o caminho para o arquivo de teste que você deseja executar. Neste caso, os testes serão executados no arquivo test_codigo.py dentro do diretório tests.

  • --cov: Indica ao Pytest para coletar informações de cobertura durante a execução dos testes.

  • Este comando executará os testes no arquivo tests\test_codigo.py e calculará a cobertura dos código associado aos testes.

Gerando relatório de cobertura de um repositório específico no terminal:

> python -m pytest tests\ --cov
=============================== test session starts ================================
platform win32 -- Python 3.11.4, pytest-7.2.1, pluggy-1.0.0
rootdir: ~\Pytest-for-Unit-tests-Guide
plugins: cov-4.0.0, mock-3.10.0, xdist-3.2.1
collected 6 items

tests\test_codigo.py ....                                                     [ 66%]
tests\utils\test_util.py ..                                                   [100%] 

---------- coverage: platform win32, python 3.11.4-final-0 -----------
Name                         Stmts   Miss  Cover
------------------------------------------------
app_proj\__init__.py             0      0   100%
app_proj\codes\__init__.py       0      0   100%
app_proj\codes\codigo.py        23      0   100%
app_proj\utils\__init__.py       0      0   100%
app_proj\utils\util.py           5      0   100%
tests\test_codigo.py            22      0   100%
tests\utils\test_util.py         8      0   100%
------------------------------------------------
TOTAL                           58      0   100%


================================ 6 passed in 0.07s =================================
  • Usado para executar testes, juntamente com a geração de um relatório de cobertura do diretorio usando o pacote coverage.

  • tests\: Especifica o diretório onde os testes devem ser procurados. Neste caso, os testes estarão no diretório tests.

  • --cov: Indica ao Pytest para coletar informações de cobertura durante a execução dos testes.

Se você executar esse comando, o Pytest executará todos os testes no diretório tests e gerará um relatório de cobertura, indicando quais partes do código foram cobertas pelos testes. O resultado será exibido no console e, dependendo da configuração, também pode gerar um relatório mais detalhado em HTML.

Gerando relatório de cobertura de um repositório específico e salvando em html:

> python -m pytest --cov-report html:coverage/ --cov=app_proj/
=============================== test session starts ================================ 
platform win32 -- Python 3.11.4, pytest-7.2.1, pluggy-1.0.0
rootdir: ~\Pytest-for-Unit-tests-Guide
plugins: cov-4.0.0, mock-3.10.0, xdist-3.2.1
collected 6 items

tests\test_codigo.py ....                                                     [ 66%] 
tests\utils\test_util.py ..                                                   [100%] 

---------- coverage: platform win32, python 3.11.4-final-0 -----------
Coverage HTML written to dir coverage/


================================ 6 passed in 0.12s =================================
  • --cov-report html:coverage/: Esta parte do comando especifica que o relatório de cobertura deve ser gerado no formato HTML e salvo no diretório coverage/. Isso criará uma visualização mais detalhada da cobertura de código em formato HTML.

  • --cov=app_proj/: Indica ao Pytest para coletar informações de cobertura para o diretório app_proj/. Isso significa que a cobertura será calculada para o código dentro desse diretório.

Relatórios Verbosos no terminal:

> python -m pytest -v            
===================================== test session starts =====================================
platform win32 -- Python 3.11.4, pytest-7.2.1, pluggy-1.0.0 -- 
cachedir: .pytest_cache
rootdir: ~\Pytest-for-Unit-tests-Guide
plugins: cov-4.0.0, mock-3.10.0, xdist-3.2.1
collected 6 items

tests/test_codigo.py::test_myclass1_greet PASSED                                         [ 16%] 
tests/test_codigo.py::test_myclass1_calculate_square PASSED                              [ 33%]
tests/test_codigo.py::test_myclass2_display_info PASSED                                  [ 50%] 
tests/test_codigo.py::test_myclass2_calculate_cube PASSED                                [ 66%] 
tests/utils/test_util.py::test_format_message PASSED                                     [ 83%] 
tests/utils/test_util.py::test_calculate_sum PASSED                                      [100%] 

====================================== 6 passed in 0.04s ======================================
  • Exibe informações detalhadas sobre a execução dos testes, incluindo o nome de cada teste e seu resultado.

Apêndice

Criação de Ambiente Virtual em Python e instalação das dependências

Crie somente um ambiente virtual, após isso instale as dependências descritas na fase de instalação de dependências.


(Opçãp 1) Criação - miniconda:

  1. Baixar o instalador miniconda:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
  1. Executando o instalador:
bash Miniconda3-latest-Linux-x86_64.sh
  1. Iniciar coda:
conda init
  1. Listar env existentes:
conda env list
  1. Criar env com pytoh3.10:
conda create -n <name env> python=3.10
  1. Ativar env:
conda activate <name env>
  1. Deletar .sh
rm Miniconda3-latest-Linux-x86_64.sh

(Opçãp 2) Criação - venv:

Para criar um ambiente virtual em Python, você pode usar a biblioteca padrão chamada venv. Siga as etapas abaixo para criar e ativar um ambiente virtual usando o venv:

  1. Verifique se o Python 3 está instalado: Abra o terminal e execute o seguinte comando:
python3 --version
  1. Se o Python 3 já estiver instalado, você verá a versão instalada. Caso contrário, siga para o próximo passo.

  2. Instale o Python 3:

No terminal, execute os comandos apropriados de acordo com a distribuição Linux que você está usando.

sudo apt install python3
  1. Instale o pip:
sudo apt install python3-pip
  1. Instale o pacote venv: O pacote venv permite criar ambientes virtuais isolados. No terminal, execute o seguinte comando:
sudo apt install python3-venv
  1. Para criação do ambiente virtual: Navegue até o diretório onde deseja criar o ambiente virtual.

  2. Digite o seguinte comando para criar um novo ambiente virtual:

python3 -m venv nome_do_ambiente

Substitua "nome_do_ambiente" pelo nome que você deseja dar ao seu ambiente virtual.

  1. Para ativar o ambiente virtual, execute o comando apropriado de acordo com o seu sistema operacional:
source nome_do_ambiente/bin/activate
  1. Agora, o ambiente virtual está ativado. Você pode instalar pacotes e executar seus projetos dentro dele sem afetar o ambiente global do Python.

Quando você terminar de trabalhar com o ambiente virtual, pode desativá-lo usando o comando:

deactivate

Extra

Configurar para conda sempre inciarlizar em uma determinada env:

conda env list
export CONDA_DEFAULT_ENV="/caminho/para/env"

Instalação das dependências

  1. Instalação das dependências do projeto no ambiente virtual:
# instalar bibliotecas especificadas em um único arquivo
pip install -r requirements.txt

# instalar bibliotecas especificadas em multiplos arquivos
pip install -r requirements_1.txt -r requirements_2.txt

Equipe


Desenvolvedor GitHub LinkedIn
👤 João Vitor Moraes https://github.com/JohKemPo https://www.linkedin.com/in/joao-vitor-de-moraes/

About

License:MIT License