Pruebas para paquetes de Python#

Three types of tests: unit, integration, and functional tests#

Existen diferentes tipos de pruebas que debes considerar al crear tu conjunto de pruebas:

  1. Pruebas Unitarias

  2. Pruebas de Integración

  3. End-to-end (also known as functional) tests

Each type of test has a different purpose. Here, you will learn about all three types of tests by working through simple examples.

Pruebas Unitarias#

Una prueba unitaria implica probar componentes individuales o unidades de código de forma aislada para asegurarse de que funcionen correctamente. El objetivo de las pruebas unitarias es verificar que cada parte del software, típicamente a nivel de función o método, realiza correctamente su función prevista.

Unit tests can be compared to examining each piece of your puzzle to ensure parts or subsections of it are not broken. If all of the pieces of that section of your puzzle don’t fit together, you will never complete it. Similarly, when working with code, tests ensure that each function, attribute, class, and method works properly when isolated.

Unit test example: Suppose you have a function that adds two numbers together. A unit test for that function ensures that when provided with two numbers, it returns the correct sum. This is a unit test because it checks a single unit (function) in isolation.

# src/mypackage/math_utils.py
def add_numbers(a, b):
    """
    Add two numbers together.

    Parameters
    ----------
    a : float
        First number.
    b : float
        Second number.

    Returns
    -------
    float
        Sum of a and b.
    """
    return a + b

Ejemplo de prueba unitaria para la función anterior. Ejecutarías esta prueba utilizando el comando pytest en tu directorio tests/.

# tests/test_math_utils.py
from mypackage.math_utils import add_numbers


def test_add_numbers():
    """
    Test the add_numbers function.
    """
    # Test with positive numbers
    assert add_numbers(2, 3) == 5

    # Test with negative numbers
    assert add_numbers(-1, 4) == 3

    # Test with zero
    assert add_numbers(0, 5) == 5

Notice that the tests above don’t just test one case where numbers are added together. Instead, they test multiple scenarios: adding positive numbers, adding a negative number, and adding zero. This helps ensure that the add_numbers function behaves correctly in different situations and is the beginning of thinking about programming defensively.

You can run this test from your terminal using pytest tests/test_math_utils.py.

imagen de las piezas de un rompecabezas que encajan perfectamente. Las piezas del rompecabezas tiene colores - púrpura, verde y turquesa.

Tus pruebas unitarias deben asegurarse de que cada parte de tu código funciona como se espera por sí sola.#

Pruebas de Integración#

Las pruebas de integración implican probar cómo funcionan juntas o se integran las partes de tu paquete. Las pruebas de integración se pueden comparar con conectar muchas de piezas de rompecabezas para formar una imagen completa. Las pruebas de integración se centran en cómo encajan y funcionan juntas las diferentes partes de tu código.

For example, suppose you have functions that convert temperatures and calculate statistics. An integration test would ensure that these functions work together correctly in a workflow where you convert temperatures and then analyze them.

# src/mypackage/temperature_utils.py
def celsius_to_fahrenheit(celsius):
    """
    Convert temperature from Celsius to Fahrenheit.

    Parameters
    ----------
    celsius : float
        Temperature in Celsius.

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


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

    Parameters
    ----------
    fahrenheit : float
        Temperature in Fahrenheit.

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


def average_temperature(temps):
    """
    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):
    """
    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)

Here’s an integration test that checks how the conversion and statistics functions work together:

# tests/test_temperature_integration.py
from mypackage.temperature_utils import convert_and_average


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 = convert_and_average(temps_celsius)
    assert abs(result - 50.0) < 0.01

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

This integration test verifies that the conversion and averaging functions work together as expected in a real workflow.

imagen de dos piezas de rompecabezas con algunas partes faltantes. Las piezas del rompecabezas son de color púrpura, turquesa, amarillo y azul. Las formas de cada pieza no encajan juntas.

Si las piezas de un rompecabezas tienen extremos faltantes, no pueden funcionar juntas con otros elementos del rompecabezas. Lo mismo ocurre con las funciones, métodos y clases individuales en tu software. El código necesita funcionar tanto individualmente como en conjunto para realizar ciertas tareas.#

imagen de las piezas de un rompecabezas que encajan perfectamente. Las piezas del rompecabezas tiene colores - púrpura, verde y turquesa.

Tus pruebas de integración deben asegurarse de que las partes de tu código que se espera que funcionen juntas, lo hagan como se espera.#

Pruebas de extremo a extremo (funcionales)#

End-to-end tests (also referred to as functional tests) in Python are like comprehensive checklists for your software. They simulate real user workflows to make sure the code base supports real-life applications and use-cases from start to finish. These tests help catch issues that might not show up in smaller tests and ensure your entire application behaves correctly. Think of them as a way to give your software a final check before it’s put into action, making sure it’s ready to deliver a smooth user experience.

Imagen de un rompecabezas completado que muestra una margarita

End-to-end or functional tests represent an entire workflow that your package supports.#

End-to-end test example: Let’s say your package opens and processes/converts temperature data from Celsius to Fahrenheit and then calculates the average temperature. An end-to-end test would simulate this entire workflow, ensuring that the package correctly handles the input temperature data and returns a summary average value. An end-to-end test would provide sample data, run the entire workflow, and verify that the final output is correct.

# tests/test_temperature_e2e.py
from mypackage.temperature_utils import convert_and_average


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 = convert_and_average(temps_celsius)

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

This end-to-end test exercises the entire user workflow: providing sample data, converting and averaging it, and verifying the output is correct.

End-to-end tests also verify how a program runs from start to finish. A tutorial that you add to your documentation and run in CI is another example of an end-to-end test. For example, a Jupyter (.ipynb) notebook or .md file with embedded code that demonstrates a complete user workflow.

Nota

For scientific packages, creating short tutorials that highlight core workflows that your package supports, that are run when your documentation is built, could also serve as end-to-end tests.

When to use which test type#

If you’re new to testing, start with unit tests. They are the simplest to write, fastest to run, and easiest to debug. As your package grows, you can then add integration and end-to-end tests where they add the most value.

Start by writing unit tests#

Are you testing a single function, method, or class in isolation?

→ Yes: Write a unit test.

  • Example: Check that add_numbers(2, 3) returns 5

  • Unit tests don’t rely on other parts of your code

  • These tests form the foundation of your test suite

  • If something breaks, unit tests make it easy to find where

Add integration tests next#

Are you testing how multiple components work together?

Yes: Write integration tests.

  • Example: Converting temperatures and then computing their average

  • Integration tests assume individual pieces already work

  • These tests verify that components interact correctly

Use end-to-end tests for core workflows#

Are you testing a complete, realistic user workflow from start to finish?

Yes: Use an end-to-end test.

  • Example: Run a full data-processing workflow a user would follow

  • These tests often mirror examples in your documentation

  • Use them sparingly for the most important workflows.

  • Tutorials run during documentation builds can serve as end-to-end tests.

Comparing unit, integration, and end-to-end tests#

Unit tests, integration tests, and end-to-end tests have complementary advantages and disadvantages. The fine-grained nature of unit tests makes them well-suited for isolating where errors are occurring. However, unit tests are not useful for verifying that different sections of code work together.

Integration and end-to-end tests verify that different portions of the program work together, but are less valuable for immediately isolating exactly where errors are occurring.

Tests don’t have to be perfect#

It is important to note that you don’t need to spend energy worrying about the specifics of test types. When you begin to work on your test suite, consider what your package does and how you may need to test parts of it. Being familiar with different test types provides a framework to help you think about writing tests and how they can complement each other.

Next steps#

Now that you understand test types, learn how to write effective tests for your package. Then explore how to run tests locally and in continuous integration. You can also learn about tracking test coverage using tools like CodeCov.