Cómo un método de prueba unitaria que devuelve una colección evitando la lógica en la prueba

14

Estoy probando un método que consiste en generar una colección de objetos de datos. Quiero verificar que las propiedades de los objetos se están configurando correctamente. Algunas de las propiedades se establecerán en la misma cosa; otros se establecerán en un valor que depende de su posición en la colección. La forma natural de hacer esto parece ser con un bucle. Sin embargo, Roy Osherove recomienda enfáticamente no usar la lógica en las pruebas unitarias ( Art of Unit Testing , 178). Él dice:

  

Una prueba que contiene lógica generalmente está probando más de una cosa a la vez, lo que no se recomienda, porque la prueba es menos legible y más frágil. Pero la lógica de prueba también agrega complejidad que puede contener un error oculto.

     

Las pruebas deben, como regla general, ser una serie de llamadas de método sin flujos de control, ni siquiera try-catch , y con llamadas de confirmación.

Sin embargo, no puedo ver nada malo en mi diseño (¿de qué otra manera genera una lista de objetos de datos, algunos de cuyos valores dependen de dónde se encuentran en la secuencia? - no se pueden generar y probar exactamente por separado). ¿Hay algo que no sea fácil de probar con mi diseño? ¿O estoy siendo demasiado rígidamente dedicado a la enseñanza de Osherove? ¿O hay alguna magia de prueba de unidad secreta que no conozco que evite este problema? (Estoy escribiendo en C # / VS2010 / NUnit, pero si es posible, estoy buscando respuestas no relacionadas con el idioma).

    
pregunta Kazark 07.05.2013 - 18:58
fuente

3 respuestas

13

TL; DR:

  • escribe la prueba
  • Si la prueba hace demasiado, el código también puede hacer demasiado.
  • Puede que no sea una prueba de unidad (pero no una prueba mala).

Lo primero para probar es que el dogma es inútil. Me gusta leer The Way of Testivus que señala algunos problemas con el dogma de una manera alegre.

  

Escriba la prueba que debe escribirse.

Si la prueba debe escribirse de alguna manera, escríbala de esa manera. Intentar forzar la prueba en un diseño de prueba idealizado o no tenerlo en absoluto no es algo bueno. Tener una prueba hoy que la prueba es mejor que tener una prueba "perfecta" algún día más tarde.

También señalaré el bit en la prueba fea:

  

Cuando el código es feo, las pruebas pueden ser feas.

     

No te gusta escribir pruebas feas, pero el código feo es el que más se necesita probar.

     

No permita que el código feo le impida escribir pruebas, pero deje que el código feo le impida escribir más.

Estos pueden considerarse truismos para aquellos que han estado siguiendo durante mucho tiempo ... y simplemente se inculcan en la forma de pensar y escribir pruebas. Para las personas que no han estado y están tratando de llegar a ese punto, los recordatorios pueden ser útiles (incluso cuando encuentro que volver a leerlos me ayuda a evitar que me encierren en algún dogma).

Tenga en cuenta que al escribir una prueba fea, si el código puede ser una indicación de que el código está tratando de hacer demasiado. Si el código que está probando es demasiado complejo para ejecutarlo correctamente escribiendo una prueba simple, es posible que desee considerar dividir el código en partes más pequeñas que se puedan probar con las pruebas más simples. Uno no debe escribir una prueba de unidad que lo haga todo (entonces podría no ser una prueba de unidad ). Así como los "objetos de dios" son malos, las "pruebas de la unidad de dios" también son malas y deberían ser indicaciones para regresar y mirar el código nuevamente.

Usted debería ser capaz de ejercer todo el código con una cobertura razonable a través de pruebas tan simples. Las pruebas que hacen más pruebas de extremo a extremo que tratan con preguntas más grandes ("Tengo este objeto, agrupado en xml, enviado al servicio web, a través de las reglas, retirado y no mal visto") es una prueba excelente, pero ciertamente no lo es. No haga una prueba de unidad (y cae en el ámbito de las pruebas de integración, incluso si se ha burlado de los servicios a los que llama y se ha personalizado en las bases de datos de memoria para realizar las pruebas). Puede seguir utilizando el marco XUnit para realizar pruebas, pero el marco de pruebas no lo convierte en una prueba unitaria.

    
respondido por el user40980 07.05.2013 - 20:36
fuente
6

Estoy agregando una nueva respuesta porque mi perspectiva es diferente de cuando escribí la pregunta y la respuesta originales; no tiene sentido unirlos en uno solo.

Lo dije en la pregunta original

  

Sin embargo, no puedo ver nada incorrecto en mi diseño (¿de qué otra manera genera una lista de objetos de datos, algunos de cuyos valores dependen de dónde se encuentran en la secuencia?), no se pueden generar y probar exactamente por separado)

Aquí es donde me equivoqué. Después de realizar la programación funcional durante el último año, ahora me doy cuenta de que solo necesitaba una operación de recolección con un acumulador. Entonces podría escribir mi función como una función pura que operaba en una sola cosa y usar alguna función de biblioteca estándar para aplicarla a la colección.

Por lo tanto, mi nueva respuesta es: utilice las técnicas de programación funcional y evitará este problema la mayor parte del tiempo. Puede escribir sus funciones para operar en cosas individuales y solo aplicarlas a colecciones de cosas en el último momento. Pero si son puros, puedes probarlos sin hacer referencia a colecciones.

Para una lógica más compleja, confíe en pruebas basadas en propiedades . Cuando tienen lógica, debe ser menor que e inversa a la lógica del código bajo prueba, y cada prueba verifica mucho más que una prueba de unidad basada en caso que la pequeña cantidad de lógica vale la pena.

Por encima de todo, siempre se apoye en sus tipos . Obtén los tipos más fuertes que puedas y úsalos en tu beneficio. Esto reducirá la cantidad de pruebas que tiene que escribir en primer lugar.

    
respondido por el Kazark 03.09.2015 - 19:59
fuente
4

No intentes probar demasiadas cosas a la vez. Cada una de las propiedades de cada objeto de datos en la colección es demasiado para una prueba. En su lugar, recomiendo:

  1. Si la colección es de longitud fija, escriba una prueba de unidad para validar la longitud. Si es de longitud variable, escriba varias pruebas de longitudes que caractericen su comportamiento (por ejemplo, 0, 1, 3, 10). De cualquier manera, no valide las propiedades en estas pruebas.
  2. Escriba una prueba de unidad para validar cada una de las propiedades. Si la colección es de longitud fija y corta, simplemente haga valer contra una propiedad de cada uno de los elementos para cada prueba. Si es de longitud fija pero larga, elija una muestra representativa pero pequeña de los elementos para hacer valer una propiedad cada uno. Si es de longitud variable, genere una colección relativamente corta pero representativa (es decir, quizás tres elementos) y haga valer contra una propiedad de cada uno.

Hacerlo de esta manera hace que las pruebas sean lo suficientemente pequeñas para que no sea doloroso dejar los bucles. Ejemplo de C # / Unidad, método dado bajo prueba ICollection<Foo> generateFoos(uint numberOfFoos) :

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

Si estás acostumbrado al paradigma de "prueba de unidad plana" (sin lógica / estructuras anidadas), estas pruebas parecen bastante limpias. Por lo tanto, se evita la lógica en las pruebas al identificar el problema original como un intento de probar demasiadas propiedades a la vez, en lugar de no tener bucles.

    
respondido por el Kazark 07.05.2013 - 21:21
fuente

Lea otras preguntas en las etiquetas