Tests unitarios en Python: unittest, pytest y mocks

Publicado el: 11/07/2026
Tempo de leitura: 5 minutos
Exemplo de testes unitários em Python com código de unittest para validação automatizada

Los tests unitarios permiten comprobar automáticamente que una función, una clase o una pequeña parte de una aplicación produce el resultado esperado. En lugar de ejecutar el programa completo y revisar todo manualmente, puedes escribir casos que se repiten en segundos después de cada cambio. Esta práctica reduce regresiones, facilita las refactorizaciones y convierte los errores en información concreta.

Para aprovechar esta guía conviene conocer las funciones en Python, el manejo de excepciones, la organización de módulos y paquetes, los fundamentos de pytest y la automatización de scripts de Python.

Qué es un test unitario

Una unidad suele ser la parte más pequeña que puede probarse de forma aislada: una función, un método o una regla de negocio. Un buen test prepara datos, ejecuta la unidad y compara el resultado con una expectativa. Si la expectativa no se cumple, el test falla y muestra dónde investigar.

Supongamos esta función:

def calcular_total(precios, descuento=0):
    subtotal = sum(precios)
    return subtotal * (1 - descuento)

Un test sencillo puede verificar el caso normal y también los límites:

def test_calcular_total_sin_descuento():
    assert calcular_total([10, 20, 30]) == 60


def test_calcular_total_con_descuento():
    assert calcular_total([100], 0.20) == 80


def test_calcular_total_lista_vacia():
    assert calcular_total([]) == 0

Empezar con unittest

unittest forma parte de la biblioteca estándar. No necesitas instalar nada. La documentación oficial de unittest describe clases de prueba, aserciones, preparación y descubrimiento automático.

import unittest


class TestCalcularTotal(unittest.TestCase):
    def test_sin_descuento(self):
        self.assertEqual(calcular_total([10, 20]), 30)

    def test_con_descuento(self):
        self.assertAlmostEqual(calcular_total([100], 0.15), 85)


if __name__ == "__main__":
    unittest.main()

Los métodos deben comenzar con test_. Entre las aserciones más útiles están assertEqual, assertTrue, assertIsNone, assertIn y assertRaises. Para números decimales, assertAlmostEqual evita fallos causados por pequeñas diferencias de representación.

Probar excepciones

Los errores esperados también forman parte del contrato de una función:

def dividir(a, b):
    if b == 0:
        raise ValueError("El divisor no puede ser cero")
    return a / b


class TestDividir(unittest.TestCase):
    def test_division_normal(self):
        self.assertEqual(dividir(10, 2), 5)

    def test_divisor_cero(self):
        with self.assertRaises(ValueError):
            dividir(10, 0)

El test no debe ocultar una excepción inesperada. Debe confirmar únicamente el tipo de error que la función promete producir.

Usar pytest

Pytest permite escribir tests más compactos y ofrece fixtures, parametrización, plugins e informes claros. Instálalo dentro de un entorno virtual:

python -m pip install pytest
python -m pytest

La documentación oficial de pytest es la referencia principal para fixtures, parametrización y configuración.

import pytest


@pytest.mark.parametrize(
    "precios, descuento, esperado",
    [
        ([10, 20], 0, 30),
        ([100], 0.10, 90),
        ([], 0, 0),
    ],
)
def test_calcular_total(precios, descuento, esperado):
    assert calcular_total(precios, descuento) == esperado

La parametrización evita copiar el mismo test para muchos datos. Cada combinación aparece como un caso separado en el informe.

Fixtures para preparar datos

Una fixture crea recursos reutilizables, como usuarios, archivos temporales o conexiones de prueba:

import pytest


@pytest.fixture
def usuario():
    return {"nombre": "Ana", "activo": True}


def test_usuario_activo(usuario):
    assert usuario["activo"] is True

Para archivos, utiliza tmp_path, que crea una carpeta temporal aislada:

def guardar_texto(ruta, contenido):
    ruta.write_text(contenido, encoding="utf-8")


def test_guardar_texto(tmp_path):
    archivo = tmp_path / "salida.txt"
    guardar_texto(archivo, "hola")
    assert archivo.read_text(encoding="utf-8") == "hola"

Qué es un mock

Un mock sustituye una dependencia externa durante el test. Es útil para APIs, correo, reloj, sistema de archivos o bases de datos. El objetivo no es simular todo, sino aislar la lógica que realmente quieres comprobar.

from unittest.mock import patch


def obtener_temperatura(cliente):
    respuesta = cliente.get("/temperatura")
    return respuesta.json()["valor"]


def test_obtener_temperatura():
    with patch("mi_modulo.cliente") as cliente_falso:
        cliente_falso.get.return_value.json.return_value = {"valor": 24}
        assert obtener_temperatura(cliente_falso) == 24

Comprueba también que la dependencia recibió los argumentos correctos con assert_called_once_with. No abuses de mocks internos porque pueden hacer que el test confirme la implementación en vez del comportamiento.

Estructura recomendada

proyecto/
├── src/
│   └── tienda/
│       ├── __init__.py
│       └── precios.py
├── tests/
│   ├── test_precios.py
│   └── test_usuarios.py
└── pyproject.toml

Mantén los tests separados del código de producción, pero con nombres que permitan localizar rápidamente la unidad relacionada. Cada test debe ser independiente: su resultado no puede depender del orden de ejecución.

Patrón Arrange, Act, Assert

Una estructura clara divide el test en tres fases. Arrange prepara datos; Act ejecuta la operación; Assert verifica el resultado:

def test_aplicar_impuesto():
    # Arrange
    precio = 100
    tasa = 0.21

    # Act
    total = precio * (1 + tasa)

    # Assert
    assert total == 121

En tests pequeños no necesitas comentarios, pero la separación visual ayuda a comprender casos complejos.

Evitar tests frágiles

No pruebes detalles privados sin necesidad. Un test que depende del nombre de una variable interna se rompe aunque el comportamiento siga siendo correcto. Evita fechas reales, servicios externos y rutas absolutas. Congela o inyecta el reloj, usa archivos temporales y sustituye las llamadas de red.

Tampoco conviene generar datos aleatorios sin una semilla o sin registrar el caso que falló. Los tests deben ser deterministas: con el mismo código y entorno deben producir el mismo resultado.

Cobertura y calidad

La cobertura indica qué líneas fueron ejecutadas, pero no demuestra que las comprobaciones sean buenas. Un proyecto puede tener cobertura alta y aserciones débiles. Prioriza reglas importantes, límites, errores, entradas vacías, valores negativos y permisos.

python -m pip install pytest-cov
python -m pytest --cov=src --cov-report=term-missing

Ejecutar tests automáticamente

Ejecuta la suite antes de cada commit y también en integración continua. Un flujo básico instala dependencias y llama a python -m pytest. No guardes secretos reales en los tests. Usa variables específicas y servicios efímeros para bases de datos.

Errores frecuentes

Los fallos más comunes son probar varias ideas en un solo caso, usar datos compartidos mutables, depender de Internet, ignorar excepciones, comparar floats con igualdad exacta y escribir tests después de que aparece el error sin reproducir primero el problema. También es un error hacer que un test modifique archivos o bases de producción.

Conclusión

Los tests unitarios convierten el comportamiento esperado en una especificación ejecutable. Empieza con funciones pequeñas, añade casos normales y límites, prueba excepciones y utiliza fixtures para aislar recursos. unittest ofrece una solución estándar y pytest reduce el código repetitivo. Los mocks ayudan con dependencias externas, pero deben usarse con moderación. Una suite rápida, independiente y legible permite mejorar el proyecto con mucha más seguridad.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Configuración TOML en un proyecto Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    tomllib en Python: lee archivos TOML

    Aprende a leer TOML con tomllib en Python, validar configuraciones, gestionar errores y organizar proyectos con pyproject.toml.

    Ler mais

    Tempo de leitura: 6 minutos
    24/07/2026
    Desarrollador monitoreando registros estructurados en Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Observabilidad en Python con structlog

    Aprende observabilidad en Python con structlog, eventos JSON, contexto, pruebas, control de volumen y recomendaciones para producción.

    Ler mais

    Tempo de leitura: 5 minutos
    24/07/2026
    Configuración segura de aplicaciones Python con Pydantic Settings
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Pydantic Settings: configuración segura

    Aprende a validar variables de entorno, organizar configuraciones y proteger secretos en proyectos Python con Pydantic Settings.

    Ler mais

    Tempo de leitura: 5 minutos
    23/07/2026
    Desenvolvedor programando em Python com Ruff para lint e formatação
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Ruff: lint y formato de código Python

    Aprende Ruff en Python para lint, formato, correcciones automáticas, pyproject.toml, VS Code y CI con una configuración práctica.

    Ler mais

    Tempo de leitura: 10 minutos
    22/07/2026
    Dicas para melhorar performance de scripts Python lentos
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    ¿Por qué Python es lento? Causas y optimización

    Descubre por qué Python puede ser lento y mejora su rendimiento con cProfile, algoritmos, sets, generadores, NumPy, caché y concurrencia.

    Ler mais

    Tempo de leitura: 5 minutos
    12/07/2026
    Leitura segura de senhas no terminal usando Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    getpass: lee contraseñas en terminal con Python

    Lee contraseñas de forma segura con getpass, valida entradas, evita logs y texto plano y almacena credenciales con hashing adecuado.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026