¿Cómo probar las pruebas?

53

Probamos nuestro código para hacerlo más correcto (en realidad, es menos probable que sea incorrecto ). Sin embargo, las pruebas también son código, también pueden contener errores. Y si sus pruebas son defectuosas, difícilmente mejorarán su código.

Puedo pensar en tres posibles tipos de errores en las pruebas:

  1. Errores lógicos, cuando el programador malinterpretó la tarea en cuestión y las pruebas hacen lo que él pensó que deberían hacer, lo cual está mal;

  2. Errores en el marco de prueba subyacente (por ejemplo, una abstracción burlona permeable);

  3. Errores en las pruebas: la prueba está haciendo un poco diferente de lo que el programador cree que es.

Los errores de tipo (1) parecen ser imposibles de evitar (a menos que el programador solo ... se vuelva más inteligente). Sin embargo, (2) y (3) pueden ser manejables. ¿Cómo manejas este tipo de errores? ¿Tienes alguna estrategia especial para evitarlos? Por ejemplo, ¿escribe algunas pruebas especiales "vacías", que solo verifican las presuposiciones del autor de la prueba? Además, ¿cómo aborda la depuración de un caso de prueba roto?

Ryszard Szopa
fuente
3
Cada pieza introductoria que he leído sobre burlas parece tener este problema. Una vez que comienzas a burlarte de las cosas, las pruebas siempre parecen ser más complicadas que el código que están probando. Obviamente, es menos probable que sea así cuando se prueba un código del mundo real, pero es bastante desalentador cuando intentas aprender.
Carson63000
@ Carson63000 Si se trata de una prueba simple que prueba algo con un simulacro probado , la complejidad está dividida y bajo control (creo).
mlvljr
13
Pero entonces, ¿cómo pruebas las pruebas de prueba?
ocodo
+1. El artículo 1 podría ser un error de requisitos. Solo se puede prevenir al revisar los requisitos. Probablemente fuera de las manos del programador a menos que también sean el analista de requisitos
MarkJ
@ocodo: De la misma manera que ves a los Vigilantes. :)
Greg Burghardt

Respuestas:

18

Las pruebas ya están probadas. Las pruebas están protegidas por diseño de los errores, porque las pruebas solo detectan diferencias entre el código y nuestras expectativas. Si hay problemas tenemos un error. El error podría estar en el código o con la misma probabilidad en las pruebas.

  1. Existen algunas técnicas que le impiden agregar el mismo error tanto en el código como en las pruebas:

    1. El cliente debe ser una persona diferente al implementador.
    2. Primero escriba las pruebas y luego el código (como en Test Driven Development).
  2. No necesita probar la plataforma subyacente. Las pruebas no solo ejercitan el código escrito por usted, sino que también ejecutan el código desde la plataforma. Si bien no necesariamente desea detectar errores en la plataforma de prueba, es muy difícil escribir código y pruebas que siempre oculten un error en la plataforma, en otras palabras, es muy difícil tener un error sistemático tanto en sus pruebas / código como en en la plataforma, y ​​la probabilidad se reduce con cada prueba que cree. Incluso si intentaras hacer esto, tendrías una tarea muy difícil.

  3. Podría tener errores en las pruebas, pero generalmente se detectan fácilmente porque el código desarrollado prueba las pruebas. Entre el código y las pruebas tiene una retroalimentación de auto aplicación. Ambos predicen cómo debería comportarse una llamada específica de una interfaz. Si la respuesta es diferente, no necesariamente tiene un error en el código. También podría tener un error en la prueba.

raisercostin
fuente
Muy buena respuesta. Me gusta la idea de un bucle de refuerzo entre las pruebas y el código y la observación de que sería difícil escribir pruebas que oculten constantemente errores en la plataforma subyacente.
Ryszard Szopa
lo que no impide que se creen pruebas basadas en suposiciones erróneas sobre lo que debe hacer el código real. Lo que puede provocar errores muy desagradables que no se detectan durante las pruebas. La única forma de evitar eso es que las pruebas sean escritas por un tercero sin ninguna relación con la organización que escribe el código real, para que no puedan "contaminarse" el pensamiento del otro cuando se trata de interpretar los documentos de requisitos.
Jwent
24

Intente hacer que las pruebas individuales sean lo más pequeñas (cortas) posible.

Esto debería reducir las posibilidades de crear un error en primer lugar. Incluso si logras crear uno, es más fácil de encontrar. Se supone que las pruebas unitarias son pequeñas y específicas, con baja tolerancia a fallas y desviaciones.

Al final, probablemente sea solo una cuestión de experiencia. Cuantas más pruebas escriba, mejor lo hará, menos posibilidades tendrá de hacer pruebas desagradables.

Dr. Hannibal Lecter
fuente
3
¿Qué pasa si las pruebas necesitan una configuración bastante complicada? A veces, este tipo de cosas no están bajo su control.
Ryszard Szopa
Bueno, supongo que la configuración complicada es la "condición inicial" de las pruebas. Si eso falla, todas sus pruebas deberían fallar. De hecho, estoy trabajando en un proyecto así en este momento, y las personas que nunca usaron pruebas unitarias constantemente preguntaban lo mismo ... hasta que explicamos qué son realmente las pruebas unitarias :) Entonces se dieron cuenta de que se puede hacer, a pesar de complejidad del proyecto.
Dr. Hannibal Lecter
Cuál es la mejor manera de verificar que se cumpla esta "condición inicial" es exactamente el punto de mi pregunta. ¿Escribes una prueba separada para eso? ¿O simplemente asumir que las pruebas se romperán si esta condición no es cierta? ¿Qué pasa con la situación cuando la configuración no es "catastróficamente" mala, solo un poco apagada?
Ryszard Szopa
2
Sus pruebas deberían fallar si las condiciones iniciales no son correctas, ese es el punto. Cuando está en estado A, espera resultados B. Si no tiene estado A, una prueba debería fallar. En ese momento, puede investigar por qué falló, malas condiciones iniciales o una mala prueba, pero debería fallar en ambos casos. Incluso si lo es, como usted dice, "ligeramente fuera" (es decir "A" => "B", "a" => "b", pero nunca "a" => "B"o su prueba es malo).
Dr. Hannibal Lecter
19

Una táctica es escribir la prueba antes del código que prueba, y asegurarse de que la prueba falla primero por la razón correcta. Si usa TDD , debe obtener al menos este nivel de prueba de pruebas.

Una forma más exhaustiva de probar la calidad de un conjunto de pruebas es utilizar pruebas de mutación .

Don roby
fuente
2
Y que su prueba falla por la razón correcta .
Frank Shearar
@ Frank - Sí. Agregaré eso a la respuesta.
Don Roby
Y está agregando una nueva prueba para el nuevo comportamiento que se probará. No agregue a las pruebas existentes.
Huperniketes
@DonRoby, ¿Le han resultado útiles las pruebas de mutación en la práctica? ¿Qué deficiencias has encontrado en tus casos de prueba con eso?
dzieciou
4

Para el n. ° 1 y n. ° 3: las pruebas unitarias no deben contener ninguna lógica; si lo hace, probablemente esté probando más de una cosa en su prueba unitaria. Una práctica recomendada para las pruebas unitarias es tener solo una prueba por prueba unitaria.

Mire este video de Roy Osherove para obtener más información sobre cómo escribir bien las pruebas unitarias.

Muelles Myers
fuente
ad # 3 - Estoy de acuerdo en que las pruebas deben ser lo más simples posible y no deben contener ninguna lógica. Sin embargo, piense en la fase de configuración de la prueba, cuando cree los objetos que necesitará. Puede crear objetos ligeramente incorrectos. Este es el tipo de problemas en los que estoy pensando.
Ryszard Szopa
Cuando dice "objetos ligeramente equivocados", ¿quiere decir que el estado del objeto no es correcto o que el diseño real del objeto no es correcto? Para el estado del objeto, probablemente podría escribir pruebas para verificar su validez. Si el diseño es incorrecto, la prueba debería fallar.
Piers Myers
3

En términos de # 1: creo que es una buena idea emparejar / revisar el código para este lado de las cosas. Es fácil hacer presuposiciones o simplemente equivocarse, pero si tiene que explicar lo que está haciendo su prueba, cuál es el punto, es más probable que responda si apunta al objetivo equivocado.

Sam J
fuente
2

Debe haber un punto en el que uno debe dejar de intentar la prueba unitaria. Debe saber cuándo dibujar la línea. ¿Deberíamos escribir casos de prueba para probar casos de prueba? ¿Qué pasa con los nuevos casos de prueba escritos para probar los casos de prueba? ¿Cómo los probaremos?

if (0 > printf("Hello, world\n")) {
  printf("Printing \"Hello, world\" failed\n");
}

Editar: actualizado con la explicación sugerida por el comentario.

aufather
fuente
-1 ¿Qué? Esto parece no tener relevancia.
alternativa el
2
Debe haber un punto en el que uno debe dejar de intentar la prueba unitaria. Debe saber cuándo dibujar la línea. ¿Deberíamos escribir casos de prueba para probar casos de prueba? ¿Qué pasa con los nuevos casos de prueba escritos para probar los casos de prueba? ¿Cómo los probaremos?
aufather
2
Process Brain levantó EInfiniteRecursion mientras intentaba extrapolar su declaración ...
Mason Wheeler
Reemplace su respuesta con su comentario y obtendrá un +1
Nota para uno mismo - piense en un nombre
3
Para ser justos, su ejemplo es un hombre de paja. Está probando el subsistema printf () en una biblioteca C, no el programa real que llama a printf (). Sin embargo, estoy de acuerdo en que en algún momento uno debe romper la prueba recursiva de las pruebas.
Tim Post
2

Oye.
Tienes que las aplicaciones:

  • Tu producto
  • Tu prueba para ese producto.

Cuando ejecuta pruebas contra su producto, en realidad no está interesado en la prueba en sí, sino en la interacción entre su producto y sus pruebas. Si la prueba falla, no dice que la aplicación tenga un error. Dice que la interacción entre el producto y la prueba no fue exitosa . Ahora es su trabajo determinar qué salió mal. Puede ser:

  • la aplicación no se comporta como esperaba (esta expectativa se expresa en su prueba)
  • la aplicación se comporta correctamente, simplemente no ha documentado este comportamiento correctamente (en sus pruebas)

Para mí, las pruebas que fallan no son simples comentarios, que esto y aquello está mal . Es un indicador de que hay inconsistencia, y necesito examinar ambos para verificar que el error salió mal. Al final, soy responsable de verificar que la aplicación sea correcta, las pruebas son solo una herramienta para resaltar áreas que vale la pena verificar.

Las pruebas solo verifican algunas partes de la aplicación. Pruebo la aplicación, pruebo las pruebas.

yoosiba
fuente
2

Las pruebas no deberían ser lo suficientemente "inteligentes" como para albergar errores.

El código que está escribiendo implementa un conjunto de especificaciones. (Si X entonces Y, a menos que Z en cuyo caso Q, etc., etc.). Todo lo que la prueba debe intentar lograr es determinar que X realmente es Y a menos que Z, en cuyo caso Q. Esto significa que todo lo que debe hacer una prueba es establecer X y verificar Y.

Pero eso no cubre todos los casos, probablemente esté diciendo, y tendría razón. Pero si hace que la prueba sea lo suficientemente "inteligente" como para saber que X solo debe ser Y si no Z, entonces básicamente está re-implementando la lógica de negocios en la prueba. Esto es problemático por razones que profundizaremos un poco más abajo. No debería mejorar la cobertura del código al hacer que su primera prueba sea "más inteligente", sino que debe agregar una segunda prueba tonta que establezca X y Z y verifique Q. De esa manera tendrá dos pruebas, una que cubre el caso general ( a veces también conocido como el camino feliz) y uno que cubre el caso límite como una prueba separada.

Hay varias razones para esto, en primer lugar, ¿cómo se determina si una prueba fallida se debe a un error en la lógica de negocios o un error en las pruebas? Obviamente, la respuesta es que si las pruebas son lo más simples posible, es muy poco probable que alberguen errores. Si crees que tus pruebas necesitan pruebas, entonces estás probando mal .

Otras razones incluyen que solo está replicando el esfuerzo (como ya mencioné, escribir una prueba lo suficientemente inteligente como para ejercer todas las posibilidades en una sola prueba es básicamente replicar la lógica de negocios que está tratando de probar en primer lugar), si los requisitos cambian entonces las pruebas deberían ser fáciles de cambiar para reflejar los nuevos requisitos, las pruebas sirven como una especie de documentación (son una forma formal de decir cuál es la especificación de la unidad bajo prueba), y así sucesivamente.

TL: DR: Si sus pruebas necesitan pruebas, lo está haciendo mal. Escribe pruebas tontas .

GordonM
fuente
0

No es una respuesta (no tengo el privilegio de comentar), pero me preguntaba si olvidó otras razones para desarrollar casos de prueba ...
Una vez que descubra todos los errores en las pruebas, puede probar su aplicación fácilmente. Las suites de prueba automatizadas lo ayudarán a encontrar problemas antes, antes de la integración. Los cambios en los requisitos son relativamente más fáciles de probar, ya que los cambios pueden convertirse en una versión más nueva y alterada de los casos de prueba más antiguos que pasan y los casos más antiguos permanecen para detectar fallas.

CMR
fuente
0

Respuesta corta: el código de producción prueba las pruebas .

Compare esto con el modelo de crédito / débito utilizado en economía. La mecánica es muy simple: si el crédito difiere del débito, hay algo mal.

Lo mismo ocurre con las pruebas unitarias: si una prueba falla, indica que algo está mal. Puede ser el código de producción, ¡pero también puede ser el código de prueba! Esta última parte si es importante.

Tenga en cuenta que sus errores tipo (1) no se pueden encontrar mediante pruebas unitarias. Para evitar este tipo de errores, necesita otras herramientas.

vidstige
fuente