Saltar a contenido

A4.B Errores y casos límite

El código casi nunca falla con los datos «normales»: falla en los bordes (el primero, el último, el cero, el vacío) y con los datos inválidos. Esas son las pruebas que más valen.

Probar que algo falla

También hay que comprobar que el código rechaza lo incorrecto, con el error y el mensaje adecuados.

with self.assertRaises(ValueError) as contexto: comprueba que el bloque lanza esa excepción; después, str(contexto.exception) es su mensaje.

Casos límite

Un caso límite es un valor en la frontera entre dos comportamientos. Si un descuento va de 0 a 100, prueba 0 y 100 (los extremos válidos) y -1 y 101 (justo fuera). Los errores de «uno de más o de menos» (< en vez de <=) viven ahí.

Qué pruebas Valores típicos
Números con un rango El mínimo, el máximo, justo debajo y justo encima
Colecciones y texto Vacío, un solo elemento, muchos
Búsquedas El primero, el último, uno que no está
Divisiones y porcentajes Cero, uno, el límite

Varios casos con una tabla

Cuando la comprobación es la misma y solo cambian los datos, se escribe una prueba que recorre una tabla de casos (entrada y resultado esperado):

Un diccionario y un bucle dentro de una prueba; self.subTest(...) hace que, si un caso falla, se vea cuál y se sigan probando los demás.

El ejemplo

Las pruebas del carrito: dos para los errores, una con la tabla de descuentos y otra para el porcentaje fuera de rango.

test_carrito_casos.py

import unittest

from carrito import Carrito


class PruebasCarritoCasos(unittest.TestCase):
    def setUp(self):
        self.carrito = Carrito()

    def test_una_cantidad_cero_o_negativa_lanza_un_error_con_un_mensaje_claro(self):
        # assertRaises como «with» comprueba que se lanza la excepción y deja mirar su mensaje
        with self.assertRaises(ValueError) as contexto:
            self.carrito.agregar("tarta", 1800, 0)
        self.assertEqual(str(contexto.exception), "la cantidad debe ser mayor que cero")
        with self.assertRaises(ValueError):
            self.carrito.agregar("tarta", 1800, -1)

    def test_un_precio_negativo_lanza_un_error(self):
        with self.assertRaises(ValueError):
            self.carrito.agregar("tarta", -5, 1)

    def test_descuentos_de_0_10_50_y_100_por_ciento_casos_limite_incluidos(self):
        self.carrito.agregar("tarta", 1800, 2)
        self.carrito.agregar("galleta", 100, 12)
        casos = {0: 4800, 10: 4320, 50: 2400, 100: 0}  # porcentaje -> total esperado
        for porcentaje, esperado in casos.items():
            # subTest hace que, si un caso falla, se vea cuál y se sigan probando los demás
            with self.subTest(porcentaje=porcentaje):
                self.assertEqual(self.carrito.con_descuento(porcentaje), esperado)

    def test_un_porcentaje_fuera_de_0_a_100_lanza_un_error(self):
        with self.assertRaises(ValueError):
            self.carrito.con_descuento(101)
        with self.assertRaises(ValueError):
            self.carrito.con_descuento(-1)


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

Al ejecutar las pruebas:

test_descuentos_de_0_10_50_y_100_por_ciento_casos_limite_incluidos (test_carrito_casos.PruebasCarritoCasos.test_descuentos_de_0_10_50_y_100_por_ciento_casos_limite_incluidos) ... ok
test_un_porcentaje_fuera_de_0_a_100_lanza_un_error (test_carrito_casos.PruebasCarritoCasos.test_un_porcentaje_fuera_de_0_a_100_lanza_un_error) ... ok
test_un_precio_negativo_lanza_un_error (test_carrito_casos.PruebasCarritoCasos.test_un_precio_negativo_lanza_un_error) ... ok
test_una_cantidad_cero_o_negativa_lanza_un_error_con_un_mensaje_claro (test_carrito_casos.PruebasCarritoCasos.test_una_cantidad_cero_o_negativa_lanza_un_error_con_un_mensaje_claro) ... ok
----------------------------------------------------------------------
Ran 4 tests
OK

Los descuentos esperados se escribieron a mano (4800 → 4320 con un 10 %): si copiaras la fórmula del código en la prueba, repetirías también sus errores.

Cobertura no es calidad

Un programa de pruebas puede ejecutar todas las líneas del código y aun así no comprobar casi nada. Mide cuántos casos importantes tienes cubiertos (bordes, errores, datos raros), no solo cuántas líneas pasan por las pruebas.

Errores frecuentes

Error Cómo evitarlo
Probar solo el caso feliz Añade siempre al menos un borde y un error
Comprobar que «lanza algún error» sin mirar cuál Comprueba el tipo y, si importa, el mensaje
Un bucle de casos donde el primer fallo oculta a los demás Usa el mecanismo del marco para indicar el caso (reason, subTest...)
Escribir las pruebas copiando la implementación Calcula el resultado esperado a mano o con otra vía

Para practicar

Los ejercicios A4.2, A4.3 y A4.5 piden pruebas de casos normales, de errores y de límites con una tabla. Para ver la misma idea en los otros lenguajes: Pasar de uno a otro.