¿Son más importantes las afirmaciones o las pruebas unitarias?

51

Tanto las afirmaciones como las pruebas unitarias sirven como documentación para una base de código y un medio para descubrir errores. Las principales diferencias son que las afirmaciones funcionan como verificaciones de cordura y ven entradas reales, mientras que las pruebas unitarias se ejecutan en entradas simuladas específicas y son pruebas contra una única "respuesta correcta" bien definida. ¿Cuáles son los méritos relativos de usar afirmaciones versus pruebas unitarias como el medio principal para verificar la corrección? ¿Cuál crees que debería enfatizarse más?

dsimcha
fuente
44
Me gusta mucho esta idea ... tanto que estoy haciendo del tema una tarea para mi curso de prueba de software y garantía de calidad. :-)
Macneil
Otra pregunta es: ¿debería hacer una prueba unitaria de sus afirmaciones? ;)
mojuba

Respuestas:

43

Las afirmaciones son útiles para informarle sobre el estado interno del programa . Por ejemplo, que sus estructuras de datos tienen un estado válido, por ejemplo, que una Timeestructura de datos no tendrá el valor de 25:61:61. Las condiciones verificadas por afirmaciones son:

  • Condiciones previas, que aseguran que la persona que llama mantiene su contrato,

  • Condiciones posteriores, que aseguran que la persona que llama mantiene su contrato, y

  • Invariantes, que aseguran que la estructura de datos siempre tenga alguna propiedad después de que la función regrese. Una invariante es una condición que es una condición previa y una condición posterior.

Las pruebas unitarias son útiles para informarle sobre el comportamiento externo del módulo . Es Stackposible que tenga un estado consistente después de push()que se llame al método, pero si el tamaño de la pila no aumenta en tres después de que se llama tres veces, entonces eso es un error. (Por ejemplo, el caso trivial donde la push()implementación incorrecta solo verifica las afirmaciones y las salidas).

En sentido estricto, la principal diferencia entre las afirmaciones y las pruebas unitarias es que las pruebas unitarias tienen datos de prueba (valores para que el programa se ejecute), mientras que las afirmaciones no. Es decir, puede ejecutar sus pruebas unitarias automáticamente, mientras que no puede decir lo mismo para las afirmaciones. En aras de esta discusión, he asumido que está hablando de ejecutar el programa en el contexto de pruebas de función de orden superior (que ejecutan todo el programa y no controlan módulos como pruebas unitarias). Si no está hablando de las pruebas de función automatizadas como el medio para "ver entradas reales", entonces claramente el valor radica en la automatización y, por lo tanto, las pruebas unitarias ganarían. Si está hablando de esto en el contexto de las pruebas de función (automatizadas), consulte a continuación.

Puede haber cierta superposición en lo que se está probando. Por ejemplo, Stackla condición posterior de una realidad puede afirmar que el tamaño de la pila aumenta en uno. Pero hay límites a lo que se puede realizar en esa afirmación: ¿Debería también verificar que el elemento superior es lo que se acaba de agregar?

Para ambos, el objetivo es aumentar la calidad. Para las pruebas unitarias, el objetivo es encontrar errores. Para las afirmaciones, el objetivo es facilitar la depuración observando estados de programa no válidos tan pronto como ocurran.

Tenga en cuenta que ninguna de las técnicas verifica la corrección. De hecho, si realiza pruebas unitarias con el objetivo de verificar que el programa sea correcto, probablemente obtendrá una prueba poco interesante que sabe que funcionará. Es un efecto psicológico: harás lo que sea para cumplir tu objetivo. Si su objetivo es encontrar errores, sus actividades reflejarán eso.

Ambos son importantes y tienen sus propios propósitos.

[Como nota final sobre las afirmaciones: para obtener el máximo valor, debe usarlas en todos los puntos críticos de su programa y no en algunas funciones clave. De lo contrario, la fuente original del problema podría haber sido enmascarada y difícil de detectar sin horas de depuración.]

Macneil
fuente
7

Cuando hable de afirmaciones, tenga en cuenta que se pueden desactivar con solo pulsar un interruptor.

Ejemplo de una afirmación muy mala:

char *c = malloc(1024);
assert(c != NULL);

¿Por qué es esto malo? Porque no se realiza ninguna comprobación de errores si se omite esa afirmación debido a que se está definiendo algo como NDEBUG.

(Probablemente) una prueba unitaria solo sería predeterminada en el código anterior. Claro, hizo su trabajo al decirle que algo salió mal, ¿o no? ¿Qué posibilidades hay de malloc()que falle en la prueba?

Las afirmaciones son para propósitos de depuración cuando un programador necesita implicar que ningún evento 'normal' causaría que la afirmación se disparara. malloc()fallar es, de hecho, un evento normal, por lo tanto, nunca debe afirmarse.

Hay muchos otros casos en los que se utilizan aserciones en lugar de manejar adecuadamente las cosas que podrían salir mal. Esta es la razón por la cual las afirmaciones tienen mala reputación, y por qué los idiomas como Go no las incluyen.

Las pruebas unitarias están diseñadas para informarle cuando algo que cambió rompió con otra cosa. Están diseñados para salvarlo (en la mayoría de los casos) de pasar por todas las funciones del programa (sin embargo, los probadores son importantes para las versiones) cada vez que realiza una compilación.

Realmente no hay una correlación clara entre los dos, aparte de que ambos te dicen que algo salió mal. Piense en una afirmación como un punto de interrupción en algo en lo que está trabajando, sin tener que usar un depurador. Piense en una prueba unitaria como algo que le dice si rompió algo en lo que no está trabajando.

Tim Post
fuente
3
Por eso se supone que las afirmaciones son siempre afirmaciones verdaderas, no pruebas de error.
Dominique McDonnell
@DominicMcDonnell Bueno, las declaraciones 'deberían ser verdaderas'. A veces afirmo que evito las peculiaridades del compilador, como ciertas versiones de gcc que tienen un buggy abs () incorporado. Lo importante a recordar es que las compilaciones de producción deberían tenerlas desactivadas de todos modos .
Tim Post
Y aquí creo que el código de producción es el lugar que necesita la afirma la mayoría , porque es en la producción donde obtendrá entradas que no pensó posible. La producción es el lugar que elimina todos los errores más difíciles.
Frank Shearar
@ Frank Shearar, muy cierto. Todavía debería estar fallando mucho en el primer estado de error detectado en producción. Seguro que los usuarios se van a quejar, pero esa es la única forma en que se asegurará de que los errores se solucionen. Y es mucho mejor obtener un bla que fue 0 que una excepción de memoria para desreferenciar algunas llamadas de función más tarde.
Dominique McDonnell
¿Por qué no es mejor manejar los problemas típicos pero a menudo poco comunes (a los ojos del usuario) en lugar de afirmar que nunca deberían suceder? No me doy cuenta de esta sabiduría. Claro, afirme que un generador principal devuelve un primer, lo que requiere un poco de trabajo extra, lo que se espera en las compilaciones de depuración. Afirmar algo que un idioma puede probar de forma nativa es simplemente, bueno, estúpido. No envolver esas pruebas en otro interruptor que se puede apagar unos meses después de un lanzamiento, aún más estúpido.
Tim Post
5

Ambas son herramientas utilizadas para ayudar a mejorar la calidad general del sistema que está creando. Mucho depende del idioma que esté utilizando, el tipo de aplicación que está creando y dónde se dedica mejor su tiempo. Sin mencionar que tienes algunas escuelas de pensamiento al respecto.

Para comenzar, si está usando un idioma sin una assertpalabra clave, no puede usar afirmaciones (al menos no en la forma en que estamos hablando aquí). Durante mucho tiempo, Java no tenía una assertpalabra clave, y varios idiomas todavía no. Las pruebas unitarias se vuelven bastante más importantes. En algunos idiomas, las aserciones solo se ejecutan cuando se establece un indicador (nuevamente con Java aquí). Cuando las protecciones no siempre están ahí, no es una característica muy útil.

Hay una escuela de pensamiento que dice que si está "afirmando" algo, también podría escribir un bloque de excepción if/ throwsignificativo. Este proceso de pensamiento proviene de muchas afirmaciones colocadas al comienzo de un método para garantizar que todos los valores estén dentro de los límites. Probar sus condiciones previas es una parte muy importante de tener una condición posterior esperada.

Las pruebas unitarias son códigos adicionales que deben escribirse y mantenerse. Para muchos esto es un inconveniente. Sin embargo, con la actual cosecha de marcos de prueba unitarios, puede generar una mayor cantidad de condiciones de prueba con un código relativamente pequeño. Las pruebas y "teorías" parametrizadas realizarán la misma prueba con una gran cantidad de muestras de datos que pueden descubrir algunos errores difíciles de encontrar.

Personalmente, descubro que obtengo más kilometraje con las pruebas unitarias que con las aspersiones por aspersión, pero eso se debe a las plataformas que desarrollo la mayor parte del tiempo (Java / C #). Otros idiomas tienen un soporte de afirmación más robusto e incluso "Diseño por contrato" (ver más abajo) para proporcionar aún más garantías. Si estuviera trabajando con uno de estos idiomas, podría usar el DBC más que la prueba de la unidad.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

Berin Loritsch
fuente