Pruebas en Python — ejercicios para dominar unittest, caja negra y TDD
Las pruebas en Python ejercicios con solución cierran este bloque. Ya viste la teoría y practicaste con programas reales. Ahora toca resolver por tu cuenta. Tres ejercicios en tres niveles: caja negra, detección de errores en pruebas mal diseñadas y TDD para una clase completa. El hilo conductor es siempre el mismo objeto: una cuenta bancaria sencilla.
Como siempre: intenta resolverlo, usa la pista si llevas más de 10 minutos atascado, y compara con la solución comentada al final.
Tabla de Contenidos
Pruebas en Python ejercicios — Nivel Básico
Ejercicio 1 — Diseñar pruebas de caja negra para validar un ingreso
Tienes esta especificación de una función. No ves el código, solo la descripción:
función: validar_ingreso(cantidad, saldo_actual)
Valida si se puede realizar un ingreso en una cuenta bancaria.
Parámetros:
cantidad (float o int): importe a ingresar
saldo_actual (float): saldo actual de la cuenta
Devuelve:
True si el ingreso es válido
False si la cantidad es 0
Lanza:
TypeError si cantidad o saldo_actual no son numéricos
ValueError si cantidad es negativa
ValueError si saldo_actual es negativo
ValueError si el saldo resultante superaría 100.000€ (límite de cuenta)
Restricciones:
- cantidad debe ser > 0 para ser válida (0 devuelve False, negativo lanza error)
- saldo_actual debe ser >= 0
- saldo_actual + cantidad no puede superar 100.000
Tu tarea es:
- Identificar todas las clases de equivalencia válidas e inválidas
- Elegir los valores representativos de cada clase
- Identificar los valores límite de cada frontera
- Escribir el conjunto completo de pruebas con
unittest
La salida esperada al ejecutar tus pruebas (cuando la implementación sea correcta):
test_ce_cantidad_cero ... ok test_ce_cantidad_valida ... ok test_ce_saldo_resultante_en_limite ... ok test_ce_tipo_incorrecto_cantidad ... ok test_ce_tipo_incorrecto_saldo ... ok test_ce_valor_negativo_cantidad ... ok test_ce_valor_negativo_saldo ... ok test_ce_saldo_supera_limite ... ok test_limite_cantidad_minima ... ok test_limite_saldo_exactamente_cero ... ok test_limite_saldo_resultado_exactamente_100000 ... ok test_limite_saldo_resultado_supera_100000 ... ok Ran 12 tests in 0.001s OK
💡 Pista — el error más común en caja negra:
El error que más aparece en FP2 es diseñar pruebas que solo cubren el camino feliz, los casos donde todo va bien. Una buena batería de pruebas de caja negra tiene más pruebas de casos inválidos que de casos válidos, porque los errores casi siempre están en los extremos y en los casos que el programador no anticipó.
# MAL — solo prueba el caso normal
def test_ingreso(self):
self.assertTrue(validar_ingreso(100, 500))
# BIEN — prueba todos los casos que pueden fallar
def test_cantidad_cero_devuelve_false(self):
self.assertFalse(validar_ingreso(0, 500))
def test_cantidad_negativa_lanza_error(self):
with self.assertRaises(ValueError):
validar_ingreso(-50, 500)
def test_saldo_supera_limite(self):
with self.assertRaises(ValueError):
validar_ingreso(1, 100000) # 100000 + 1 > 100000
Para los valores límite de esta función presta especial atención a las fronteras: cantidad=0 (devuelve False), cantidad=0.01 (válida), saldo_actual + cantidad = 100000 (válido), saldo_actual + cantidad = 100000.01 (error).
Pruebas en Python ejercicios — Nivel Interedio
Ejercicio 2 — Encuentra los errores en estas pruebas mal diseñadas
Un compañero ha escrito las pruebas para la clase CuentaBancaria pero ha cometido varios errores de diseño. Tu tarea es identificar exactamente qué está mal en cada prueba y reescribirla correctamente.
Esta es la especificación de la clase que se está probando:
class CuentaBancaria:
"""
Cuenta bancaria con titular, saldo y límite de 100.000€.
__init__(titular, saldo_inicial=0)
titular (str): nombre del titular, mínimo 2 caracteres
saldo_inicial (float): saldo inicial, debe ser >= 0
Lanza ValueError si titular tiene menos de 2 caracteres
Lanza ValueError si saldo_inicial es negativo
ingresar(cantidad)
Ingresa cantidad en la cuenta.
Lanza ValueError si cantidad <= 0
Lanza ValueError si el saldo superaría 100.000€
retirar(cantidad)
Retira cantidad de la cuenta.
Lanza ValueError si cantidad <= 0
Lanza ValueError si no hay saldo suficiente
saldo (property): saldo actual
titular (property): nombre del titular
"""
Y estas son las pruebas mal diseñadas de tu compañero — hay 6 errores en total:
import unittest
class TestCuentaBancaria(unittest.TestCase):
# PRUEBA A
def crear_cuenta(self):
cuenta = CuentaBancaria("Sergio", 1000)
self.assertEqual(cuenta.saldo, 1000)
# PRUEBA B
def test_ingreso_correcto(self):
cuenta = CuentaBancaria("Sergio", 500)
cuenta.ingresar(200)
cuenta.ingresar(300)
cuenta.ingresar(100)
self.assertEqual(cuenta.saldo, 1100)
# PRUEBA C
def test_retirada_correcta(self):
cuenta = CuentaBancaria("Sergio", 500)
cuenta.retirar(200)
self.assertEqual(cuenta.saldo, 200) # ← ¿es correcto este valor?
# PRUEBA D
def test_titular_invalido(self):
cuenta = CuentaBancaria("A", 0)
self.assertFalse(cuenta)
# PRUEBA E
def test_saldo_insuficiente(self):
cuenta = CuentaBancaria("Sergio", 100)
cuenta.retirar(200)
self.assertEqual(cuenta.saldo, -100)
# PRUEBA F
def test_comparar_saldos(self):
cuenta1 = CuentaBancaria("Sergio", 500)
cuenta2 = CuentaBancaria("María", 500)
self.assertEqual(cuenta1.saldo == cuenta2.saldo, True)
if __name__ == "__main__":
unittest.main()
Para cada prueba, identifica:
- ¿Hay un error? Sí o no
- ¿Qué tipo de error es? (nombre incorrecto, lógica incorrecta, assert incorrecto, etc.)
- ¿Cómo la reescribirías?
💡 Pista — los errores más frecuentes en pruebas:
Hay tres categorías de errores que aparecen siempre en FP2:
# ERROR 1 — método sin prefijo test_ → no se ejecuta nunca
def crear_cuenta(self): # ← unittest no lo ve
...
# CORRECCIÓN: def test_crear_cuenta(self):
# ERROR 2 — assert incorrecto para la situación
self.assertFalse(cuenta) # prueba si cuenta es falsy, no si lanza error
self.assertEqual(resultado, True) # usa assertTrue directamente
# CORRECCIÓN:
with self.assertRaises(ValueError): ... # para excepciones
self.assertTrue(condicion) # para booleanos
# ERROR 3 — lógica incorrecta en el valor esperado
cuenta = CuentaBancaria("Sergio", 500)
cuenta.retirar(200)
self.assertEqual(cuenta.saldo, 200) # 500 - 200 = 300, no 200
# CORRECCIÓN: self.assertEqual(cuenta.saldo, 300)
Pruebas en Python ejercicios — Desafío Final
Ejercicio 3 — TDD para la clase CuentaBancaria completa
Construye la clase CuentaBancaria completa usando TDD, primero la prueba, luego el código mínimo, luego refactorizas. Sigue el ciclo ROJO → VERDE → REFACTOR en cada iteración.
La clase debe tener exactamente lo que describe la especificación del Ejercicio 2, más:
__str__que devuelva"Cuenta de {titular}: {saldo}€"__repr__que devuelva"CuentaBancaria('{titular}', {saldo})"transferir(cantidad, cuenta_destino)que transfiera dinero de una cuenta a otra — lanzaValueErrorsi no hay saldo suficiente o si la cantidad es inválida
El orden de las iteraciones TDD que debes seguir:
Iteración 1: Constructor con titular y saldo_inicial Iteración 2: Validación del titular (mínimo 2 caracteres) Iteración 3: Validación del saldo inicial (no negativo) Iteración 4: Método ingresar — caso normal Iteración 5: Método ingresar — validaciones (cantidad <= 0, límite 100.000€) Iteración 6: Método retirar — caso normal Iteración 7: Método retirar — validaciones (cantidad <= 0, saldo insuficiente) Iteración 8: __str__ y __repr__ Iteración 9: transferir — caso normal y validaciones
La salida esperada al ejecutar todas las pruebas al final:
test_constructor_titular_y_saldo ... ok test_constructor_saldo_por_defecto ... ok test_titular_minimo_dos_caracteres ... ok test_titular_exactamente_dos_caracteres ... ok test_saldo_inicial_negativo ... ok test_ingresar_cantidad_valida ... ok test_ingresar_actualiza_saldo ... ok test_ingresar_cantidad_cero ... ok test_ingresar_cantidad_negativa ... ok test_ingresar_supera_limite ... ok test_ingresar_exactamente_limite ... ok test_retirar_cantidad_valida ... ok test_retirar_actualiza_saldo ... ok test_retirar_cantidad_cero ... ok test_retirar_cantidad_negativa ... ok test_retirar_saldo_insuficiente ... ok test_str ... ok test_repr ... ok test_transferir_caso_normal ... ok test_transferir_actualiza_ambos_saldos ... ok test_transferir_saldo_insuficiente ... ok test_transferir_cantidad_invalida ... ok ---------------------------------------------------------------------- Ran 22 tests in 0.002s OK
💡 Pistas:
- No escribas el código antes que la prueba — si lo haces no estás haciendo TDD
- Empieza siempre por el
setUppara no repetir la creación de cuentas en cada prueba - Para
transferirpiensa en qué debe pasar si la transferencia falla a mitad: el saldo de origen no debe cambiar si la cantidad es inválida - El límite de 100.000€ aplica también al destino de una transferencia
Soluciones Comentadas
Solución Ejercicio 1:
import unittest
# Tabla de clases de equivalencia
# ─────────────────────────────────────────────────────────────
# CE válidas:
# CV1: cantidad > 0 y saldo + cantidad <= 100000 → True
# CV2: cantidad == 0 → False
#
# CE inválidas:
# CI1: cantidad < 0 → ValueError
# CI2: saldo_actual < 0 → ValueError
# CI3: saldo + cantidad > 100000 → ValueError
# CI4: cantidad no numérico → TypeError
# CI5: saldo no numérico → TypeError
#
# Valores límite:
# cantidad = 0 → False (frontera CV1/CV2)
# cantidad = 0.01 → True (mínimo válido)
# saldo + cantidad = 100000 → True (límite exacto)
# saldo + cantidad = 100000.01 → ValueError (justo por encima)
# ─────────────────────────────────────────────────────────────
class TestValidarIngreso(unittest.TestCase):
# ── Clases de equivalencia válidas ──────────────────────────────────
def test_ce_cantidad_valida(self):
"""CV1 — cantidad positiva con margen de saldo."""
self.assertTrue(validar_ingreso(100, 500))
def test_ce_cantidad_cero(self):
"""CV2 — cantidad cero devuelve False."""
self.assertFalse(validar_ingreso(0, 500))
def test_ce_saldo_resultante_en_limite(self):
"""CV1 — saldo resultante justo en el límite."""
self.assertTrue(validar_ingreso(500, 99500))
# ── Clases de equivalencia inválidas ────────────────────────────────
def test_ce_valor_negativo_cantidad(self):
"""CI1 — cantidad negativa lanza ValueError."""
with self.assertRaises(ValueError):
validar_ingreso(-50, 500)
def test_ce_valor_negativo_saldo(self):
"""CI2 — saldo_actual negativo lanza ValueError."""
with self.assertRaises(ValueError):
validar_ingreso(100, -500)
def test_ce_saldo_supera_limite(self):
"""CI3 — saldo resultante supera 100.000€."""
with self.assertRaises(ValueError):
validar_ingreso(200, 99900) # 99900 + 200 = 100100 > 100000
def test_ce_tipo_incorrecto_cantidad(self):
"""CI4 — cantidad como cadena lanza TypeError."""
with self.assertRaises(TypeError):
validar_ingreso("cien", 500)
def test_ce_tipo_incorrecto_saldo(self):
"""CI5 — saldo como None lanza TypeError."""
with self.assertRaises(TypeError):
validar_ingreso(100, None)
# ── Valores límite ───────────────────────────────────────────────────
def test_limite_cantidad_minima(self):
"""Mínimo positivo — 0.01€ es válido."""
self.assertTrue(validar_ingreso(0.01, 0))
def test_limite_saldo_exactamente_cero(self):
"""Saldo inicial exactamente 0 es válido."""
self.assertTrue(validar_ingreso(100, 0))
def test_limite_saldo_resultado_exactamente_100000(self):
"""Saldo resultante exactamente 100.000€ — válido."""
self.assertTrue(validar_ingreso(100, 99900))
def test_limite_saldo_resultado_supera_100000(self):
"""Saldo resultante 100.000,01€ — inválido."""
with self.assertRaises(ValueError):
validar_ingreso(0.01, 100000)
if __name__ == "__main__":
unittest.main(verbosity=2)
Solución Ejercicio 2 — Los 6 errores:
# PRUEBA A — ERROR: el método no tiene prefijo test_
# unittest solo ejecuta métodos que empiecen por test_
# Este método nunca se ejecuta — la prueba no existe para unittest
def crear_cuenta(self): # ← MAL
...
# CORRECCIÓN:
def test_crear_cuenta(self): # ← BIEN
cuenta = CuentaBancaria("Sergio", 1000)
self.assertEqual(cuenta.saldo, 1000)
# PRUEBA B — ERROR: la prueba hace demasiadas cosas a la vez
# Si falla no sabes cuál de los tres ingresos causó el problema
# Además el valor esperado es correcto (500+200+300+100=1100) pero
# la prueba debería ser más pequeña y enfocada
def test_ingreso_correcto(self): # ← MEJORABLE
cuenta = CuentaBancaria("Sergio", 500)
cuenta.ingresar(200)
cuenta.ingresar(300)
cuenta.ingresar(100)
self.assertEqual(cuenta.saldo, 1100)
# CORRECCIÓN — una prueba, una responsabilidad:
def test_ingresar_actualiza_saldo(self):
cuenta = CuentaBancaria("Sergio", 500)
cuenta.ingresar(200)
self.assertEqual(cuenta.saldo, 700) # 500 + 200 = 700
# PRUEBA C — ERROR: valor esperado incorrecto
# 500 - 200 = 300, no 200
def test_retirada_correcta(self):
cuenta = CuentaBancaria("Sergio", 500)
cuenta.retirar(200)
self.assertEqual(cuenta.saldo, 200) # ← MAL: debería ser 300
# CORRECCIÓN:
self.assertEqual(cuenta.saldo, 300) # ← BIEN: 500 - 200 = 300
# PRUEBA D — ERROR: assert incorrecto para comprobar una excepción
# assertFalse comprueba si cuenta es falsy — no si lanzó un error
# CuentaBancaria("A", 0) debería lanzar ValueError pero la prueba
# no lo captura — si la clase lanza el error la prueba falla con
# un error inesperado, no con un fallo limpio
def test_titular_invalido(self):
cuenta = CuentaBancaria("A", 0)
self.assertFalse(cuenta) # ← MAL
# CORRECCIÓN:
def test_titular_invalido(self):
with self.assertRaises(ValueError): # ← BIEN
CuentaBancaria("A", 0)
# PRUEBA E — ERROR: la prueba espera un comportamiento incorrecto
# Si retirar() está bien implementado debe lanzar ValueError
# cuando no hay saldo suficiente — no devolver un saldo negativo
# La prueba valida un comportamiento que NO debería existir
def test_saldo_insuficiente(self):
cuenta = CuentaBancaria("Sergio", 100)
cuenta.retirar(200)
self.assertEqual(cuenta.saldo, -100) # ← MAL: valida comportamiento erróneo
# CORRECCIÓN:
def test_saldo_insuficiente(self):
cuenta = CuentaBancaria("Sergio", 100)
with self.assertRaises(ValueError): # ← BIEN: espera el error correcto
cuenta.retirar(200)
# PRUEBA F — ERROR: assert innecesariamente complejo
# assertEqual(a == b, True) es equivalente a assertTrue(a == b)
# pero ambos son peores que assertEqual(a, b) directamente
def test_comparar_saldos(self):
cuenta1 = CuentaBancaria("Sergio", 500)
cuenta2 = CuentaBancaria("María", 500)
self.assertEqual(cuenta1.saldo == cuenta2.saldo, True) # ← MAL
# CORRECCIÓN:
def test_comparar_saldos(self):
cuenta1 = CuentaBancaria("Sergio", 500)
cuenta2 = CuentaBancaria("María", 500)
self.assertEqual(cuenta1.saldo, cuenta2.saldo) # ← BIEN
Solución Ejercicio 3:
# cuenta_bancaria.py
class CuentaBancaria:
LIMITE = 100_000.0
def __init__(self, titular, saldo_inicial=0):
if not isinstance(titular, str) or len(titular) < 2:
raise ValueError("El titular debe tener al menos 2 caracteres.")
if saldo_inicial < 0:
raise ValueError("El saldo inicial no puede ser negativo.")
self.__titular = titular
self.__saldo = float(saldo_inicial)
@property
def titular(self):
return self.__titular
@property
def saldo(self):
return self.__saldo
def ingresar(self, cantidad):
if cantidad <= 0:
raise ValueError("La cantidad a ingresar debe ser positiva.")
if self.__saldo + cantidad > self.LIMITE:
raise ValueError(f"El saldo superaría el límite de {self.LIMITE}€.")
self.__saldo += cantidad
def retirar(self, cantidad):
if cantidad <= 0:
raise ValueError("La cantidad a retirar debe ser positiva.")
if cantidad > self.__saldo:
raise ValueError("Saldo insuficiente.")
self.__saldo -= cantidad
def transferir(self, cantidad, cuenta_destino):
if cantidad <= 0:
raise ValueError("La cantidad a transferir debe ser positiva.")
if cantidad > self.__saldo:
raise ValueError("Saldo insuficiente para la transferencia.")
if cuenta_destino.saldo + cantidad > self.LIMITE:
raise ValueError("La transferencia superaría el límite de la cuenta destino.")
self.__saldo -= cantidad
cuenta_destino.__saldo += cantidad # acceso al atributo privado de la misma clase
def __str__(self):
return f"Cuenta de {self.__titular}: {self.__saldo}€"
def __repr__(self):
return f"CuentaBancaria('{self.__titular}', {self.__saldo})"
# test_cuenta_bancaria.py
import unittest
from cuenta_bancaria import CuentaBancaria
class TestCuentaBancaria(unittest.TestCase):
def setUp(self):
"""Cuentas reutilizables para varios tests."""
self.cuenta = CuentaBancaria("Sergio", 1000)
self.cuenta2 = CuentaBancaria("María", 500)
# ── Constructor ──────────────────────────────────────────────────────
def test_constructor_titular_y_saldo(self):
self.assertEqual(self.cuenta.titular, "Sergio")
self.assertEqual(self.cuenta.saldo, 1000)
def test_constructor_saldo_por_defecto(self):
cuenta = CuentaBancaria("Ana")
self.assertEqual(cuenta.saldo, 0)
# ── Validación titular ───────────────────────────────────────────────
def test_titular_minimo_dos_caracteres(self):
with self.assertRaises(ValueError):
CuentaBancaria("A", 0)
def test_titular_exactamente_dos_caracteres(self):
cuenta = CuentaBancaria("Jo", 0)
self.assertEqual(cuenta.titular, "Jo")
# ── Validación saldo inicial ─────────────────────────────────────────
def test_saldo_inicial_negativo(self):
with self.assertRaises(ValueError):
CuentaBancaria("Sergio", -100)
# ── Ingresar ─────────────────────────────────────────────────────────
def test_ingresar_cantidad_valida(self):
self.cuenta.ingresar(500)
self.assertEqual(self.cuenta.saldo, 1500)
def test_ingresar_actualiza_saldo(self):
saldo_antes = self.cuenta.saldo
self.cuenta.ingresar(200)
self.assertEqual(self.cuenta.saldo, saldo_antes + 200)
def test_ingresar_cantidad_cero(self):
with self.assertRaises(ValueError):
self.cuenta.ingresar(0)
def test_ingresar_cantidad_negativa(self):
with self.assertRaises(ValueError):
self.cuenta.ingresar(-100)
def test_ingresar_supera_limite(self):
with self.assertRaises(ValueError):
self.cuenta.ingresar(99_001) # 1000 + 99001 > 100000
def test_ingresar_exactamente_limite(self):
cuenta = CuentaBancaria("Test", 0)
cuenta.ingresar(100_000)
self.assertEqual(cuenta.saldo, 100_000)
# ── Retirar ──────────────────────────────────────────────────────────
def test_retirar_cantidad_valida(self):
self.cuenta.retirar(300)
self.assertEqual(self.cuenta.saldo, 700)
def test_retirar_actualiza_saldo(self):
saldo_antes = self.cuenta.saldo
self.cuenta.retirar(200)
self.assertEqual(self.cuenta.saldo, saldo_antes - 200)
def test_retirar_cantidad_cero(self):
with self.assertRaises(ValueError):
self.cuenta.retirar(0)
def test_retirar_cantidad_negativa(self):
with self.assertRaises(ValueError):
self.cuenta.retirar(-50)
def test_retirar_saldo_insuficiente(self):
with self.assertRaises(ValueError):
self.cuenta.retirar(5000)
# ── Representación ───────────────────────────────────────────────────
def test_str(self):
self.assertEqual(str(self.cuenta), "Cuenta de Sergio: 1000.0€")
def test_repr(self):
self.assertEqual(repr(self.cuenta), "CuentaBancaria('Sergio', 1000.0)")
# ── Transferir ───────────────────────────────────────────────────────
def test_transferir_caso_normal(self):
self.cuenta.transferir(300, self.cuenta2)
self.assertEqual(self.cuenta.saldo, 700)
def test_transferir_actualiza_ambos_saldos(self):
self.cuenta.transferir(300, self.cuenta2)
self.assertEqual(self.cuenta2.saldo, 800) # 500 + 300
def test_transferir_saldo_insuficiente(self):
with self.assertRaises(ValueError):
self.cuenta.transferir(5000, self.cuenta2)
def test_transferir_cantidad_invalida(self):
with self.assertRaises(ValueError):
self.cuenta.transferir(0, self.cuenta2)
if __name__ == "__main__":
unittest.main(verbosity=2)
Chuletario — Pruebas en Python
# ============================================
# CHULETARIO — Pruebas en Python
# Sergio Learns · sergiolearns.com
# ============================================
# QUÉ ES UNA PRUEBA
# Entrada conocida + resultado esperado + comparación
# Objetivo: ENCONTRAR errores, no confirmar que funciona
# Una prueba que pasa no garantiza ausencia de errores —
# solo garantiza ausencia de errores para ese caso concreto
# ── CAJA NEGRA ───────────────────────────────────────────────
# CLASES DE EQUIVALENCIA
# Dividir entradas en grupos con el mismo comportamiento
# Probar un valor representativo por grupo
# Tipos:
# Válidas → entrada correcta → resultado esperado
# Inválidas → entrada incorrecta → error esperado
# VALORES LÍMITE
# Probar los extremos de cada clase, no solo el centro
# Para cada frontera: mínimo, mínimo+1, máximo-1, máximo
# Ejemplo para rango [8, 15]:
# Probar: 7, 8, 9 (frontera inferior) y 14, 15, 16 (frontera superior)
# PROCESO CAJA NEGRA
# 1. Leer solo la especificación (no el código)
# 2. Identificar clases válidas e inválidas
# 3. Elegir un valor representativo por clase
# 4. Añadir valores límite en cada frontera
# 5. Escribir una prueba por caso
# ── CAJA BLANCA ──────────────────────────────────────────────
# GRAFO DE FLUJO
# Nodo → bloque de instrucciones consecutivas
# Nodo predicado → condición (if, while, for) — dos salidas
# Arco → flecha entre nodos
# COMPLEJIDAD CICLOMÁTICA
# = número de nodos predicado + 1
# = número mínimo de pruebas para cubrir todos los arcos
# PROCESO CAJA BLANCA
# 1. Identificar nodos predicado (if, while, for)
# 2. Dibujar el grafo de flujo
# 3. Calcular: complejidad = predicados + 1
# 4. Encontrar caminos que cubran todos los arcos
# 5. Diseñar un caso de prueba por camino
# ── TDD ──────────────────────────────────────────────────────
# CICLO TDD
# 1. ROJO → escribe la prueba ANTES del código (falla)
# 2. VERDE → escribe el mínimo código para que pase
# 3. REFACTOR → mejora sin romper las pruebas
# 4. Repite
# ORDEN TDD PARA UNA CLASE
# 1. Constructor básico
# 2. Validaciones del constructor
# 3. Propiedades (getters)
# 4. Métodos simples
# 5. Métodos que dependen de otros
# 6. __str__, __repr__, __eq__
# ── UNITTEST ─────────────────────────────────────────────────
import unittest
class TestMiClase(unittest.TestCase):
def setUp(self):
"""Se ejecuta ANTES de cada prueba."""
self.objeto = MiClase(...) # preparar el terreno
def tearDown(self):
"""Se ejecuta DESPUÉS de cada prueba."""
pass # limpiar recursos (ficheros, conexiones...)
def test_caso_normal(self):
"""Descripción clara de qué prueba este método."""
resultado = self.objeto.metodo(entrada)
self.assertEqual(resultado, esperado)
def test_caso_limite(self):
with self.assertRaises(ValueError):
self.objeto.metodo(entrada_invalida)
if __name__ == "__main__":
unittest.main(verbosity=2) # verbosity=2 → muestra nombre de cada prueba
# MÉTODOS ASSERT ESENCIALES
self.assertEqual(a, b) # a == b
self.assertNotEqual(a, b) # a != b
self.assertTrue(x) # bool(x) es True
self.assertFalse(x) # bool(x) es False
self.assertIsNone(x) # x is None
self.assertIsNotNone(x) # x is not None
self.assertIn(a, b) # a in b
self.assertNotIn(a, b) # a not in b
self.assertRaises(Error, f, *args) # f(*args) lanza Error
self.assertAlmostEqual(a, b) # para flotantes (evita imprecisión)
self.assertAlmostEqual(a, b, places=2) # hasta 2 decimales
# USO DE assertRaises — dos formas
# Forma 1 — como gestor de contexto (recomendada)
with self.assertRaises(ValueError):
funcion(dato_invalido)
# Forma 2 — como llamada directa
self.assertRaises(ValueError, funcion, dato_invalido)
# INFORME DE RESULTADOS
# . → prueba pasada
# F → fallo (assert incorrecto)
# E → error inesperado (excepción no capturada)
# S → prueba saltada (skip)
# ERRORES TÍPICOS
# 1. Método sin prefijo test_ → no se ejecuta
def mi_prueba(self): ... # MAL — no se ejecuta
def test_mi_prueba(self): ... # BIEN
# 2. Comparar flotantes con assertEqual
self.assertEqual(0.1 + 0.2, 0.3) # FALLA por imprecisión
self.assertAlmostEqual(0.1 + 0.2, 0.3) # BIEN
# 3. Assert incorrecto para excepciones
resultado = funcion(malo)
self.assertFalse(resultado) # MAL — puede no capturar el error
with self.assertRaises(ValueError): # BIEN
funcion(malo)
# 4. Valor esperado incorrecto
cuenta = CuentaBancaria("Sergio", 500)
cuenta.retirar(200)
self.assertEqual(cuenta.saldo, 200) # MAL — 500-200=300, no 200
# 5. Prueba que hace demasiadas cosas
def test_todo(self): # MAL — si falla no sabes dónde
objeto.metodo1()
objeto.metodo2()
objeto.metodo3()
self.assertEqual(objeto.estado, ...)
def test_metodo1_actualiza_estado(self): # BIEN — una cosa por prueba
objeto.metodo1()
self.assertEqual(objeto.estado, ...)
# 6. Solo probar el camino feliz
def test_ingreso(self): # MAL — falta probar errores
self.assertTrue(ingresar(100))
# BIEN — probar también casos inválidos:
def test_ingreso_cantidad_negativa(self):
with self.assertRaises(ValueError): ingresar(-100)
def test_ingreso_supera_limite(self):
with self.assertRaises(ValueError): ingresar(999999)
Sigue aprendiendoTodos los artículos de Python¿Te ha servido este artículo? Así puedes apoyar SergioLearns →

2 comentarios