Saltar al contenido

Blog técnico | TDD basado en la propiedad

Las pruebas basadas en la propiedad son un tipo de prueba que utiliza insumos generados aleatoriamente para probar un atributo o una característica del sujeto sometido a prueba. Se puede contrastar con el enfoque más tradicional de las pruebas basadas en ejemplos, en el que se proporcionan casos de prueba específicos para el sujeto sometido a prueba. Normalmente, la TDD se realiza utilizando pruebas basadas en ejemplos. ¿Qué sucede cuando utilizamos pruebas basadas en propiedades en un flujo de trabajo TDD?

Las herramientas que voy a usar son .Net Core, C#, FsCheck y la extensión FsCheck.XUnit.

Blog técnico | TDD basado en la propiedad
Blog técnico | TDD basado en la propiedad

Voy a usar FizzBuzz como nuestro problema de ejemplo. Si no estás familiarizado, comprueba eso aquí.

Si quieres seguir 1. Crear un nuevo proyecto en .Net Core:

dotnet nueva xunit -o PropertyBasedTestingcd PropertyBasedTesting/dotnet agregar paquete FsCheckdotnet agregar paquete FsCheck.Xunit
  1. Añade unas cuantas declaraciones de uso a tu archivo de prueba:usingSystem;usingFsCheck;usingFsCheck.Xunit;

Nuestra primera prueba

Para nuestra primera prueba, asegurémonos de que cualquier cosa divisible por 3 y no divisible por 5 devuelva «Fizz»:

[Propiedad]publicPropertyanything_divisible_by_three_but_not_five_returns_fizz(intinput){Func<bool>property=()=>Fizz.Buzz(input)=="Fizz";returnproperty.When(input%3==0&&input%5!=0);}

Podemos pasar la primera prueba con:

clase públicaFizz{publicstaticstringBuzz(intinput)={; "Fizz";}

Usando las capacidades de fuzzing que están incorporadas en FsCheck, podemos generar entradas aleatorias para nuestra función. FsCheck también proporciona propiedades condicionales – esto nos permite restringir nuestra entrada a ser sólo enteros que son divisibles por tres, pero no divisibles por cinco.

Nuestra segunda prueba

Nuestra segunda prueba asegurará que cualquier número divisible por 5 y no divisible por 3 devolverá «Buzz»:

[Propiedad]publicPropertyanything_divisible_by_five_but_not_three_returns_buzz(intinput){Func<bool>property=()=>Fizz.Buzz(input)=="Buzz";returnproperty.When(input%5==0&&input%3!=0);}

Podemos pasar esta prueba con:

clase públicaFizz{publicstaticstringBuzz(intinput)==0? "Buzz": "Fizz";}

Nuestra tercera prueba

A continuación, probemos para asegurarnos de que devolvemos «FizzBuzz» cuando nuestra entrada sea divisible entre 3 y 5:

[Propiedad]publicPropertyanything_divisible_by_three_and_five_returns_fizzbuzz(intinput){Func<bool>property=()=>Fizz.Buzz(input)=="FizzBuzz";returnproperty.When(input%3==0&&input%5==0);}

Intenté pasar esta prueba con la implementación de abajo, pero me encontré con un error. La implementación:

clase públicaFizz{publicstaticstringBuzz(intinput){varoutput="";if(input%3==0)output+="Fizz";if(input%5==0)output+="Buzz";returnoutput;}}

FsCheck intentó generar entradas aleatorias para mí que satisficieran la condición When(input % 3 == 0 && input == 5), pero se agotó antes de que pudiera llegar a suficientes casos de prueba. Después de buscar en Google, resulta que este es el comportamiento esperado en FsCheck, sólo intentará generar un número limitado de datos de prueba antes de rendirse. Podemos evitar esto escribiendo un generador personalizado para nuestros datos de prueba.

Implementación de un generador personalizado

Un generador personalizado nos permite aplicar condicionales a los datos que se generan para la fuzzing. La implementación que se me ocurrió se parece a esto:

publicstaticclassFizzBuzzGenerator{publicstaticArbitrary<int;Generar(){retornoArb.Default.Int32().Filter(x={;x%3==0&&x%5==0);}}

Podemos instruir a nuestra prueba de fallas para usar el nuevo generador a través del atributo de Arbitraje:

[Propiedad(Arbitraje = nuevo[]{tipo de(FizzBuzzGenerator)})]publicPropiedadcualquiercosa_divisible_por_tres_y_cinco_retornos_fizzbuzz(intinput){varactual=Fizz.Buzz(input);return(actual=="FizzBuzz").APropiedad();}

Nuestra prueba final

Ahora estamos en la recta final. Lo último que necesitamos hacer es escribir una prueba para confirmar que cuando la entrada no es divisible por tres o cinco, devolvemos la entrada. Nuestra prueba final:

[Propiedad]publicProperty cualquier cosa que no sea divisible por tres y cinco debe devolver el valor como cadena (intinput){Func<bool>property=()=>Fizz.Buzz(input)==input.ToString();returnproperty.When(input%3!=0&&input%5!=0);}

Esta prueba está fallando actualmente. Tengo una idea de por qué está fallando, pero es un poco difícil saber en qué entrada está fallando. Podemos actualizar la prueba para «etiquetar» los fallos de modo que podamos ver la entrada exacta que está causando que la prueba falle. Para añadir el etiquetado, hacemos lo siguiente:

[Propiedad]públicoPropiedad cualquier cosa que no sea divisible por tres y cinco debe devolver el valor como cuerda (entrada) {Func<bool ];propiedad=()=[]Fizz. Buzz(input)==input.ToString();returnproperty.When(input%3!=0&&input%5!=0).Label($"Failed on input {input}");}

La función Etiqueta escribirá ahora una cadena en la consola que contiene la entrada exacta en la que estamos fallando. Con eso en mano, podemos ver el fallo y actualizar nuestra implementación de FizzBuzz de la siguiente manera:

clase públicaFizz{publicstaticstringBuzz(intinput){varoutput="";if(input%3==0)output+="Fizz";if(input%5==0)output+="Buzz";returnstring.IsNullOrEmpty(output)?input.ToString():output;}}

Una rápida retrospectiva

Este ejercicio fue un poco más fácil de lo que había previsto. Sin embargo, puedo ver que llegar a las propiedades en un dominio más complejo sería difícil. Hay algunas grandes guías para encontrar propiedades, pero todavía tengo que intentarlo con una aplicación «real». Uno de los beneficios que he encontrado con TDD es que te ayuda a descubrir tu diseño escribiendo pruebas sencillas y permitiendo que tu solución «emerja» con el tiempo. Con las pruebas basadas en propiedades, hacer pruebas no se siente tan «simple». Pero… esa es mi ingenua suposición basada en la falta de experiencia. También me encontré preguntándome si podría usar pruebas basadas en ejemplos para descubrir propiedades.

Para obtener más información sobre las pruebas basadas en la propiedad, consulte los siguientes enlaces:

Nuestro código fuente completo está abajo.

usandoSistema;usandoFsCheck;usandoFsCheck.Xunit;nombrespruebasbasadasenpropiedad{clase públicaFizzBuzzTests{ [Propiedad]públicaPropiedadcualquiercosa_divisible_por_tres_pero_no_cinco_devoluciones_fizz(intinput){Func<bool>propiedad=()=[;Fizz. Buzz(input)=="Fizz";returnproperty.When(input%3==0&&input%5!=0);} [Propiedad]publicPropertyanything_divisible_by_five_but_not_three_returns_buzz(intinput){Func<bool>property=()=(;Fizz.Buzz(input)=="Buzz";returnproperty.When(input%5==0&&input%3!=0);} [Propiedad(Arbitrario = nuevo[]{tipo de(FizzBuzzGenerator)}]]publicPropiedadcualquiercosa_divisible_por_tres_y_cinco_retornos_fizzbuzz(intinput){varactual=Fizz.Buzz(input);return(actual=="FizzBuzz").ToPropiedad();} [Propiedad]publicPropertycualquier cosa que no sea divisible por tres y cinco debe devolver el valor como cadena (intinput){Func<bool>property=()=()Fizz.Buzz(input)==input.ToString();returnproperty.When(input%3!=0&&input%5!=0). Label($"Failed on input {input}");}}publicclassFizz{publicstaticstringBuzz(intinput){varoutput="";if(input%3==0)output+="Fizz";if(input%5==0)output+="Buzz";returnstring.IsNullOrEmpty(output)? input.ToString():output;}}publicstaticclassFizzBuzzGenerator{publicstaticArbitrary<int>Generate(){returnArb.Default.Int32().Filter(x={{i};x%3==0&&x%5==0);}}

Categorías: technicalTags: testing, tdd, c-sharp