Tipos de teste para pacotes Python#

Três tipos de teste: unitário, de integração e funcional#

Há diferentes tipos de teste que você deve considerar ao criar sua suíte de testes:

  1. Testes unitários

  2. Testes de integração

  3. Testes de ponta a ponta (também conhecidos como testes funcionais)

Cada tipo de teste tem uma finalidade diferente. Aqui, você conhecerá os três tipos por meio de exemplos simples.

Testes unitários#

Um teste unitário verifica componentes ou unidades individuais de código de forma isolada para garantir que funcionem corretamente. O objetivo é confirmar que cada parte do software, geralmente no nível de função ou método, execute corretamente a tarefa esperada.

Os testes unitários podem ser comparados à análise de cada peça de um quebra-cabeça para garantir que as partes ou subseções não estejam quebradas. Se todas as peças daquela seção não se encaixarem, você nunca completará o quebra-cabeça. Da mesma forma, ao trabalhar com código, os testes garantem que cada função, atributo, classe e método funcione corretamente de forma isolada.

Exemplo de teste unitário: Suponha que você tenha uma função que soma dois números. Um teste unitário para essa função garante que, ao receber dois números, ela retorne a soma correta. Esse é um teste unitário porque verifica uma única unidade (função) de forma isolada.

# src/examplePy/numbers.py
def add_numbers(a: float, b: float) -> float:
    """
    Add two numbers together and return the result.

    Parameters
    ----------
    a : float
        The first number to add.
    b : float
        The second number to add.

    Returns
    -------
    float
        The sum of the two numbers.
    """
    return a + b

Exemplo de teste unitário para a função acima. Você executaria esse teste com o comando pytest no diretório tests/.

# tests/examplePy/test_numbers.py
from examplePy.numbers import add_numbers


def test_add_numbers():
    """Test the add_numbers function."""
    # test with positive numbers
    result = add_numbers(2, 3)
    assert result == 5, f"Expected 5, but got {result}"

    # test with negative numbers
    result2 = add_numbers(-1, 4)
    assert result2 == 3, f"Expected 3, but got {result2}"

    # test with zero
    result3 = add_numbers(0, 5)
    assert result3 == 5, f"Expected 5, but got {result3}"

Observe que os testes acima não verificam apenas um caso em que números são somados. Em vez disso, testam vários cenários: a soma de números positivos, a soma de um número negativo e a soma de zero. Isso ajuda a garantir que a função add_numbers se comporte corretamente em situações diferentes e é o começo de uma abordagem de programação defensiva.

Você pode executar esse teste no terminal usando pytest tests/test_math_utils.py.

imagem de peças de quebra-cabeça que se encaixam perfeitamente. As peças são coloridas - roxas, verdes e azul-petróleo.

Seus testes unitários devem garantir que cada parte do código funcione isoladamente conforme o esperado.#

Testes de integração#

Os testes de integração verificam como as partes do pacote funcionam em conjunto ou se integram. Eles podem ser comparados a conectar várias peças de um quebra-cabeça para formar uma imagem completa. Os testes de integração se concentram em como diferentes partes do código se encaixam e funcionam juntas.

Por exemplo, suponha que você tenha funções que convertem temperaturas e calculam estatísticas. Um teste de integração garantiria que essas funções trabalhassem corretamente juntas em um fluxo no qual você converte as temperaturas e depois as analisa.

# src/examplePy/temperature.py
def celsius_to_fahrenheit(celsius: float) -> float:
    """
    Convert temperature from Celsius to Fahrenheit.

    Parameters:
        celsius (float): Temperature in Celsius.

    Returns:
        float: Temperature in Fahrenheit.
    """
    fahrenheit = (celsius * 9 / 5) + 32
    return fahrenheit


def fahrenheit_to_celsius(fahrenheit: float) -> float:
    """
    Convert temperature from Fahrenheit to Celsius.

    Parameters:
        fahrenheit (float): Temperature in Fahrenheit.

    Returns:
        float: Temperature in Celsius.
    """
    celsius = (fahrenheit - 32) * 5 / 9
    return celsius


def average_temperature(temps: list[float]) -> float:
    """
    Calculate average temperature from a list.

    Parameters
    ----------
    temps : list
        List of temperatures.

    Returns
    -------
    float
        Average temperature.
    """
    return sum(temps) / len(temps)


def convert_and_average(temps_celsius: list[float]) -> float:
    """
    Convert list of Celsius temps to Fahrenheit and
    calculate the average.

    Parameters
    ----------
    temps_celsius : list
        List of Celsius temperatures.

    Returns
    -------
    float
        Average temperature in Fahrenheit.
    """
    temps_fahrenheit = [celsius_to_fahrenheit(t)
                        for t in temps_celsius]
    return average_temperature(temps_fahrenheit)

Veja um teste de integração que verifica como as funções de conversão e de estatística trabalham juntas:

# tests/examplePy/test_temperature.py
from examplePy import temperature


def test_convert_and_average():
    """
    Test that convert_and_average correctly combines conversion
    and averaging.
    """
    # Test with known values: [0, 10, 20] Celsius
    # Should average to 10 Celsius = 50 Fahrenheit
    temps_celsius = [0, 10, 20]
    result = temperature.convert_and_average(temps_celsius)
    assert abs(result - 50.0) < 0.01

    # Test with different values
    temps_celsius = [0, 100]
    result = temperature.convert_and_average(temps_celsius)
    # Average of 32 and 212 Fahrenheit = 122
    assert abs(result - 122.0) < 0.01

Esse teste de integração verifica se as funções de conversão e de cálculo da média trabalham juntas conforme o esperado em um fluxo de trabalho real.

imagem de duas peças de quebra-cabeça com algumas partes ausentes. As peças são roxas, azul-petróleo, amarelas e azuis. Os formatos das peças não se encaixam.

Se as peças de um quebra-cabeça tiverem extremidades faltando, elas não conseguem se encaixar nos outros elementos. O mesmo vale para funções, métodos e classes individuais do seu software. O código precisa funcionar tanto isoladamente quanto em conjunto para executar determinados grupos de tarefas.#

imagem de peças de quebra-cabeça que se encaixam perfeitamente. As peças são coloridas - roxas, verdes e azul-petróleo.

Seus testes de integração devem garantir que as partes do código que precisam trabalhar juntas façam isso conforme o esperado.#

Testes de ponta a ponta (funcionais)#

Os testes de ponta a ponta (também chamados de testes funcionais) em Python são como listas de verificação completas para o software. Eles simulam fluxos de trabalho reais dos usuários para garantir que a base de código ofereça suporte a aplicações e casos de uso reais, do início ao fim. Esses testes ajudam a encontrar problemas que talvez não apareçam em testes menores e garantem que toda a aplicação se comporte corretamente. Pense neles como uma verificação final do software antes de colocá-lo em uso, para garantir que esteja pronto para oferecer uma experiência fluida ao usuário.

Imagem de um quebra-cabeça completo mostrando uma margarida

Testes de ponta a ponta ou funcionais representam um fluxo de trabalho completo ao qual seu pacote oferece suporte.#

Exemplo de teste de ponta a ponta: Digamos que seu pacote abra e processe/converta dados de temperatura de Celsius para Fahrenheit e, depois, calcule a temperatura média. Um teste de ponta a ponta simularia todo esse fluxo, garantindo que o pacote processe corretamente os dados de temperatura de entrada e retorne um valor médio resumido. O teste forneceria dados de exemplo, executaria o fluxo completo e verificaria se a saída final está correta.

# tests/examplePy/test_temperature.py
def test_temperature_workflow():
    """
    Test the complete temperature processing workflow.

    This end-to-end test provides sample temperature data in
    Celsius, processes it through the full workflow
    (conversion and averaging), and verifies the output is
    correct.
    """
    # Sample temperature data in Celsius
    temps_celsius = [0, 10, 20]

    # Run the complete workflow
    result = temperature.convert_and_average(temps_celsius)

    # Verify the output
    # Average of 32, 50, and 68 Fahrenheit = 50 Fahrenheit
    assert abs(result - 50.0) < 0.01

Esse teste de ponta a ponta exercita todo o fluxo do usuário: fornece dados de exemplo, converte-os, calcula a média e verifica se a saída está correta.

Os testes de ponta a ponta também verificam como um programa é executado do início ao fim. Um tutorial adicionado à documentação e executado na CI é outro exemplo de teste de ponta a ponta. Por exemplo, um notebook Jupyter (.ipynb) ou arquivo .md com código incorporado que demonstre um fluxo de trabalho completo do usuário.

Nota

Para pacotes científicos, criar tutoriais curtos que destaquem os principais fluxos de trabalho oferecidos pelo pacote e sejam executados durante a construção da documentação também pode servir como testes de ponta a ponta.

Quando usar cada tipo de teste#

Se você está começando a trabalhar com testes, comece pelos unitários. Eles são os mais simples de escrever, os mais rápidos de executar e os mais fáceis de depurar. À medida que o pacote crescer, adicione testes de integração e de ponta a ponta nos pontos em que eles agregarem mais valor.

Comece escrevendo testes unitários#

Você está testando uma única função, método ou classe de forma isolada?

→ Sim: Escreva um teste unitário.

  • Exemplo: Verifique se add_numbers(2, 3) retorna 5

  • Testes unitários não dependem de outras partes do código

  • Esses testes formam a base da sua suíte de testes

  • Se algo quebrar, os testes unitários facilitam descobrir onde

Depois, adicione testes de integração#

Você está testando como vários componentes funcionam em conjunto?

→ Sim: Escreva testes de integração.

  • Exemplo: Converter temperaturas e depois calcular a média

  • Testes de integração pressupõem que as partes individuais já funcionem

  • Esses testes verificam se os componentes interagem corretamente

Use testes de ponta a ponta nos principais fluxos de trabalho#

Você está testando um fluxo de trabalho completo e realista do usuário, do início ao fim?

→ Sim: Use um teste de ponta a ponta.

  • Exemplo: Execute um fluxo completo de processamento de dados que um usuário seguiria

  • Esses testes geralmente refletem exemplos da sua documentação

  • Use-os com moderação nos fluxos de trabalho mais importantes.

  • Tutoriais executados durante a construção da documentação podem servir como testes de ponta a ponta.

Comparação entre testes unitários, de integração e de ponta a ponta#

Testes unitários, de integração e de ponta a ponta apresentam vantagens e desvantagens complementares. A granularidade dos testes unitários os torna adequados para isolar onde os erros ocorrem. No entanto, eles não são úteis para verificar se diferentes partes do código funcionam em conjunto.

Testes de integração e de ponta a ponta verificam se diferentes partes do programa funcionam juntas, mas são menos eficazes para isolar imediatamente o ponto exato em que os erros ocorrem.

Os testes não precisam ser perfeitos#

É importante observar que você não precisa gastar energia se preocupando com os detalhes dos tipos de teste. Quando começar a trabalhar na sua suíte, pense no que seu pacote faz e em como talvez seja necessário testar suas partes. Conhecer diferentes tipos de teste oferece uma estrutura para ajudar você a pensar em como escrevê-los e em como eles podem se complementar.

Próximos passos#

Agora que você entende os tipos de teste, aprenda a escrever testes eficazes para seu pacote. Depois, veja como executar testes localmente e em integração contínua. Você também pode aprender a acompanhar a cobertura de testes usando ferramentas como o CodeCov.