Python

Python

Tutorial Python: Testing con pytest

Aprende a instalar, configurar y escribir tests efectivos con pytest en Python. Domina aserciones, fixtures y buenas prácticas de testing.

Aprende Python y certifícate

Instalación y configuración de pytest

Pytest es uno de los frameworks de testing más populares y potentes para Python. Su diseño minimalista y su capacidad para escribir tests de forma sencilla lo han convertido en la herramienta preferida por muchos desarrolladores para realizar pruebas unitarias y funcionales.

Para comenzar a utilizar pytest, primero necesitamos instalarlo en nuestro entorno de desarrollo. La forma más común es mediante el gestor de paquetes pip, que viene incluido con las instalaciones modernas de Python.

Instalación básica

La instalación de pytest es muy sencilla. Abre una terminal o línea de comandos y ejecuta:

pip install pytest

Para verificar que la instalación se ha completado correctamente, puedes comprobar la versión instalada:

pytest --version

Esto mostrará la versión de pytest que has instalado, confirmando que está listo para usarse.

Instalación en entornos virtuales

Es una buena práctica utilizar entornos virtuales para aislar las dependencias de cada proyecto. Para crear un entorno virtual e instalar pytest en él:

# Crear un entorno virtual
python -m venv venv

# Activar el entorno virtual
# En Windows:
venv\Scripts\activate
# En macOS/Linux:
source venv/bin/activate

# Instalar pytest en el entorno virtual
pip install pytest

Instalación de plugins

Una de las grandes ventajas de pytest es su ecosistema de plugins que extienden su funcionalidad. Algunos plugins populares incluyen:

  • pytest-cov: Para medir la cobertura de código
  • pytest-xdist: Para ejecutar tests en paralelo
  • pytest-mock: Para facilitar el uso de mocks en los tests

Para instalar un plugin, simplemente usa pip:

pip install pytest-cov

Configuración básica

Pytest está diseñado para funcionar con una configuración mínima. Por defecto, buscará archivos con el patrón test_*.py o *_test.py en el directorio actual y sus subdirectorios.

Sin embargo, puedes personalizar su comportamiento mediante un archivo de configuración. Crea un archivo llamado pytest.ini en la raíz de tu proyecto:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

Este archivo configura:

  • testpaths: Directorios donde buscar tests
  • python_files: Patrón de nombres de archivos de test
  • python_classes: Patrón de nombres de clases de test
  • python_functions: Patrón de nombres de funciones de test

Configuración avanzada con pyproject.toml

En proyectos modernos de Python, puedes usar pyproject.toml para configurar pytest, lo que sigue las recomendaciones de PEP 518 y PEP 621:

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
markers = [
    "slow: marks tests as slow (deselect with '-m \"not slow\"')",
    "integration: marks tests as integration tests",
]

Esta configuración incluye markers (etiquetas) que permiten categorizar tus tests y ejecutarlos selectivamente.

Configuración de entorno de desarrollo

Para una experiencia de desarrollo óptima, es recomendable configurar tu editor o IDE para trabajar con pytest:

VSCode

En Visual Studio Code, puedes instalar la extensión "Python" de Microsoft y configurar pytest como tu framework de testing predeterminado:

  1. Abre la configuración (Ctrl+,)
  2. Busca "python.testing.pytestEnabled"
  3. Establece su valor a true

PyCharm

PyCharm tiene soporte integrado para pytest:

  1. Ve a File > Settings > Tools > Python Integrated Tools
  2. En la sección "Testing", selecciona "pytest" como Default test runner

Opciones de línea de comandos

Pytest ofrece numerosas opciones de línea de comandos para personalizar la ejecución de tests:

  • Mostrar información detallada:
pytest -v
  • Detener la ejecución después del primer fallo:
pytest -x
  • Ejecutar solo tests que coincidan con un patrón:
pytest -k "nombre_test"
  • Ejecutar tests con una etiqueta específica:
pytest -m "integration"
  • Generar un informe de cobertura (requiere pytest-cov):
pytest --cov=mi_paquete

Integración con herramientas de CI/CD

Pytest se integra fácilmente con sistemas de integración continua como GitHub Actions, Travis CI o Jenkins. Por ejemplo, para GitHub Actions, puedes crear un archivo .github/workflows/tests.yml:

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pytest pytest-cov
          pip install -e .
      - name: Test with pytest
        run: |
          pytest --cov=./ --cov-report=xml
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3

Este workflow ejecutará tus tests automáticamente cada vez que hagas push o crees un pull request, y subirá los resultados de cobertura a Codecov.

Configuración para proyectos grandes

En proyectos de mayor escala, es útil organizar los tests en una estructura clara y configurar pytest para manejar dependencias complejas:

mi_proyecto/
├── src/
│   └── mi_paquete/
│       ├── __init__.py
│       └── modulo.py
├── tests/
│   ├── conftest.py
│   ├── unit/
│   │   └── test_modulo.py
│   └── integration/
│       └── test_integracion.py
└── pyproject.toml

El archivo conftest.py es especialmente útil para definir fixtures compartidas entre múltiples tests:

import pytest
from pathlib import Path

@pytest.fixture
def data_dir():
    """Fixture que proporciona la ruta al directorio de datos de prueba."""
    return Path(__file__).parent / "data"

@pytest.fixture(scope="session")
def db_connection():
    """Fixture que proporciona una conexión a la base de datos de prueba."""
    # Configuración
    connection = create_test_db_connection()
    yield connection
    # Limpieza
    connection.close()

Creación de tests

Una vez configurado pytest en nuestro entorno, el siguiente paso es aprender a escribir tests efectivos. La creación de tests en pytest sigue una filosofía simple pero potente que facilita tanto la escritura como el mantenimiento del código de prueba.

Estructura básica de un test

En pytest, un test es simplemente una función cuyo nombre comienza con test_. Esta convención permite a pytest identificar automáticamente qué funciones debe ejecutar como tests:

# archivo: test_ejemplo.py

def test_suma():
    resultado = 2 + 2
    assert resultado == 4

La palabra clave assert es fundamental en pytest. A diferencia de otros frameworks que requieren métodos especiales de aserción, pytest aprovecha la instrucción assert nativa de Python y proporciona mensajes de error detallados cuando fallan las aserciones.

Organización de tests

Existen dos enfoques principales para organizar tus tests:

  • Funciones de test: Ideal para tests simples y directos
  • Clases de test: Útil para agrupar tests relacionados y compartir configuración

Enfoque funcional

# test_calculadora.py
def test_suma():
    assert 1 + 1 == 2

def test_resta():
    assert 3 - 1 == 2

Enfoque de clases

# test_calculadora.py
class TestCalculadora:
    def test_suma(self):
        assert 1 + 1 == 2
    
    def test_resta(self):
        assert 3 - 1 == 2

El enfoque de clases es particularmente útil cuando necesitas compartir estado o configuración entre varios tests.

Estructura de directorios recomendada

Para proyectos de tamaño mediano a grande, es recomendable seguir una estructura de directorios que separe claramente el código de producción de los tests:

mi_proyecto/
├── src/
│   └── mi_paquete/
│       ├── __init__.py
│       └── calculadora.py
└── tests/
    ├── __init__.py
    ├── unit/
    │   └── test_calculadora.py
    └── integration/
        └── test_sistema.py

Esta estructura permite organizar los tests por tipo (unitarios, integración, etc.) y mantiene una clara separación entre código de producción y código de prueba.

Convenciones de nomenclatura

Seguir convenciones de nomenclatura consistentes facilita la organización y descubrimiento de tests:

  • Archivos de test: test_<modulo>.py o <modulo>_test.py
  • Funciones de test: test_<funcionalidad>()
  • Clases de test: Test<Componente>
  • Métodos de test: test_<funcionalidad>()

Escribiendo tests efectivos

Un buen test debe ser claro, conciso y enfocado en probar una única funcionalidad. El patrón AAA (Arrange-Act-Assert) es una buena práctica para estructurar tus tests:

def test_division():
    # Arrange (Preparar)
    a = 10
    b = 2
    
    # Act (Actuar)
    resultado = a / b
    
    # Assert (Verificar)
    assert resultado == 5

Este patrón hace que los tests sean más legibles y mantenibles al separar claramente la preparación, la acción y la verificación.

Testing de excepciones

Verificar que el código lanza las excepciones esperadas es una parte importante del testing. Pytest proporciona una forma elegante de probar excepciones:

import pytest

def dividir(a, b):
    if b == 0:
        raise ValueError("No se puede dividir por cero")
    return a / b

def test_division_por_cero():
    with pytest.raises(ValueError) as excinfo:
        dividir(10, 0)
    
    # Opcionalmente, verificar el mensaje de error
    assert "No se puede dividir por cero" in str(excinfo.value)

El contexto pytest.raises() captura la excepción y permite verificar tanto su tipo como su mensaje.

Parametrización de tests

La parametrización permite ejecutar el mismo test con diferentes conjuntos de datos, lo que reduce la duplicación de código y aumenta la cobertura:

import pytest

def es_par(numero):
    return numero % 2 == 0

@pytest.mark.parametrize("numero,esperado", [
    (2, True),
    (3, False),
    (4, True),
    (5, False),
])
def test_es_par(numero, esperado):
    assert es_par(numero) == esperado

El decorador @pytest.mark.parametrize recibe dos argumentos:

  1. Una cadena con los nombres de los parámetros separados por comas
  2. Una lista de tuplas con los valores para cada combinación de parámetros

Testing de funciones con efectos secundarios

Cuando probamos funciones que tienen efectos secundarios (como modificar archivos o bases de datos), es importante asegurarse de que el test no afecte al entorno real:

import os
import tempfile

def test_escritura_archivo():
    # Crear un archivo temporal
    fd, path = tempfile.mkstemp()
    try:
        with os.fdopen(fd, 'w') as temp:
            temp.write("datos de prueba")
        
        # Verificar que el archivo contiene lo esperado
        with open(path, 'r') as f:
            contenido = f.read()
        assert contenido == "datos de prueba"
    finally:
        # Limpiar siempre, incluso si el test falla
        os.unlink(path)

Este enfoque garantiza que el test sea aislado y no deje "residuos" en el sistema.

Testing de código asíncrono

Python 3.13 mejora significativamente el soporte para código asíncrono. Pytest permite probar funciones asíncronas de forma natural:

import asyncio
import pytest

async def obtener_datos():
    await asyncio.sleep(0.1)  # Simular operación asíncrona
    return {"status": "ok", "data": [1, 2, 3]}

@pytest.mark.asyncio
async def test_obtener_datos():
    resultado = await obtener_datos()
    assert resultado["status"] == "ok"
    assert len(resultado["data"]) == 3

El decorador @pytest.mark.asyncio indica a pytest que debe ejecutar la función de test en un bucle de eventos asyncio. Para usar este decorador, necesitas instalar el plugin pytest-asyncio:

pip install pytest-asyncio

Testing de aplicaciones web

Para aplicaciones web, puedes combinar pytest con bibliotecas como requests para tests de integración:

import requests

def test_api_endpoint(servidor_prueba):
    # servidor_prueba sería una fixture que proporciona la URL base
    url = f"{servidor_prueba}/api/usuarios"
    respuesta = requests.get(url)
    
    assert respuesta.status_code == 200
    datos = respuesta.json()
    assert "usuarios" in datos

Buenas prácticas para escribir tests

  • Independencia: Cada test debe poder ejecutarse de forma aislada
  • Determinismo: Los tests deben producir el mismo resultado en cada ejecución
  • Enfoque: Cada test debe verificar una única funcionalidad
  • Claridad: El propósito del test debe ser evidente por su nombre y estructura
  • Rapidez: Los tests deben ejecutarse rápidamente para facilitar el desarrollo iterativo

Docstrings en tests

Documentar tus tests con docstrings claros ayuda a entender su propósito y comportamiento:

def test_autenticacion_usuario():
    """
    Verifica que un usuario válido puede autenticarse correctamente.
    
    El test comprueba:
    1. Que se devuelve un token JWT válido
    2. Que el token contiene la información correcta del usuario
    """
    # Implementación del test

Tests que documentan el código

Los tests bien escritos sirven como documentación viva del código. Muestran cómo se espera que funcione cada componente y proporcionan ejemplos de uso:

def test_formato_moneda():
    """Demuestra el uso de la función formato_moneda."""
    # Este test muestra que la función:
    # 1. Añade el símbolo de moneda
    # 2. Usa separadores de miles
    # 3. Muestra dos decimales
    assert formato_moneda(1234.56, "€") == "€1.234,56"
    assert formato_moneda(1000, "$") == "$1.000,00"

Marcadores personalizados

Los marcadores permiten categorizar tus tests y ejecutarlos selectivamente:

import pytest

@pytest.mark.lento
def test_operacion_costosa():
    # Test que toma mucho tiempo
    pass

@pytest.mark.integracion
def test_conexion_bd():
    # Test que requiere una base de datos
    pass

Para registrar estos marcadores y evitar advertencias, añade en tu pyproject.toml:

[tool.pytest.ini_options]
markers = [
    "lento: tests que toman mucho tiempo",
    "integracion: tests que requieren recursos externos",
]

Luego puedes ejecutar tests específicos:

# Ejecutar solo tests marcados como 'integracion'
pytest -m integracion

# Ejecutar todos excepto los tests lentos
pytest -m "not lento"

Testing de código con dependencias externas

Para probar código que depende de servicios externos, es recomendable usar mocks o stubs que simulen estas dependencias:

from unittest.mock import patch

def obtener_clima(ciudad):
    # En producción, esta función llamaría a una API externa
    # ...
    pass

def test_obtener_clima():
    # Simular la respuesta de la API externa
    with patch('mi_modulo.requests.get') as mock_get:
        mock_get.return_value.json.return_value = {
            "temperatura": 25,
            "condicion": "soleado"
        }
        mock_get.return_value.status_code = 200
        
        resultado = obtener_clima("Madrid")
        
        assert resultado["temperatura"] == 25
        assert resultado["condicion"] == "soleado"

Este enfoque permite probar tu código sin depender de servicios externos, lo que hace que los tests sean más rápidos y confiables.

Aserciones y fixtures

Las aserciones y fixtures son dos componentes fundamentales en pytest que permiten crear tests robustos, mantenibles y expresivos. Mientras que las aserciones verifican que el código se comporta como esperamos, las fixtures proporcionan un mecanismo elegante para preparar el entorno de prueba y compartir recursos entre tests.

Aserciones en pytest

A diferencia de otros frameworks de testing que requieren métodos específicos para realizar comprobaciones, pytest aprovecha la instrucción assert nativa de Python y la mejora con mensajes de error detallados y contextuales.

Aserciones básicas

La forma más simple de verificar resultados es mediante la instrucción assert:

def test_suma_simple():
    resultado = 2 + 2
    assert resultado == 4

Cuando una aserción falla, pytest genera automáticamente un mensaje de error detallado que muestra los valores reales y esperados:

def test_ejemplo_fallo():
    lista = [1, 2, 3, 5]
    assert 4 in lista  # Esto fallará

Al ejecutar este test, pytest mostrará algo como:

E       assert 4 in [1, 2, 3, 5]
E        +  where [1, 2, 3, 5] = [1, 2, 3, 5]

Comparación de estructuras complejas

Pytest maneja elegantemente la comparación de estructuras de datos complejas como diccionarios y listas:

def test_diccionario():
    resultado = {"nombre": "Ana", "edad": 30, "roles": ["admin", "editor"]}
    esperado = {"nombre": "Ana", "edad": 30, "roles": ["admin", "editor"]}
    assert resultado == esperado

Si hay diferencias, pytest mostrará exactamente dónde están:

def test_diccionario_diferencia():
    resultado = {"nombre": "Ana", "edad": 30, "roles": ["admin", "editor"]}
    esperado = {"nombre": "Ana", "edad": 31, "roles": ["admin", "editor"]}
    assert resultado == esperado  # Fallará y mostrará la diferencia en "edad"

Aserciones con mensajes personalizados

Puedes añadir mensajes personalizados a tus aserciones para proporcionar contexto adicional:

def test_con_mensaje():
    usuario_id = obtener_id_usuario("admin@ejemplo.com")
    assert usuario_id is not None, "No se pudo encontrar el ID para admin@ejemplo.com"

Verificación de excepciones

Para verificar que una función lanza la excepción esperada, utiliza el contexto pytest.raises:

import pytest

def dividir(a, b):
    if b == 0:
        raise ValueError("División por cero")
    return a / b

def test_division_por_cero():
    with pytest.raises(ValueError) as excinfo:
        dividir(10, 0)
    
    # Verificar el mensaje de la excepción
    assert "División por cero" in str(excinfo.value)

También puedes usar una sintaxis más concisa cuando solo necesitas verificar el tipo de excepción:

def test_division_por_cero_simple():
    with pytest.raises(ValueError):
        dividir(10, 0)

Verificación de advertencias

De manera similar, puedes verificar que tu código emite advertencias específicas:

import warnings

def funcion_con_advertencia():
    warnings.warn("Esta función está obsoleta", DeprecationWarning)
    return True

def test_advertencia():
    with pytest.warns(DeprecationWarning):
        assert funcion_con_advertencia()

Aserciones aproximadas

Para comparaciones de números de punto flotante, donde la igualdad exacta puede ser problemática debido a errores de redondeo, pytest proporciona pytest.approx:

def test_aproximacion():
    resultado = 0.1 + 0.2
    assert resultado == pytest.approx(0.3)  # Pasa a pesar de que 0.1 + 0.2 != 0.3 exactamente

También puedes especificar la tolerancia:

def test_aproximacion_con_tolerancia():
    valor = calcular_pi()
    assert valor == pytest.approx(3.14159, abs=1e-5)

Fixtures: preparando el entorno de prueba

Las fixtures son funciones que pytest ejecuta antes (y opcionalmente después) de los tests para preparar el entorno de prueba. Proporcionan una forma elegante de:

  • Configurar precondiciones para los tests
  • Proporcionar datos de prueba
  • Inyectar dependencias
  • Gestionar recursos compartidos
  • Realizar limpieza después de los tests

Definición básica de fixtures

Una fixture se define como una función decorada con @pytest.fixture:

import pytest

@pytest.fixture
def usuario_prueba():
    return {"id": 1, "nombre": "Usuario Test", "email": "test@ejemplo.com"}

def test_nombre_usuario(usuario_prueba):
    assert usuario_prueba["nombre"] == "Usuario Test"

Cuando un test recibe un parámetro con el mismo nombre que una fixture, pytest automáticamente ejecuta la fixture y pasa su valor de retorno al test.

Alcance de las fixtures

El alcance (scope) de una fixture determina con qué frecuencia se ejecuta:

@pytest.fixture(scope="function")  # Por defecto, se ejecuta para cada test
def conexion_bd_efimera():
    # Configurar
    conexion = crear_conexion_bd_prueba()
    yield conexion
    # Limpiar
    conexion.cerrar()

@pytest.fixture(scope="module")  # Se ejecuta una vez por módulo
def datos_compartidos():
    return cargar_datos_prueba()

@pytest.fixture(scope="session")  # Se ejecuta una vez por sesión de pytest
def servidor_prueba():
    # Iniciar servidor
    servidor = iniciar_servidor_prueba()
    yield servidor
    # Detener servidor
    servidor.detener()

Los posibles valores para scope son:

  • function: Se ejecuta para cada test (valor predeterminado)
  • class: Se ejecuta una vez por clase de test
  • module: Se ejecuta una vez por módulo
  • package: Se ejecuta una vez por paquete
  • session: Se ejecuta una vez por sesión de pytest

Fixtures con finalización

Para realizar limpieza después de un test, utiliza yield en lugar de return:

import os
import tempfile

@pytest.fixture
def archivo_temporal():
    # Configuración: crear archivo temporal
    fd, ruta = tempfile.mkstemp()
    with os.fdopen(fd, 'w') as f:
        f.write("datos iniciales")
    
    # Proporcionar la ruta al test
    yield ruta
    
    # Limpieza: eliminar archivo temporal
    os.unlink(ruta)

def test_leer_archivo(archivo_temporal):
    with open(archivo_temporal, 'r') as f:
        contenido = f.read()
    assert contenido == "datos iniciales"
    
    # Modificar el archivo
    with open(archivo_temporal, 'w') as f:
        f.write("nuevos datos")
    
    # El archivo se eliminará automáticamente después del test

Fixtures parametrizadas

Las fixtures pueden ser parametrizadas para proporcionar múltiples valores:

@pytest.fixture(params=[10, 20, 30])
def valor_entrada(request):
    return request.param

def test_multiplicacion(valor_entrada):
    resultado = valor_entrada * 2
    assert resultado == valor_entrada * 2

Este test se ejecutará tres veces, una para cada valor del parámetro.

Fixtures que utilizan otras fixtures

Las fixtures pueden depender de otras fixtures:

@pytest.fixture
def usuario():
    return {"id": 1, "nombre": "Admin"}

@pytest.fixture
def usuario_autenticado(usuario):
    usuario["token"] = "jwt_token_simulado"
    return usuario

def test_usuario_tiene_token(usuario_autenticado):
    assert "token" in usuario_autenticado
    assert usuario_autenticado["token"] == "jwt_token_simulado"

Fixtures autousables

Las fixtures marcadas con autouse=True se ejecutan automáticamente para cada test, sin necesidad de declararlas como parámetros:

@pytest.fixture(autouse=True)
def configurar_entorno():
    # Configurar variables de entorno para los tests
    os.environ["ENTORNO"] = "prueba"
    yield
    # Restaurar
    os.environ.pop("ENTORNO", None)

Compartir fixtures entre módulos con conftest.py

Para compartir fixtures entre múltiples archivos de test, colócalas en un archivo llamado conftest.py:

# conftest.py
import pytest
import sqlite3

@pytest.fixture(scope="session")
def bd_prueba():
    # Crear BD en memoria
    conexion = sqlite3.connect(":memory:")
    cursor = conexion.cursor()
    
    # Crear esquema
    cursor.execute("""
        CREATE TABLE usuarios (
            id INTEGER PRIMARY KEY,
            nombre TEXT,
            email TEXT
        )
    """)
    
    # Insertar datos de prueba
    cursor.execute("""
        INSERT INTO usuarios (nombre, email)
        VALUES ('Usuario Test', 'test@ejemplo.com')
    """)
    conexion.commit()
    
    yield conexion
    
    # Cerrar conexión
    conexion.close()

Luego, cualquier test en el mismo directorio o subdirectorios puede usar esta fixture:

# test_usuarios.py
def test_consulta_usuario(bd_prueba):
    cursor = bd_prueba.cursor()
    cursor.execute("SELECT * FROM usuarios WHERE nombre = ?", ("Usuario Test",))
    usuario = cursor.fetchone()
    assert usuario is not None
    assert usuario[1] == "Usuario Test"

Fixtures dinámicas

Puedes crear fixtures dinámicamente utilizando la función request.getfixturevalue():

@pytest.fixture
def datos_usuario():
    return {"nombre": "Ana", "rol": "admin"}

@pytest.fixture
def permisos_usuario(request):
    datos = request.getfixturevalue("datos_usuario")
    
    if datos["rol"] == "admin":
        return ["leer", "escribir", "eliminar"]
    else:
        return ["leer"]

def test_permisos_admin(permisos_usuario):
    assert "eliminar" in permisos_usuario

Fixtures para mocks

Las fixtures son ideales para configurar mocks que simulan dependencias externas:

from unittest.mock import patch, MagicMock

@pytest.fixture
def mock_api_clima():
    with patch('mi_app.servicios.api_clima.obtener_temperatura') as mock:
        mock.return_value = 25.5
        yield mock

def test_recomendacion_clima(mock_api_clima):
    from mi_app.recomendaciones import recomendar_actividad
    
    actividad = recomendar_actividad("Madrid")
    assert "soleado" in actividad.lower()
    
    # Verificar que se llamó a la API con los parámetros correctos
    mock_api_clima.assert_called_once_with("Madrid")

Fixtures para pruebas asíncronas

Para tests asíncronos, puedes crear fixtures que devuelvan objetos awaitable:

import pytest
import asyncio

@pytest.fixture
async def datos_async():
    await asyncio.sleep(0.1)  # Simular operación asíncrona
    return {"status": "ok", "datos": [1, 2, 3]}

@pytest.mark.asyncio
async def test_procesamiento_asincrono(datos_async):
    resultado = await procesar_datos(datos_async)
    assert resultado["procesado"] == True

Para usar fixtures asíncronas, necesitas instalar el plugin pytest-asyncio.

Combinando fixtures y parametrización

Puedes combinar fixtures con la parametrización de tests para crear casos de prueba más completos:

@pytest.fixture
def cliente_api():
    return ClienteAPI(base_url="https://api.ejemplo.com")

@pytest.mark.parametrize("id_usuario,nombre_esperado", [
    (1, "Ana García"),
    (2, "Carlos López"),
    (3, "Elena Martínez")
])
def test_obtener_usuario(cliente_api, id_usuario, nombre_esperado):
    # Configurar mock para simular respuesta de API
    cliente_api.configurar_respuesta(f"/usuarios/{id_usuario}", {
        "id": id_usuario,
        "nombre": nombre_esperado
    })
    
    # Ejecutar función bajo prueba
    usuario = obtener_detalles_usuario(cliente_api, id_usuario)
    
    # Verificar resultado
    assert usuario["nombre"] == nombre_esperado

Mejores prácticas para aserciones y fixtures

  • Mantén las fixtures enfocadas: Cada fixture debe tener una responsabilidad clara.
  • Usa el alcance adecuado: Elige el scope más restrictivo posible para mejorar el rendimiento.
  • Nombra descriptivamente: Usa nombres claros que indiquen lo que proporciona la fixture.
  • Documenta tus fixtures: Añade docstrings que expliquen el propósito y uso de cada fixture.
  • Prefiere múltiples fixtures pequeñas: En lugar de una fixture grande que lo haga todo.
  • Limpia siempre los recursos: Usa yield para asegurar la limpieza de recursos como archivos o conexiones.
  • Evita efectos secundarios: Las fixtures no deben modificar el estado global de forma permanente.

Con estas herramientas, puedes crear tests que no solo verifican el comportamiento de tu código, sino que también son legibles, mantenibles y eficientes.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

Ejercicios de esta lección Testing con pytest

Evalúa tus conocimientos de esta lección Testing con pytest con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

Todas las lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Instalar y configurar pytest y sus plugins en entornos virtuales.
  • Escribir y organizar tests efectivos usando funciones y clases.
  • Utilizar aserciones avanzadas para verificar comportamientos y excepciones.
  • Crear y gestionar fixtures para preparar y limpiar el entorno de pruebas.
  • Aplicar buenas prácticas y técnicas avanzadas como parametrización y testing asíncrono.