21. Probar código que trabaja con listas, diccionarios y fechas

21.1 Objetivo del tema

Muchas aplicaciones trabajan con colecciones de datos: listas de ventas, diccionarios de usuarios, fechas de pedidos o reportes por período. Estos casos suelen tener errores en filtros, orden, acumulaciones y límites de fecha.

En este tema probaremos funciones que procesan listas, diccionarios y fechas usando pytest.

Idea clave: cuando probamos colecciones, hay que verificar contenido, orden, casos vacíos y límites de fecha.

21.2 Crear una carpeta de práctica

Crea un proyecto nuevo:

mkdir pytest-colecciones-fechas-demo
cd pytest-colecciones-fechas-demo

Si pytest no está instalado en el entorno activo:

python -m pip install pytest

21.3 Crear el módulo a probar

Crea un archivo llamado reportes.py:

from datetime import date


def calcular_total_ventas(ventas):
    return sum(venta["importe"] for venta in ventas)


def filtrar_ventas_por_cliente(ventas, cliente):
    return [venta for venta in ventas if venta["cliente"] == cliente]


def obtener_clientes_unicos(ventas):
    return sorted({venta["cliente"] for venta in ventas})


def agrupar_total_por_cliente(ventas):
    totales = {}
    for venta in ventas:
        cliente = venta["cliente"]
        totales[cliente] = totales.get(cliente, 0) + venta["importe"]
    return totales


def filtrar_ventas_por_fecha(ventas, desde, hasta):
    return [
        venta
        for venta in ventas
        if desde <= venta["fecha"] <= hasta
    ]


def venta_mas_reciente(ventas):
    if not ventas:
        return None
    return max(ventas, key=lambda venta: venta["fecha"])


def dias_entre(inicio, fin):
    return (fin - inicio).days

Usamos datetime.date para representar fechas sin hora.

21.4 Crear datos de prueba con fixture

Crea test_reportes.py y prepara ventas reutilizables:

from datetime import date

import pytest


@pytest.fixture
def ventas():
    return [
        {"cliente": "Ana", "importe": 1000, "fecha": date(2026, 5, 1)},
        {"cliente": "Luis", "importe": 500, "fecha": date(2026, 5, 2)},
        {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)},
    ]

La fixture permite reutilizar la misma lista en varias pruebas sin repetirla.

21.5 Probar el total de una lista

Agrega esta prueba:

from reportes import calcular_total_ventas


def test_calcular_total_ventas(ventas):
    resultado = calcular_total_ventas(ventas)

    assert resultado == 3000

La prueba verifica que se sumen todos los importes de la lista.

21.6 Probar lista vacía

Una lista vacía debe devolver total cero:

def test_calcular_total_ventas_con_lista_vacia():
    assert calcular_total_ventas([]) == 0

Este caso evita errores cuando no hay ventas para procesar.

21.7 Probar filtro por diccionario

Para filtrar por cliente:

from reportes import filtrar_ventas_por_cliente


def test_filtrar_ventas_por_cliente(ventas):
    resultado = filtrar_ventas_por_cliente(ventas, "Ana")

    assert resultado == [
        {"cliente": "Ana", "importe": 1000, "fecha": date(2026, 5, 1)},
        {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)},
    ]

Cuando comparamos listas de diccionarios, importan los valores y el orden.

21.8 Probar filtro sin resultados

Si no hay coincidencias, esperamos una lista vacía:

def test_filtrar_ventas_por_cliente_sin_resultados(ventas):
    resultado = filtrar_ventas_por_cliente(ventas, "Marta")

    assert resultado == []

21.9 Probar clientes únicos

La función devuelve nombres únicos ordenados:

from reportes import obtener_clientes_unicos


def test_obtener_clientes_unicos(ventas):
    resultado = obtener_clientes_unicos(ventas)

    assert resultado == ["Ana", "Luis"]

Usar orden explícito hace que el resultado sea fácil de comparar.

21.10 Probar agrupación en diccionario

Para totales por cliente:

from reportes import agrupar_total_por_cliente


def test_agrupar_total_por_cliente(ventas):
    resultado = agrupar_total_por_cliente(ventas)

    assert resultado == {
        "Ana": 2500,
        "Luis": 500,
    }

Los diccionarios se comparan por claves y valores.

21.11 Probar filtro por fechas

Importa filtrar_ventas_por_fecha y prueba un rango:

from reportes import filtrar_ventas_por_fecha


def test_filtrar_ventas_por_fecha(ventas):
    resultado = filtrar_ventas_por_fecha(
        ventas,
        date(2026, 5, 2),
        date(2026, 5, 3),
    )

    assert resultado == [
        {"cliente": "Luis", "importe": 500, "fecha": date(2026, 5, 2)},
        {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)},
    ]

El rango incluye fecha inicial y fecha final.

21.12 Probar límites de fecha

Conviene probar que los límites son inclusivos:

def test_filtrar_ventas_por_fecha_incluye_limites(ventas):
    resultado = filtrar_ventas_por_fecha(
        ventas,
        date(2026, 5, 1),
        date(2026, 5, 1),
    )

    assert resultado == [
        {"cliente": "Ana", "importe": 1000, "fecha": date(2026, 5, 1)},
    ]

21.13 Probar venta más reciente

La función debe devolver el diccionario con fecha mayor:

from reportes import venta_mas_reciente


def test_venta_mas_reciente(ventas):
    resultado = venta_mas_reciente(ventas)

    assert resultado == {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)}

También debemos cubrir lista vacía:

def test_venta_mas_reciente_con_lista_vacia():
    assert venta_mas_reciente([]) is None

21.14 Probar diferencia entre fechas

Para calcular días entre dos fechas:

from reportes import dias_entre


def test_dias_entre_fechas():
    resultado = dias_entre(date(2026, 5, 1), date(2026, 5, 10))

    assert resultado == 9

La diferencia entre fechas devuelve la cantidad de días completos entre ambas.

21.15 Parametrizar diferencias de fechas

En el archivo completo usaremos una prueba parametrizada para reemplazar la prueba individual anterior y cubrir varios rangos con la misma función de prueba:

@pytest.mark.parametrize("inicio, fin, esperado", [
    (date(2026, 5, 1), date(2026, 5, 1), 0),
    (date(2026, 5, 1), date(2026, 5, 2), 1),
    (date(2026, 5, 1), date(2026, 5, 10), 9),
])
def test_dias_entre_parametrizado(inicio, fin, esperado):
    assert dias_entre(inicio, fin) == esperado

21.16 Archivo completo de pruebas

El archivo test_reportes.py puede quedar así:

from datetime import date

import pytest

from reportes import (
    agrupar_total_por_cliente,
    calcular_total_ventas,
    dias_entre,
    filtrar_ventas_por_cliente,
    filtrar_ventas_por_fecha,
    obtener_clientes_unicos,
    venta_mas_reciente,
)


@pytest.fixture
def ventas():
    return [
        {"cliente": "Ana", "importe": 1000, "fecha": date(2026, 5, 1)},
        {"cliente": "Luis", "importe": 500, "fecha": date(2026, 5, 2)},
        {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)},
    ]


def test_calcular_total_ventas(ventas):
    assert calcular_total_ventas(ventas) == 3000


def test_calcular_total_ventas_con_lista_vacia():
    assert calcular_total_ventas([]) == 0


def test_filtrar_ventas_por_cliente(ventas):
    resultado = filtrar_ventas_por_cliente(ventas, "Ana")
    assert resultado == [
        {"cliente": "Ana", "importe": 1000, "fecha": date(2026, 5, 1)},
        {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)},
    ]


def test_filtrar_ventas_por_cliente_sin_resultados(ventas):
    assert filtrar_ventas_por_cliente(ventas, "Marta") == []


def test_obtener_clientes_unicos(ventas):
    assert obtener_clientes_unicos(ventas) == ["Ana", "Luis"]


def test_agrupar_total_por_cliente(ventas):
    assert agrupar_total_por_cliente(ventas) == {
        "Ana": 2500,
        "Luis": 500,
    }


def test_filtrar_ventas_por_fecha(ventas):
    resultado = filtrar_ventas_por_fecha(
        ventas,
        date(2026, 5, 2),
        date(2026, 5, 3),
    )
    assert resultado == [
        {"cliente": "Luis", "importe": 500, "fecha": date(2026, 5, 2)},
        {"cliente": "Ana", "importe": 1500, "fecha": date(2026, 5, 3)},
    ]


def test_filtrar_ventas_por_fecha_incluye_limites(ventas):
    resultado = filtrar_ventas_por_fecha(
        ventas,
        date(2026, 5, 1),
        date(2026, 5, 1),
    )
    assert resultado == [
        {"cliente": "Ana", "importe": 1000, "fecha": date(2026, 5, 1)},
    ]


def test_venta_mas_reciente(ventas):
    assert venta_mas_reciente(ventas) == {
        "cliente": "Ana",
        "importe": 1500,
        "fecha": date(2026, 5, 3),
    }


def test_venta_mas_reciente_con_lista_vacia():
    assert venta_mas_reciente([]) is None


@pytest.mark.parametrize("inicio, fin, esperado", [
    (date(2026, 5, 1), date(2026, 5, 1), 0),
    (date(2026, 5, 1), date(2026, 5, 2), 1),
    (date(2026, 5, 1), date(2026, 5, 10), 9),
])
def test_dias_entre_parametrizado(inicio, fin, esperado):
    assert dias_entre(inicio, fin) == esperado

21.17 Ejecutar las pruebas

Ejecuta:

python -m pytest

La salida esperada será similar a:

collected 13 items

test_reportes.py .............                                  [100%]

13 passed in 0.04s

21.18 Probar orden en listas

Cuando una función devuelve una lista, decide si el orden forma parte del comportamiento. Si importa, compara la lista completa:

assert resultado == ["Ana", "Luis"]

Si el orden no importa, puedes comparar conjuntos:

assert set(resultado) == {"Ana", "Luis"}

21.19 Probar diccionarios completos o parciales

Si toda la estructura importa, compara el diccionario completo. Si solo importa una regla, verifica una clave:

assert resultado["Ana"] == 2500

Esto evita pruebas demasiado rígidas cuando el diccionario puede crecer con más datos.

21.20 Errores frecuentes

  • No probar lista vacía: muchas funciones de colección fallan sin elementos.
  • Ignorar el orden: si el orden importa, debe aparecer en la prueba.
  • Comparar fechas como cadenas sin necesidad: usa date para trabajar con fechas reales.
  • No probar límites de rango: las fechas inicial y final suelen provocar errores.
  • Usar datos de prueba demasiado grandes: pocos datos bien elegidos suelen ser más claros.

21.21 Comandos usados en este tema

mkdir pytest-colecciones-fechas-demo
cd pytest-colecciones-fechas-demo
python -m pip install pytest
python -m pytest
python -m pytest -v
python -m pytest test_reportes.py::test_filtrar_ventas_por_fecha -v

21.22 Qué debes recordar de este tema

  • Las listas se comparan por contenido y orden.
  • Los diccionarios pueden compararse completos o por claves específicas.
  • Las listas vacías son casos importantes.
  • Para fechas conviene usar datetime.date.
  • Los rangos de fecha deben probar límites inicial y final.
  • Las fixtures ayudan a reutilizar colecciones de datos.

21.23 Conclusión

En este tema probamos funciones que trabajan con listas, diccionarios y fechas. Vimos filtros, totales, agrupaciones, orden, listas vacías y límites de fecha.

En el próximo tema trabajaremos con lectura y escritura de archivos temporales, una necesidad frecuente al probar código que interactúa con el sistema de archivos.