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.

expect(() => f(), throwsArgumentError) comprueba que el código lanza ese error: se pasa una función (con () =>) para que expect la ejecute y capture el error. Para mirar el mensaje, throwsA(isA<ArgumentError>().having((e) => e.message, '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 mapa constante y un forEach dentro de una prueba; el parámetro reason: 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.

carrito_casos_test.dart

import 'package:carrito_app/carrito.dart';
import 'package:test/test.dart';

void main() {
  late Carrito carrito;

  setUp(() {
    carrito = Carrito();
  });

  test('una cantidad cero o negativa lanza un error con un mensaje claro', () {
    expect(
      () => carrito.agregar('tarta', 1800, 0),
      throwsA(isA<ArgumentError>().having((e) => e.message, 'mensaje', 'la cantidad debe ser mayor que cero')),
    );
    expect(() => carrito.agregar('tarta', 1800, -1), throwsArgumentError);
  });

  test('un precio negativo lanza un error', () {
    expect(() => carrito.agregar('tarta', -5, 1), throwsArgumentError);
  });

  test('descuentos de 0, 10, 50 y 100 por ciento (casos límite incluidos)', () {
    carrito.agregar('tarta', 1800, 2);
    carrito.agregar('galleta', 100, 12);
    const casos = {0: 4800, 10: 4320, 50: 2400, 100: 0}; // porcentaje -> total esperado
    casos.forEach((porcentaje, esperado) {
      expect(carrito.conDescuento(porcentaje), esperado, reason: 'con $porcentaje %');
    });
  });

  test('un porcentaje fuera de 0 a 100 lanza un error', () {
    expect(() => carrito.conDescuento(101), throwsArgumentError);
    expect(() => carrito.conDescuento(-1), throwsArgumentError);
  });
}

Al ejecutar las pruebas:

+0: una cantidad cero o negativa lanza un error con un mensaje claro
+1: un precio negativo lanza un error
+2: descuentos de 0, 10, 50 y 100 por ciento (casos límite incluidos)
+3: un porcentaje fuera de 0 a 100 lanza un error
+4: All tests passed!

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.