A4.B Errores y casos límite¶
Probar que algo falla¶
Un programa correcto no solo da el resultado bueno con datos buenos: también falla de la forma prevista con datos malos. Eso también se prueba. En xUnit, Assert.Throws<T> comprueba que un trozo de código lanza exactamente esa excepción (una subclase no vale; para aceptarla, Assert.ThrowsAny<T>), y devuelve la excepción por si quieres mirar su mensaje:
var error = Assert.Throws<ArgumentOutOfRangeException>(() => Edades.Categoria(-1));
Assert.Contains("negativa", error.Message);
Fíjate en que el código que debe fallar va dentro de una lambda: si lo escribieras suelto, la excepción saltaría antes de que Assert.Throws pudiera atraparla.
Casos límite¶
Los errores se esconden en los bordes: el primer y último valor de cada tramo, el cero, el vacío, el negativo, el máximo. Una función que clasifica edades en tramos (niño hasta 12, adolescente hasta 17...) no se prueba con una edad por tramo, sino con los valores de cada frontera: 12 y 13, 17 y 18, 64 y 65. Un < en vez de un <= solo se descubre ahí.
| Tipo de dato | Casos límite habituales |
|---|---|
| Números | 0, 1, -1, el mínimo y el máximo permitidos, uno más allá |
| Texto | Vacío, un solo carácter, con espacios, con mayúsculas, con tildes |
| Colecciones | Vacía, un elemento, muchos, con repetidos |
| Intervalos | Los dos extremos y los valores justo fuera |
Varios casos con una tabla¶
Repetir un método de prueba para cada valor es tedioso. En xUnit, [Theory] convierte un método con parámetros en una prueba que se ejecuta una vez por cada [InlineData(...)], y el informe muestra cada caso por separado. Si un solo caso falla, sabes exactamente cuál.
El ejemplo¶
Edades.cs:
public static class Edades
{
public static string Categoria(int edad)
{
if (edad < 0)
{
throw new ArgumentOutOfRangeException(nameof(edad), "la edad no puede ser negativa");
}
if (edad <= 12) return "niño";
if (edad <= 17) return "adolescente";
if (edad <= 64) return "adulto";
return "mayor";
}
}
EdadesTests.cs:
using Xunit;
public class EdadesTests
{
// Probar que algo FALLA: el error esperado es parte del comportamiento
[Fact]
public void UnaEdadNegativaLanzaError()
{
var error = Assert.Throws<ArgumentOutOfRangeException>(() => Edades.Categoria(-1));
Assert.Contains("negativa", error.Message);
}
// Varios casos con una tabla: [Theory] repite el método con cada [InlineData]
[Theory]
[InlineData(0, "niño")] // límite inferior
[InlineData(12, "niño")] // último de niño
[InlineData(13, "adolescente")] // primero de adolescente
[InlineData(17, "adolescente")]
[InlineData(18, "adulto")]
[InlineData(64, "adulto")]
[InlineData(65, "mayor")]
[InlineData(120, "mayor")]
public void LaCategoriaEnLosLimites(int edad, string esperada)
{
Assert.Equal(esperada, Edades.Categoria(edad));
}
}
Resultado de dotnet test:
Con error: 0, Superado: 9, Omitido: 0, Total: 9
El resultado cuenta 9 pruebas: la del error y una por cada fila de la tabla. (Las filas se leen como casos independientes; xUnit ejecuta la [Theory] una vez por cada una.) Para tablas más grandes o datos calculados existen [MemberData] y [ClassData].
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Probar solo el «camino feliz» | Añadir los errores y los límites |
Poner el código que falla fuera de la lambda de Assert.Throws |
Siempre dentro: () => ... |
| Aceptar cualquier excepción | Comprueba el tipo concreto y, si importa, el mensaje |
| Probar un valor por tramo y no las fronteras | Probar justo a cada lado de cada límite |
| Copiar y pegar la misma prueba con otros números | [Theory] con [InlineData] |
Para practicar¶
Los ejercicios A4.5 y A4.6 usan estas ideas. Para ver la misma idea escrita en los otros lenguajes: Pasar de uno a otro.