Saltar al pie de página
Iron Academy Logo
Aprender C#
Aprender C#

Otras categorías

Aserciones Fluent en Pruebas Unitarias en C#

[[academy-video-youtube({"vid": "TytferBCLOo", "start_time": "0", "title": "Fluent Assertions in Unit Testing in C#", "creator": "Tim Corey", "length": "10m 6s"})]]

Las pruebas unitarias se escriben una vez y se leen muchas veces, lo que hace que la línea de afirmación sea uno de los detalles más importantes en un conjunto de pruebas. Assert.Equal(expected, actual) funciona, pero un mensaje de error que solo compara dos cadenas no siempre explica qué estaba verificando la prueba. Fluent Assertions remodela la sintaxis de la afirmación para que la línea se lea como la regla que impone, y el mensaje de fallo se convierta en una oración en lugar de una diferencia.

En su video "Fluent Assertions in Unit Testing in C#", Tim Corey toma un proyecto xUnit que ya prueba un SampleClass camino feliz, luego introduce un caso límite (Eddie Van Halen) que rompe la lógica simple de dividir por espacios. Instala el paquete Fluent Assertions, reescribe la afirmación usando la cadena .Should().Be(), demuestra cómo encadenar múltiples condiciones en un solo valor y termina con AssertionScope para que una sola prueba informe de todas las condiciones fallidas a la vez en lugar de salir en la primera. Los equipos cuyo conjunto de pruebas ha crecido más allá del punto donde las diferencias de igualdad simples se leen claramente encontrarán aquí el camino de actualización.

El punto de partida de xUnit

[0:35 - 2:46] El proyecto en pantalla es pequeño: una biblioteca de clases con un solo SampleClass que toma un nombre completo en su constructor y lo divide en el espacio para FirstName y LastName, además de un proyecto de prueba xUnit que prueba el camino feliz. Las pruebas utilizan Theory con atributos InlineData para ejecutar la misma lógica contra dos entradas ("Tim Corey" y "Sue Storm"), y cada afirmación es la forma estándar Assert.Equal(expected, actual).

[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}
[Theory]
[InlineData("Tim Corey", "Tim")]
[InlineData("Sue Storm", "Sue")]
public void TestFirstNameProperty(string fullName, string expected)
{
    var sample = new SampleClass(fullName);
    Assert.Equal(expected, sample.FirstName);
}

Ejecutar el conjunto da seis pruebas aprobadas. Nada de esto está mal, y para los casos que se ajustan a "primera espacio último", las afirmaciones se leen con suficiente claridad. El territorio interesante comienza cuando un nombre no se ajusta a esa forma.

Donde deja de funcionar el camino feliz

[2:46 - 4:32] El caso límite que Tim elige es "Eddie Van Halen", un nombre de tres palabras donde el apellido es "Van Halen", no solo el token después del primer espacio. La implementación actual toma el índice 1 de la división y lo llama el apellido, por lo que la clase devuelve "Van" como el apellido y descarta "Halen" por completo. El error es real, y escribir una prueba fallida para él es el primer paso hacia su solución.

Escribir esa prueba con Assert.Equal("Van Halen", sample.LastName) funciona, pero el mensaje de error se lee como una comparación de cadenas sin contexto. Cambiar a Fluent Assertions hace que la misma prueba exprese la regla en palabras y produzca un mensaje de fallo que nombre la propiedad bajo prueba. Para un conjunto pequeño la diferencia parece cosmética; para un conjunto de unas pocas cientos de afirmaciones, la diferencia de legibilidad se acumula.

Installing Fluent Assertions

[4:32 - 5:10] El paquete está en NuGet bajo FluentAssertions. Tim lo señala como uno de los paquetes más descargados en el registro, lo cual es un contexto útil: es estadísticamente probable que cualquier nuevo contribuyente lo haya visto antes. El proyecto de prueba obtiene dos directivas using en la parte superior:

using FluentAssertions;
using FluentAssertions.Execution;
using FluentAssertions;
using FluentAssertions.Execution;

La primera trae los métodos de extensión de afirmación, que es lo que la mayoría de las pruebas necesita. El segundo existe para el tipo AssertionScope cubierto más adelante en el video; las pruebas que no agrupan afirmaciones pueden dejarlo fuera.

La sintaxis Should.Be

[5:10 - 6:14] La reescritura mínima de Fluent Assertions de la prueba Van Halen:

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");
    sample.LastName.Should().Be("Van Halen");
}

La cadena se lee cercana al inglés hablado. La extensión .Should() devuelve un objeto de afirmación cuyos métodos describen la comparación; .Be(...) realiza una verificación de igualdad. La sensibilidad a mayúsculas y minúsculas, los espacios en blanco iniciales y finales son parte de esa comprobación, que coincide con el comportamiento de la mayoría de las comparaciones de cadenas en código de producción.

Ejecutar esta prueba contra la implementación defectuosa produce un mensaje de error que nombra la propiedad y explica la diferencia: "Se esperaba que sample.LastName fuera 'Van Halen' con una longitud de 9, pero 'Van' tiene una longitud de 3." El mensaje identifica el valor bajo prueba mediante la expresión que lo produjo, que es el tipo de contexto que la forma Assert.Equal no proporciona.

Encadenamiento de múltiples condiciones

[6:14 - 8:10] Una sola propiedad a menudo necesita satisfacer más de una regla. Fluent Assertions compone condiciones con .And para que la cadena se mantenga en una única declaración:

sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");
sample.LastName.Should()
    .StartWith("Van")
    .And.EndWith("len")
    .And.Contain(" ");

Cada eslabón en la cadena es una condición separada. Por defecto, la cadena deja de informar en la primera falla: si StartWith("Van") pasa pero EndWith("len") falla, el mensaje de error nombrará EndWith y la afirmación se detiene allí. Para las pruebas donde cada condición es independiente y quieres que todos los fallos emerjan, el alcance de la afirmación (siguiente sección) cambia ese comportamiento.

La sintaxis de encadenamiento se inclina hacia expresar intención sobre enumerar igualdad. Una prueba que dice "el apellido debería empezar con Van, terminar con len y contener un espacio" se lee como una especificación. La misma lógica escrita con tres llamadas Assert.True separadas se leería como tres comprobaciones booleanas sin una narrativa que las conecte.

Reportando todos los fallos con alcances de afirmación

[8:10 - 9:34] El comportamiento por defecto de detenerse en el primer fallo está bien para cadenas donde las condiciones posteriores dependen de las anteriores. Para cadenas donde cada condición importa independientemente, envolver las afirmaciones en un AssertionScope hace que la prueba informe de cada fallo en una sola ejecución:

[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}
[Fact]
public void TestEdgeCaseNames()
{
    var sample = new SampleClass("Eddie Van Halen");

    using var _ = new AssertionScope();
    sample.LastName.Should().StartWith("Van");
    sample.LastName.Should().EndWith("len");
    sample.LastName.Should().Contain(" ");
}

La línea using-var-discard es el patrón estándar: el alcance dura hasta que la variable sale del alcance al final del método, y el subrayado de descarte indica que la variable en sí nunca se lee. Cuando la prueba falla, el mensaje contiene cada condición fallida como una línea separada, lo que significa que una ejecución da toda la historia en lugar de forzar tres ejecuciones para descubrir tres problemas. Para pruebas que verifican la forma de un objeto devuelto a través de muchas propiedades, esta es la diferencia entre solucionar un error por ciclo de prueba y solucionar todos a la vez.

Conclusión: Pruebas que parecen especificaciones

[9:34 - 10:06] Fluent Assertions no cambia lo que las pruebas verifican; cambia cómo se lee la verificación. La cadena .Should(), la composición .And, y el agrupamiento AssertionScope acercan el código de prueba a una especificación escrita del comportamiento bajo prueba. Combinado con el mismo tipo de estilo fluido utilizado por FluentValidation para validación de entradas, el patrón produce conjuntos de pruebas y reglas de validación que los recién llegados pueden leer sin necesidad de una guía.

Conclusión

[9:34 - 10:06] Agregar Fluent Assertions a un proyecto de pruebas requiere una instalación de NuGet, dos directivas using y una reescritura de la línea de afirmación de Assert.Equal(expected, actual) a actual.Should().Be(expected). Las cadenas expresan reglas de múltiples condiciones en una sola declaración, y AssertionScope hace que una sola prueba informe cada falla en lugar de detenerse en la primera. El beneficio son mensajes de error que explican la regla que se rompió, que es la diferencia entre una traza de pila y una frase.

Ejemplo de consejo: Al afirmar sobre colecciones, prefiera result.Should().BeEquivalentTo(expected) sobre la comparación propiedad por propiedad. Recorre el gráfico del objeto por usted e informa la primera propiedad divergente por ruta (por ejemplo, users[2].Address.City), lo cual es mucho más útil que un mensaje de "las colecciones no son iguales" cuando una lista de 50 elementos no está de acuerdo en un campo anidado.

Mira el video completo en su canal de YouTube y obtén más información sobre cómo escribir pruebas de C# legibles y mantenibles en la serie de Entrenamiento de 10 Minutos.

Hero Worlddot related to Aserciones Fluent en Pruebas Unitarias en C#
Hero Affiliate related to Aserciones Fluent en Pruebas Unitarias en C#

Gana más compartiendo lo que te gusta

¿Creas contenidos para desarrolladores que trabajan con .NET, C#, Java, Python o Node.js? ¡Convierte tu experiencia en un ingreso extra!

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame