PythonAprende PythonDocumentación

Testing con pytest

Escribe pruebas automáticas: la red de seguridad de tu código.

Un test es código que verifica tu código. pytest es el framework estándar de Python: escribes funciones normales con asserts y él hace el resto. Sin tests, cada cambio es un salto al vacío.

Tu primer test

Digamos que tienes mis_funciones.py:

def sumar(a, b):
    return a + b

Creas test_mis_funciones.py:

from mis_funciones import sumar

def test_sumar_dos_numeros():
    assert sumar(2, 3) == 5

def test_sumar_negativos():
    assert sumar(-1, -1) == -2

Ejecutas en la terminal:

pytest          # descubre y corre todos los test_*.py

Y ves:

test_mis_funciones.py ..                    [100%]
2 passed

Eso es todo: archivos test_*.py con funciones test_*() que usan assert. pytest los descubre por convención.

La convención de descubrimiento

  • Archivos: test_algo.py o algo_test.py
  • Funciones: empiezan con test_
  • Clases de tests: TestAlgo (sin __init__)

Si el nombre no cumple la convención, pytest no lo corre — error típico de principiante.

Qué probar: los tres niveles

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

def test_caso_normal():
    assert dividir(10, 2) == 5.0

def test_caso_limite():
    assert dividir(0, 5) == 0.0      # el "borde" de tu dominio

def test_caso_de_error():            # verificar que SÍ lanza
    import pytest
    with pytest.raises(ValueError):
        dividir(1, 0)
  • Caso normal: entrada típica, resultado esperado
  • Casos límite: ceros, vacíos, negativos, valores máximos — donde viven los bugs
  • Casos de error: verificar que lanza la excepción correcta (pytest.raises)

Fixtures: preparar el escenario

Cuando varios tests necesitan los mismos datos, una fixture los construye:

import pytest

@pytest.fixture
def lista_ejemplo():
    return [3, 1, 2]

def test_maximo(lista_ejemplo):        # pytest inyecta la fixture
    assert max(lista_ejemplo) == 3

def test_ordenada(lista_ejemplo):
    assert sorted(lista_ejemplo) == [1, 2, 3]

La fixture se declara una vez y pytest la pasa como argumento por su nombre. Ideal para datos de prueba, conexiones temporales, archivos temporales (tmp_path).

Estructura de un buen test: AAA

def test_retiro_resta_del_saldo():
    # Arrange: preparar el escenario
    cuenta = Cuenta(saldo_inicial=100)

    # Act: ejecutar la acción bajo prueba
    cuenta.retirar(30)

    # Assert: verificar el resultado
    assert cuenta.saldo == 70

Un test = una sola razón de fallo. Si pruebas cinco cosas en un test y falla, no sabes cuál.

La regla de oro

No pruebes la implementación (cómo lo hace); prueba el comportamiento (qué promete). Así puedes refactorizar todo el interior sin romper un solo test — la red de seguridad que te deja moverte rápido sin miedo.

Resumen

  • test_*.py con funciones test_*() y assert — eso es pytest
  • Prueba casos normales, límites y errores (pytest.raises)
  • Las fixtures preparan datos reutilizables que se inyectan por nombre
  • Estructura AAA: Arrange, Act, Assert
  • Los tests no evitan bugs: los detectan antes de que lleguen a producción

Quiz

  1. 1. ¿Cómo debe llamarse un archivo y una función de test para que pytest los descubra?

  2. 2. ¿Con qué instrucción se afirma un resultado en pytest?

  3. 3. ¿Para qué sirve una fixture en pytest?

Ejercicios

Ejercicio 1: Tu primer caso de prueba

Completa `es_palindromo(texto)` (se lee igual al derecho y al revés, sin distinguir mayúsculas). Los asserts funcionan como un mini pytest en tu navegador.

Cargando editor…

Ejercicio 2: Pensar en casos límite

Completa `suma_digitos(n)` (suma los dígitos del número). Los asserts cubren el caso normal, el límite (0) y números grandes.

Cargando editor…