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.

assertFailsWith<IllegalArgumentException> { ... } comprueba que el bloque lanza esa excepción y la devuelve, para mirar su message.

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 mapOf y un for dentro de una prueba; el último parámetro de assertEquals es el mensaje que dice qué caso falló.

El ejemplo

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

CarritoCasosTest.kt

import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class CarritoCasosTest {
    private lateinit var carrito: Carrito

    @BeforeTest
    fun preparar() {
        carrito = Carrito()
    }

    @Test
    fun `una cantidad cero o negativa lanza un error con un mensaje claro`() {
        // assertFailsWith comprueba que se lanza la excepción y la devuelve para mirar su mensaje
        val error = assertFailsWith<IllegalArgumentException> { carrito.agregar("tarta", 1800, 0) }
        assertEquals("la cantidad debe ser mayor que cero", error.message)
        assertFailsWith<IllegalArgumentException> { carrito.agregar("tarta", 1800, -1) }
    }

    @Test
    fun `un precio negativo lanza un error`() {
        assertFailsWith<IllegalArgumentException> { carrito.agregar("tarta", -5, 1) }
    }

    @Test
    fun `descuentos de 0, 10, 50 y 100 por ciento (casos límite incluidos)`() {
        carrito.agregar("tarta", 1800, 2)
        carrito.agregar("galleta", 100, 12)
        val casos = mapOf(0 to 4800, 10 to 4320, 50 to 2400, 100 to 0) // porcentaje -> total esperado
        for ((porcentaje, esperado) in casos) {
            assertEquals(esperado, carrito.conDescuento(porcentaje), "con $porcentaje %")
        }
    }

    @Test
    fun `un porcentaje fuera de 0 a 100 lanza un error`() {
        assertFailsWith<IllegalArgumentException> { carrito.conDescuento(101) }
        assertFailsWith<IllegalArgumentException> { carrito.conDescuento(-1) }
    }
}

Al ejecutar las pruebas:

....
OK (4 tests)

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.