¿Por qué las pruebas unitarias que fallan se consideran malas?


93

Aparentemente, en algunas organizaciones, parte del proceso de lanzamiento del software es usar pruebas unitarias, pero en cualquier momento todas las pruebas unitarias deben pasar. Por ejemplo, puede haber alguna pantalla que muestre que todas las pruebas unitarias pasan en verde, lo que se supone que es bueno.

Personalmente, creo que no es así por las siguientes razones:

  1. Promueve la idea de que el código debe ser perfecto y que no deben existir errores, lo que en el mundo real seguramente es imposible para un programa de cualquier tamaño.

  2. Es un desincentivo pensar en pruebas unitarias que fracasarán. O, sin duda, realizar pruebas unitarias que serían difíciles de solucionar.

  3. Si en algún momento todas las pruebas unitarias pasan, entonces no hay una imagen general del estado del software en ningún momento. No hay hoja de ruta / objetivo.

  4. Disuade de escribir pruebas unitarias por adelantado, antes de la implementación.

Incluso sugeriría que incluso lanzar software con pruebas unitarias fallidas no es necesariamente malo. Al menos, entonces sabes que algún aspecto del software tiene limitaciones.

¿Me estoy perdiendo de algo? ¿Por qué las organizaciones esperan que todas las pruebas unitarias pasen? ¿No es esto vivir en un mundo de sueños? ¿Y no disuade realmente una comprensión real del código?


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
maple_shaft

Respuestas:


270

Esta pregunta contiene en mi humilde opinión varios conceptos erróneos, pero el principal en el que me gustaría centrarme es que no diferencia entre las ramas de desarrollo local, el tronco, la puesta en escena o las ramas de liberación.

En una rama de desarrollo local, es probable que tenga algunas pruebas unitarias fallidas en casi cualquier momento. En el maletero, solo es aceptable hasta cierto punto, pero ya es un fuerte indicador para arreglar las cosas lo antes posible. Tenga en cuenta que las pruebas de unidades fallidas en el baúl pueden perturbar al resto del equipo, ya que requieren que todos verifiquen si su último cambio no fue el causante de la falla.

En una rama de puesta en escena o de lanzamiento, las pruebas que fallan son "alerta roja", lo que muestra que ha ocurrido algo completamente incorrecto con algún conjunto de cambios, cuando se fusionó desde el tronco a la rama de lanzamiento.

Incluso sugeriría que incluso lanzar software con pruebas unitarias fallidas no es necesariamente malo.

Lanzar software con algunos errores conocidos por debajo de cierta gravedad no es necesariamente malo. Sin embargo, estos fallos conocidos no deberían causar una prueba de unidad fallida. De lo contrario, después de cada ejecución de prueba unitaria, habrá que examinar las 20 pruebas unitarias fallidas y verificar una por una si la falla fue aceptable o no. Esto se vuelve engorroso, propenso a errores y descarta una gran parte del aspecto de automatización de las pruebas unitarias.

Si realmente tiene pruebas de errores conocidos y aceptables, use la función de deshabilitar / ignorar de la herramienta de prueba de la unidad (para que no se ejecuten de manera predeterminada, solo a pedido). Además, agregue un ticket de baja prioridad a su rastreador de problemas, para que el problema no se olvide.


18
Creo que esta es la respuesta real. OP menciona "proceso de lanzamiento" y "alguna pantalla [que muestra los resultados de la prueba]", que suena como un servidor de compilación. La publicación no es lo mismo que el desarrollo (¡no se desarrolle en producción!); está bien tener pruebas fallidas en el desarrollo, son como TODOS; todos deberían ser verdes (HECHO) cuando se envían al servidor de compilación.
Warbo

77
Una respuesta mucho mejor que la más votada. Muestra una comprensión de dónde proviene la operación sin darles una conferencia sobre una situación mundial ideal, reconoce la posibilidad de errores conocidos (para los cuales no se elimina toda la hoja de ruta para solucionar un caso de esquina raro) y explica que las pruebas unitarias solo deberían definitivamente ser verde en una rama / proceso de lanzamiento.
Sebastiaan van den Broek

55
@SebastiaanvandenBroek: gracias por su respuesta positiva. Solo para dejar esto en claro: las pruebas de unidades fallidas en mi humilde opinión deberían ser raras incluso en el baúl, ya que obtener tales fallas con demasiada frecuencia perturbará a todo el equipo, no solo al que realizó el cambio que causó la falla.
Doc Brown

44
Creo que el problema aquí es pensar que todas las pruebas automatizadas son pruebas unitarias. Muchos marcos de prueba incluyen la capacidad de marcar pruebas que se espera que fallen (a menudo llamadas XFAIL). (Esto es diferente de una prueba que requiere un resultado de error. Idealmente, las pruebas XFAIL tendrían éxito, pero no lo hacen). El conjunto de pruebas aún pasa con estos errores. El caso de uso más común es cosas que solo fallan en algunas plataformas (y solo son XFAIL en ellas), pero usar la función para rastrear algo que requerirá demasiado trabajo para solucionar en este momento también está dentro de lo razonable. Pero este tipo de pruebas generalmente no son pruebas unitarias.
Kevin Cathcart

1
+1, aunque sugiero una pequeña adición (en negrita) a esta oración: "Esto se vuelve engorroso, propenso a errores, condiciona a las personas a ignorar las fallas en el conjunto de pruebas como ruido , y descarta una gran parte del aspecto de automatización de las pruebas unitarias .
mtraceur

228

... todas las pruebas unitarias pasan en verde, lo que se supone que es bueno.

Que es buena. No "se supone que debe ser" al respecto.

Promueve la idea de que el código debe ser perfecto y que no deben existir errores, lo que en el mundo real seguramente es imposible para un programa de cualquier tamaño.

No. Esto demuestra que has probado el código tan bien como puedes hasta ahora. Es completamente posible que sus pruebas no cubran todos los casos. Si es así, cualquier error eventualmente aparecerá en los informes de errores y usted escribirá pruebas [fallidas] para reproducir los problemas y luego arreglará la aplicación para que las pruebas pasen.

Es un desincentivo pensar en pruebas unitarias que fracasarán.

Las pruebas negativas o negativas imponen límites firmes a lo que su solicitud aceptará y no aceptará. La mayoría de los programas que conozco se opondrán a una "fecha" del 30 de febrero. Además, los desarrolladores, tipos creativos que somos, no quieren romper "sus bebés". El enfoque resultante en casos de "camino feliz" conduce a aplicaciones frágiles que se rompen, a menudo.

Para comparar la mentalidad del Desarrollador y el Probador:

  • Un desarrollador se detiene tan pronto como el código hace lo que quiere.
  • Un probador se detiene cuando ya no puede romper el código.

Estas son perspectivas radicalmente diferentes y difíciles de conciliar para muchos desarrolladores.

O, sin duda, realizar pruebas unitarias que serían difíciles de solucionar.

No escribes pruebas para hacer el trabajo por ti mismo. Escribes pruebas para asegurarte de que tu código está haciendo lo que se supone que debe hacer y, lo que es más importante, que continúa haciendo lo que se supone que debe hacer después de que hayas cambiado su implementación interna.

  • La depuración "prueba" que el código hace lo que quiere hoy .
  • Las pruebas "prueban" que el código sigue haciendo lo que quieres con el tiempo .

Si en algún momento todas las pruebas unitarias pasan, entonces no hay una imagen general del estado del software en ningún momento. No hay hoja de ruta / objetivo.

La única prueba de "imagen" que le brinda es una instantánea de que el código "funciona" en el momento en que se probó. Cómo evoluciona después de eso es una historia diferente.

Disuade de escribir pruebas unitarias por adelantado, antes de la implementación.

Eso es exactamente lo que deberías estar haciendo. Escriba una prueba que falle (porque el método que está probando aún no se ha implementado), luego escriba el código del método para que el método funcione y, por lo tanto, la prueba pase. Eso es más o menos el quid del desarrollo impulsado por pruebas.

Incluso sugeriría que incluso lanzar software con pruebas unitarias fallidas no es necesariamente malo. Al menos, entonces sabes que algún aspecto del software tiene limitaciones.

Publicar código con pruebas rotas significa que parte de su funcionalidad ya no funciona como antes. Eso puede ser un acto deliberado porque ha corregido un error o mejorado una característica (pero luego debería haber cambiado la prueba primero para que fallara, luego codificó la corrección / mejora, haciendo que la prueba funcione en el proceso). Más importante aún: todos somos humanos y cometemos errores. Si rompe el código, debería romper las pruebas y esas pruebas interrumpidas deberían hacer sonar las alarmas.

¿No es esto vivir en un mundo de sueños?

En todo caso, está viviendo en el mundo real , reconociendo que los desarrolladores son omniscientes ni infallable, que nos hacen cometer errores y que necesitamos una red de seguridad que nos atrapan si y cuando nos hacemos lío!
Ingrese las pruebas.

¿Y no disuade realmente una comprensión real del código?

Quizás. No necesariamente necesita comprender la implementación de algo para escribir pruebas para ello (eso es parte del objetivo de ellos). Las pruebas definen el comportamiento y los límites de la aplicación y aseguran que permanezcan igual a menos que los cambie deliberadamente.


77
@Tibos: deshabilitar una prueba es como comentar una función. Tienes control de versiones. Úsalo.
Kevin

66
@Kevin No sé a qué te refieres con 'úsalo'. Marco una prueba como 'omitida' o 'pendiente' o cualquier convención que use mi corredor de prueba, y confirmo esa etiqueta de omisión al control de versiones.
dcorking

44
@dcorking: quiero decir, no comentes el código, bórralo. Si luego decide que lo necesita, restaurelo desde el control de versiones. Cometer una prueba deshabilitada no es diferente.
Kevin

44
"Es completamente posible que sus pruebas no cubran todos los casos". Me atrevería a decir que por cada código de prueba no trivial probado, definitivamente no tiene todos los casos cubiertos.
corsiKa

66
@Tibos Los defensores de las pruebas unitarias dicen que el tiempo de ciclo desde que se escribe una prueba fallida hasta que se escribe el código debe ser pequeño (por ejemplo, 20 minutos. Algunos reclaman 30 segundos). Si no tiene tiempo para escribir el código de inmediato, probablemente sea demasiado complejo. Si no es complejo, elimine la prueba, ya que puede reescribirse si la característica eliminada se agrega nuevamente. ¿Por qué no comentarlo? No sabe que la función volverá a agregarse, por lo que la prueba comentada (o código) es solo ruido.
CJ Dennis

32

¿Por qué las pruebas unitarias que fallan se consideran malas?

No lo son: el desarrollo basado en pruebas se basa en la noción de pruebas fallidas. Fallas en las pruebas unitarias para impulsar el desarrollo, fallas en las pruebas de aceptación para conducir una historia ...

Lo que te falta es el contexto ; ¿Dónde pueden fallar las pruebas unitarias?

La respuesta habitual es que las pruebas unitarias solo pueden fallar en cajas de arena privadas.

La noción básica es la siguiente: en un entorno donde se comparten pruebas fallidas, se requiere un esfuerzo adicional para comprender si un cambio en el código de producción ha introducido un nuevo error. La diferencia entre cero y no cero es mucho más fácil de detectar y gestionar que la diferencia entre N y no N.

Además, mantener limpio el código compartido significa que los desarrolladores pueden permanecer en la tarea. Cuando fusiono su código, no necesito cambiar los contextos del problema que me pagan por resolver para calibrar mi comprensión de cuántas pruebas deberían fallar. Si el código compartido está pasando todas las pruebas, cualquier falla que aparezca cuando fusiono mis cambios debe ser parte de la interacción entre mi código y la línea de base limpia existente.

Del mismo modo, durante el embarque, un nuevo desarrollador puede ser productivo más rápidamente, ya que no necesitan perder tiempo descubriendo qué pruebas fallidas son "aceptables".

Para ser más precisos: la disciplina es que las pruebas que se ejecutan durante la construcción deben pasar.

Lo mejor que puedo decir es que no hay nada de malo en tener pruebas fallidas que están deshabilitadas .

Por ejemplo, en un entorno de "integración continua", compartirás código en una alta cadencia. La integración a menudo no significa necesariamente que sus cambios tengan que estar listos para su lanzamiento. Hay una variedad de técnicas de despliegue oscuro que evitan que el tráfico se libere en secciones del código hasta que estén listas.

Esas mismas técnicas se pueden usar para deshabilitar las pruebas fallidas también.

Uno de los ejercicios que realicé en un lanzamiento puntual fue el desarrollo de un producto con muchas pruebas fallidas. La respuesta que obtuvimos fue simplemente revisar la suite, deshabilitar las pruebas fallidas y documentar cada una. Eso nos permitió llegar rápidamente a un punto en el que todas las pruebas habilitadas estaban pasando, y la gerencia / donante de objetivos / propietario de oro podían ver qué intercambios habíamos hecho para llegar a ese punto, y podían tomar decisiones informadas sobre la limpieza frente al nuevo trabajo.

En resumen: existen otras técnicas para el seguimiento del trabajo que no se han realizado, que dejar un montón de pruebas fallidas en la suite en ejecución.


Habría dicho "No hay ... nada de malo en tener pruebas fallidas que están deshabilitadas ".
CJ Dennis

Ese cambio ciertamente aclara el significado. Gracias.
VoiceOfUnreason

26

Hay muchas respuestas excelentes, pero me gustaría agregar otro ángulo que creo que aún no está bien cubierto: cuál es exactamente el punto de tener pruebas.

Las pruebas unitarias no están ahí para verificar que su código esté libre de errores.

Creo que este es el principal error. Si este fuera su papel, de hecho esperaría tener pruebas fallidas por todas partes. Pero en vez,

Las pruebas unitarias verifican que su código haga lo que usted cree que hace.

En casos extremos, puede incluir verificar que los errores conocidos no se corrijan. El punto es tener control sobre su base de código y evitar cambios accidentales. Cuando realiza un cambio, está bien y se espera que rompa algunas pruebas: está cambiando el comportamiento del código. La prueba recién rota ahora es un buen rastro de lo que has cambiado. Verifique que todas las roturas se ajusten a lo que desea de su cambio. Si es así, solo actualice las pruebas y continúe. Si no, bueno, su nuevo código definitivamente tiene errores, ¡vuelva y corríjalo antes de enviarlo!

Ahora, todo lo anterior funciona solo si todas las pruebas son verdes, dando un resultado positivo: así es exactamente como funciona el código. Las pruebas rojas no tienen esa propiedad. "Esto es lo que este código no hace" rara vez es una información útil.

Las pruebas de aceptación pueden ser lo que estás buscando.

Existen pruebas de aceptación. Puede escribir un conjunto de pruebas que deben cumplirse para llamar al próximo hito. Está bien que sean rojos, porque para eso fueron diseñados. Pero son muy diferentes de las pruebas unitarias y no pueden ni deben reemplazarlas.


2
Una vez tuve que reemplazar una biblioteca por otra. Las pruebas unitarias me ayudaron a asegurar que todos los casos de esquina aún fueran tratados de manera idéntica por el nuevo código.
Thorbjørn Ravn Andersen

24

Lo veo como el equivalente de software del síndrome de ventana rota .

Las pruebas de trabajo me dicen que el código es de una calidad dada y que los propietarios del código se preocupan por él.

En cuanto a cuándo debe preocuparse por la calidad, eso depende más bien de qué rama / repositorio de código fuente está trabajando. El código de desarrollo puede muy bien tener pruebas rotas que indican trabajo en progreso (¡con suerte!).

Las pruebas rotas en una sucursal / repositorio para un sistema en vivo deberían hacer sonar inmediatamente las alarmas. Si se permite que las pruebas interrumpidas continúen fallando o si se marcan permanentemente como "ignorar", espere que su número aumente con el tiempo. Si no se revisan regularmente, se habrá establecido el precedente de que está bien que se dejen las pruebas rotas.

Las pruebas rotas se ven tan peyorativamente en muchas tiendas como para tener una restricción sobre si el código roto puede incluso ser cometido .


99
Si las pruebas documentan la forma en que se encuentra un sistema, ciertamente siempre deberían estar aprobadas; si no lo están, significa que los invariantes están rotos. Pero si documentan la forma en que se supone que debe ser un sistema , las pruebas fallidas también pueden tener su uso, siempre y cuando el marco de prueba de su unidad admita una buena forma de marcarlas como "problemas conocidos", y si las vincula con un elemento en su rastreador de problemas. Creo que ambos enfoques tienen sus méritos.
Luaan

1
@Luaan Sí, esto supone que todas las pruebas unitarias se crean por igual. Ciertamente, no es raro que los gerentes de compilación corten y corten las pruebas a través de algún atributo dependiendo de cuánto tiempo se ejecuten, cuán frágiles sean y otros criterios.
Robbie Dee

Esta respuesta es genial por mi propia experiencia. Una vez que algunas personas se acostumbren a ignorar un montón de pruebas fallidas, o para romper las mejores prácticas en algunos puntos, deje pasar un par de meses y verá que el% de las pruebas ignoradas aumenta dramáticamente, la calidad del código baja al nivel de "script de pirateo" . Y será muy difícil recordar a todos al proceso.
usr-local-ΕΨΗΕΛΩΝ

11

Aquí está la falacia lógica subyacente:

Si es bueno cuando pasan todas las pruebas, entonces debe ser malo si alguna prueba falla.

Con las pruebas unitarias, ES bueno cuando pasan todas las pruebas. También es bueno cuando una prueba falla. Los dos no necesitan estar en oposición.

Una prueba fallida es un problema que sus herramientas detectaron antes de que llegara a un usuario. Es una oportunidad para corregir un error antes de que se publique. Y eso es algo bueno.


Interesante línea de pensamiento. Veo la falacia de la pregunta más como esta: "dado que es bueno cuando falla una prueba unitaria, es malo cuando pasan todas las pruebas".
Doc Brown

Si bien su último párrafo es un buen punto, parece que el problema es más un malentendido de "en cualquier momento todas las pruebas unitarias deben pasar" (como indica la respuesta aceptada) y el punto de las pruebas unitarias.
Dukeling

9

La respuesta de Phill W es genial. No puedo reemplazarlo.

Sin embargo, quiero centrarme en otra parte que puede haber sido parte de la confusión.

Aparentemente, en algunas organizaciones, parte del proceso de lanzamiento del software es usar pruebas unitarias, pero en cualquier momento todas las pruebas unitarias deben pasar

"en cualquier momento" está exagerando su caso. Lo importante es que las pruebas unitarias pasen después de que se haya implementado cierto cambio, antes de comenzar a implementar otro cambio.
Así es como realiza un seguimiento de qué cambio provocó la aparición de un error. Si las pruebas unitarias comenzaron a fallar después de implementar el cambio 25 pero antes de implementar el cambio 26, entonces sabrá que el cambio 25 causó el error.

Durante la implementación de un cambio, por supuesto, las pruebas unitarias podrían fallar; Eso depende mucho de cuán grande sea el cambio. Si estoy volviendo a desarrollar una característica principal, que es más que un pequeño ajuste, es probable que rompa las pruebas por un tiempo hasta que termine de implementar mi nueva versión de la lógica.


Esto puede crear conflictos en cuanto a las reglas del equipo. En realidad me encontré con esto hace unas semanas:

  • Cada commit / push provoca una compilación. La compilación nunca debe fallar (si falla o falla alguna prueba, se culpa al desarrollador que se compromete).
  • Se espera que cada desarrollador empuje sus cambios (incluso si están incompletos) al final del día, para que los líderes del equipo puedan revisar el código por la mañana.

Cualquiera de las dos reglas estaría bien. Pero ambas reglas no pueden funcionar juntas. Si se me asigna un cambio importante que demore varios días en completarse, no podría cumplir con ambas reglas al mismo tiempo. A menos que comente mis cambios todos los días y solo los comente sin comentar después de que todo esté hecho; que es solo un trabajo sin sentido.

En este escenario, el problema aquí no es que las pruebas unitarias no tengan ningún propósito; Es que la empresa tiene expectativas poco realistas . Su conjunto de reglas arbitrario no cubre todos los casos, y el incumplimiento de las reglas se considera ciegamente como un error del desarrollador en lugar de un error de la regla (que es, en mi caso).


3
La única forma en que esto puede funcionar es mediante la ramificación, de modo que los desarrolladores se comprometan y presionen para presentar ramas que no necesitan construirse limpiamente mientras estén incompletas, pero las confirmaciones a la rama central desencadenan una construcción, que debería compilarse limpiamente.
Gwyn Evans

1
Hacer cumplir cambios incompletos es absurdo, no veo ninguna justificación para hacerlo. ¿Por qué no revisar el código cuando se completa el cambio?
Callum Bradbury

Bueno, por un lado, es una forma rápida de garantizar que el código no solo esté en la computadora portátil / estación de trabajo del desarrollador si su disco duro dejara de funcionar o se perdiera, si hay una política de compromiso incluso si está en el medio de trabajo, entonces Hay una cantidad limitada de trabajo en riesgo.
Gwyn Evans

1
Las banderas de características arreglan la aparente paradoja.
RubberDuck

1
@Flater sí, para reelaborar la lógica existente también.
RubberDuck

6

Si no repara todas las pruebas unitarias, puede ingresar rápidamente al estado en el que nadie repara las pruebas rotas.

  1. Es incorrecto ya que pasar las pruebas unitarias no muestran que el código sea perfecto

  2. Es un desincentivo crear un código que también sea difícil de probar, lo cual es bueno desde el punto de vista del diseño

  3. La cobertura del código puede ayudar allí (aunque no es una panacea). Además, las pruebas unitarias son solo un aspecto de las pruebas: también desea pruebas de integración / aceptación.


6

Para agregar algunos puntos a las respuestas ya buenas ...

pero en cualquier momento todas las pruebas unitarias deben pasar

Esto muestra una falta de comprensión de un proceso de lanzamiento. Una falla de prueba puede indicar una característica planificada bajo TDD que aún no se ha implementado; o puede indicar un problema conocido que tiene una solución planificada para un lanzamiento futuro; o puede ser simplemente algo en lo que la gerencia ha decidido que esto no es lo suficientemente importante como para solucionarlo porque es poco probable que los clientes lo noten. La clave de todo lo que comparten es que la gerencia ha emitido un juicio sobre la falla.

Promueve la idea de que el código debe ser perfecto y que no deben existir errores, lo que en el mundo real seguramente es imposible para un programa de cualquier tamaño.

Otras respuestas han cubierto los límites de las pruebas.

Sin embargo, no entiendo por qué piensas que eliminar errores es un inconveniente. Si no desea entregar el código que ha verificado (lo mejor que puede) hace lo que se supone que debe hacer, ¿por qué está trabajando en software?

Si en algún momento todas las pruebas unitarias pasan, entonces no hay una imagen general del estado del software en ningún momento. No hay hoja de ruta / objetivo.

¿Por qué debe haber una hoja de ruta?

Las pruebas unitarias inicialmente verifican que la funcionalidad funciona, pero luego (como pruebas de regresión) verifican que no haya roto nada inadvertidamente. Para todas las funciones con pruebas unitarias existentes, no hay una hoja de ruta . Se sabe que todas las funciones funcionan (dentro de los límites de las pruebas). Si ese código está terminado, no tiene una hoja de ruta porque no hay necesidad de trabajar más en él.

Como ingenieros profesionales, debemos evitar la trampa del chapado en oro. Los aficionados pueden darse el lujo de perder el tiempo haciendo pequeños ajustes con algo que funcione. Como profesionales, necesitamos entregar un producto. Eso significa que tenemos algo funcionando, verificamos que está funcionando y pasamos al siguiente trabajo.


6

Promueve la idea de que el código debe ser perfecto y que no deben existir errores, lo que en el mundo real seguramente es imposible para un programa de cualquier tamaño.

No es verdad. ¿Por qué crees que es imposible? Aquí ejemplo para el programa que funciona:

public class MyProgram {
  public boolean alwaysTrue() {
    return true;
  }

  @Test
  public void testAlwaysTrue() {
    assert(alwaysTrue() == true);
  }
}

Es un desincentivo pensar en pruebas unitarias que fracasarán. O, sin duda, realizar pruebas unitarias que serían difíciles de solucionar.

En ese caso, puede no ser una prueba unitaria, sino una prueba de integración si es complicada

Si en algún momento todas las pruebas unitarias pasan, entonces no hay una imagen general del estado del software en ningún momento. No hay hoja de ruta / objetivo.

Es cierto, se llama prueba unitaria por una razón, comprueba una pequeña unidad de código.

Disuade de escribir pruebas unitarias por adelantado, antes de la implementación.

Desarrolladores serádisuadir de escribir cualquier prueba si no entienden sus beneficiospor su naturaleza (a menos que provengan de QA)


“Los desarrolladores disuadirán [sic] de escribir cualquier prueba por su naturaleza”, lo cual es una tontería. Trabajo en una compañía completa de desarrolladores que practican TDD y BDD.
RubberDuck

@RubberDuck Traté de responder a un "hecho" en cuestión y estaba exagerando. Actualizaré
user7294900

"X será disuadido de hacer Y si no entienden los beneficios de Y" se aplica a casi cualquier X e Y, por lo que esa afirmación probablemente no sea particularmente útil. Probablemente tendría más sentido explicar los beneficios de escribir las pruebas, y específicamente hacerlo por adelantado.
Dukeling

2
"imposible para un programa de cualquier tamaño" no significa "todos los programas, sin importar su tamaño", significa "cualquier programa significativo (que tenga una longitud no trivial)" Su contraejemplo no es aplicable, porque no es t un programa significativo y útil.
Ben Voigt

@BenVoigt No creo que deba dar un "programa significativo" como respuesta.
user7294900

4

Promueve la idea de que el código debe ser perfecto y que no deben existir errores

Definitivamente no. Promueve la idea de que sus pruebas no deben fallar, nada más y nada menos. Asumir que tener pruebas (incluso muchas de ellas) dice algo sobre "perfecto" o "sin errores" es una falacia. Decidir cuán superficiales o profundas deben ser sus pruebas es una parte importante de escribir buenas pruebas, y la razón por la que tenemos categorías de pruebas distintivamente separadas (pruebas de "unidad", pruebas de integración, "escenarios" en el sentido del pepino, etc.).

Es un desincentivo pensar en pruebas unitarias que fracasarán. O, sin duda, realizar pruebas unitarias que serían difíciles de solucionar.

En el desarrollo impulsado por pruebas, es obligatorio que cada prueba unitaria falle primero, antes de comenzar a codificar. Se llama "ciclo rojo-verde" (o "ciclo rojo-verde-refactorizador") por esta misma razón.

  • Sin que falle la prueba, no sabe si el código es realmente probado por la prueba. Los dos podrían no estar relacionados en absoluto.
  • Al cambiar el código para precisamente hacer la vuelta de prueba de rojo a verde, nada más y nada menos, puede estar bastante seguro de que el código hace lo que se supone que debe hacer, y no mucho más (que nunca puede asistir).

Si en algún momento todas las pruebas unitarias pasan, entonces no hay una imagen general del estado del software en ningún momento. No hay hoja de ruta / objetivo.

Las pruebas son más un micro objetivo. En el desarrollo basado en pruebas, el programador escribirá primero una prueba (singular) y luego tendrá un objetivo claro para implementar algún código; luego la siguiente prueba, y así sucesivamente.

La función de las pruebas no es estar completa antes de que se escriba el código.

Cuando se hace correctamente, en un idioma y con una biblioteca de prueba que se adapta bien a este enfoque, esto puede acelerar el desarrollo de forma masiva, ya que los mensajes de error (excepciones / seguimientos de pila) pueden apuntar directamente al desarrollador hacia dónde necesita realizar el trabajo próximo.

Disuade de escribir pruebas unitarias por adelantado, antes de la implementación.

No veo cómo esta afirmación sería cierta. Lo ideal es que las pruebas de escritura sean parte de la implementación.

¿Me estoy perdiendo de algo? ¿Por qué las organizaciones esperan que todas las pruebas unitarias pasen?

Porque las organizaciones esperan que las pruebas tengan relevancia para el código. Escribir pruebas que tengan éxito significa que ha documentado alguna parte de su aplicación y ha demostrado que la aplicación hace lo que dice (la prueba). Nada más y nada menos.

Además, una parte muy importante de tener pruebas es la "regresión". Desea poder desarrollar o refactorizar un nuevo código con confianza. Tener una gran cantidad de pruebas verdes le permite hacer eso.

Esto va de nivel organizacional a psicológico. Un desarrollador que sabe que es muy probable que sus errores sean atrapados por las pruebas será mucho más libre de encontrar soluciones inteligentes y audaces para los problemas que necesita resolver. Por otro lado, un desarrollador que no tiene pruebas, después de un tiempo, se detendrá (debido al miedo) porque nunca sabe si un cambio que hace rompe el resto de la aplicación.

¿No es esto vivir en un mundo de sueños?

No. Trabajar con una aplicación basada en pruebas es pura alegría, a menos que simplemente no le guste el concepto por cualquier razón ("más esfuerzo", etc., etc.) que podemos discutir en otra pregunta.

¿Y no disuade realmente una comprensión real del código?

Absolutamente no, ¿por qué lo haría?

Encontrará muchos proyectos grandes de código abierto (para los cuales la gestión de la "comprensión" y el conocimiento sobre el código es un tema muy apremiante) que realmente utilizan las pruebas como la documentación principal del software, además de ser pruebas, también proporciona ejemplos reales, funcionales y sintácticamente correctos para usuarios o desarrolladores de la aplicación / biblioteca. Esto a menudo funciona espléndidamente.

Obviamente, escribir malas pruebas es malo. Pero eso no tiene nada que ver con la función de las pruebas per se.


3

(De mis comentarios originales)

Hay una diferencia entre la funcionalidad requerida y los objetivos futuros. Las pruebas son para la funcionalidad requerida: son precisas, formales, ejecutables y si fallan, el software no funciona. Los objetivos futuros pueden no ser precisos o formales, y mucho menos ejecutables, por lo que es mejor dejarlos en lenguaje natural, como rastreadores de problemas / errores, documentación, comentarios, etc.

Como ejercicio, intente reemplazar la frase "prueba unitaria" en su pregunta con "error del compilador" (o "error de sintaxis", si no hay compilador). Es obvio que una versión no debería tener errores de compilación, ya que sería inutilizable; Sin embargo, los errores de compilación y los errores de sintaxis son la situación normal en la máquina de un desarrollador cuando está escribiendo código. Los errores solo desaparecen cuando han terminado; y eso es exactamente cuando se debe empujar el código. Ahora reemplace "error del compilador" en este párrafo con "prueba de unidad" :)


2

El propósito de las pruebas automatizadas es informarle cuándo ha roto algo lo antes posible . El flujo de trabajo se parece un poco a esto:

  1. Hacer un cambio
  2. Cree y pruebe su cambio (idealmente automáticamente)
  3. Si las pruebas fallan, significa que rompiste algo que anteriormente funcionaba
  4. Si las pruebas pasan, debe estar seguro de que su cambio no introdujo nuevas regresiones (dependiendo de la cobertura de la prueba)

Si sus pruebas ya estaban fallando, entonces el paso 3 no funciona de manera tan efectiva: las pruebas fallarán, pero no sabe si eso significa que rompió algo o no sin investigar. Tal vez podría contar el número de pruebas fallidas, pero luego un cambio podría corregir un error y romper otro, o una prueba podría comenzar a fallar por una razón diferente. Esto significa que debe esperar una cierta cantidad de tiempo antes de saber si algo se ha roto, ya sea hasta que se hayan solucionado todos los problemas o hasta que se haya investigado cada prueba fallida.

La capacidad de las pruebas unitarias para encontrar errores recién introducidos lo antes posible es lo más valioso de las pruebas automatizadas: cuanto más tiempo se descubra un defecto, más costoso será repararlo.

Promueve la idea de que el código debe ser perfecto y que no deben existir errores
Es un desincentivo pensar en pruebas unitarias que fallarán

Las pruebas para las cosas que no funcionan no le dicen nada: escriba pruebas unitarias para las cosas que funcionan o que está a punto de arreglar. No significa que su software esté libre de defectos, significa que ninguno de los defectos para los que previamente escribió pruebas unitarias ha vuelto.

Disuade la escritura de pruebas unitarias por adelantado

Si funciona para usted, escriba pruebas por adelantado, simplemente no las registre en su maestro / troncal hasta que pasen.

Si en algún momento todas las pruebas unitarias pasan, entonces no hay una imagen general del estado del software en ningún momento. No hay hoja de ruta / objetivo.

Las pruebas unitarias no son para establecer una hoja de ruta / objetivo, ¿tal vez usar un trabajo de reserva para eso? Si todas sus pruebas pasan, entonces el "panorama general" es que su software no está dañado (si su cobertura de prueba es buena). ¡Bien hecho!


2

Las respuestas existentes son ciertamente buenas, pero no he visto a nadie abordar este error fundamental en la pregunta:

en cualquier momento todas las pruebas unitarias deben pasar

No. Lo más seguro es que esto no será cierto. Mientras desarrollo el software, NCrunch suele ser marrón (falla de compilación) o rojo (prueba fallida).

Cuando NCrunch necesita ser verde (todas las pruebas pasan) es cuando estoy listo para enviar un commit al servidor de control de origen, porque en ese momento otros pueden depender de mi código.

Esto también alimenta el tema de la creación de nuevas pruebas: las pruebas deben afirmar la lógica y el comportamiento del código. Condiciones de contorno, condiciones de falla, etc. Cuando escribo nuevas pruebas, trato de identificar estos "puntos calientes" en el código.

Las pruebas unitarias documentan cómo espero que se llame mi código: condiciones previas, salidas esperadas, etc.

Si una prueba se rompe después de un cambio, necesito decidir si el código o la prueba están en error.


Como nota al margen, las pruebas unitarias a veces van de la mano con Test Driven Development. Uno de los principios de TDD es que las pruebas rotas son su guía. Cuando una prueba falla, debe corregir el código para que pase la prueba. Aquí hay un ejemplo concreto de principios de esta semana:

Antecedentes : escribí y ahora soporto una biblioteca utilizada por nuestros desarrolladores que se utiliza para validar consultas de Oracle. Tuvimos pruebas que afirmaban que la consulta coincidía con algún valor esperado, lo que hacía que el caso fuera importante (no está en Oracle) y aprobaba alegremente las consultas no válidas siempre que coincidieran por completo con el valor esperado.

En cambio, mi biblioteca analiza la consulta usando Antlr y una sintaxis Oracle 12c, y luego envuelve varias afirmaciones en el árbol de sintaxis. Cosas como, es válido (no se generaron errores de análisis), la recopilación de parámetros satisface todos sus parámetros, todas las columnas esperadas leídas por el lector de datos están presentes en la consulta, etc. Todos estos son elementos que se han deslizado hasta producción en varios momentos.

Uno de mis colegas ingenieros me envió una consulta el lunes que había fallado (o mejor dicho, había tenido éxito cuando debería haber fallado) durante el fin de semana. Mi biblioteca dijo que la sintaxis estaba bien, pero explotó cuando el servidor intentó ejecutarla. Y cuando miró la consulta, era obvio por qué:

UPDATE my_table(
SET column_1 = 'MyValue'
WHERE id_column = 123;

Cargué el proyecto y agregué una prueba unitaria que afirmaba que esta consulta no debería ser válida. Obviamente, la prueba falló.

A continuación, depuré la prueba fallida, revisé el código donde esperaba que arrojara la excepción y descubrí que Antlr estaba generando un error en el par abierto, pero no de la manera que esperaba el código anterior. Modifiqué el código, verifiqué que la prueba ahora era verde (aprobada) y que ninguna otra había interrumpido el proceso, comprometido y empujado.

Esto tomó tal vez 20 minutos, y en el proceso en realidad mejoré significativamente la biblioteca porque ahora soportaba un rango completo de errores que anteriormente había estado ignorando. Si no tuviera pruebas unitarias para la biblioteca, investigar y solucionar el problema podría llevar horas.


0

Un punto que no creo que salga de las respuestas anteriores es que hay una diferencia entre las pruebas internas y las pruebas externas (y creo que muchos proyectos no son lo suficientemente cuidadosos como para distinguir los dos). Una prueba interna prueba que algún componente interno está funcionando como debería; Una prueba externa muestra que el sistema en su conjunto está funcionando como debería. Es bastante posible, por supuesto, tener fallas en los componentes que no resultan en una falla del sistema (tal vez haya una característica del componente que el sistema no usa, o tal vez el sistema se recupere de una falla del sistema). componente). Una falla de un componente que no resulte en una falla del sistema no debería impedir que se lance.

He visto proyectos que están paralizados por tener demasiadas pruebas de componentes internos. Cada vez que intenta e implementa una mejora de rendimiento, interrumpe docenas de pruebas, porque está cambiando el comportamiento de los componentes sin cambiar realmente el comportamiento del sistema visible desde el exterior. Esto lleva a una falta de agilidad en el proyecto en su conjunto. Creo que la inversión en pruebas de sistemas externos generalmente tiene una rentabilidad mucho mejor que la inversión en pruebas de componentes internos, especialmente cuando se trata de componentes de muy bajo nivel.

Cuando sugieres que las pruebas unitarias fallidas realmente no importan, me pregunto si esto es lo que tienes en mente. Tal vez debería evaluar el valor de las pruebas unitarias y deshacerse de las que causan más problemas de los que valen, mientras se concentra más en las pruebas que verifican el comportamiento visible desde el exterior de la aplicación.


Creo que lo que está describiendo como "pruebas externas" a menudo se describe en otras partes como pruebas de "integración".
GalacticCowboy

Sí, pero he encontrado diferencias en la terminología. Para algunas personas, las pruebas de integración tienen más que ver con la configuración de software / hardware / red implementada, mientras que estoy hablando del comportamiento externo de un software que está desarrollando.
Michael Kay

0

"pero en cualquier momento todas las pruebas unitarias deben pasar"

Si esa es la actitud en su empresa, eso es un problema. En CIERTO tiempo, es decir, cuando declaramos que el código está listo para pasar al siguiente entorno, todas las pruebas unitarias deben pasar. Pero durante el desarrollo, deberíamos esperar que muchas pruebas unitarias fallen.

Ninguna persona razonable espera que un programador perfeccione su trabajo en el primer intento. Lo que razonablemente esperamos es que siga trabajando en ello hasta que no haya problemas conocidos.

"Es un desincentivo pensar en pruebas unitarias que fracasarán. O, ciertamente, proponer pruebas unitarias que serían difíciles de solucionar". Si alguien en su organización piensa que no debería mencionar una posible prueba porque podría fallar y hacer que trabajen más para solucionarla, esa persona no está calificada para su trabajo. Esta es una actitud desastrosa. ¿Desearía un médico que diga: "Cuando estoy haciendo una cirugía, deliberadamente no verifico si los puntos son correctos, porque si veo que no lo son, tendré que volver y rehacerlos y eso se ralentizará terminando la operación "?

Si el equipo es hostil con los programadores que identifican errores antes de que el código pase a producción, tiene un problema real con la actitud de ese equipo. Si la administración castiga a los programadores que identifican errores que retrasan la entrega, lo más probable es que su empresa se dirija a la bancarrota.

Sí, es cierto que a veces las personas racionales dicen: "Nos estamos acercando a la fecha límite, este es un problema trivial y no vale la pena dedicar los recursos en este momento que se necesitaría para solucionarlo". Pero no puedes tomar esa decisión racionalmente si no lo sabes. Examinar fríamente una lista de errores y asignar prioridades y cronogramas para corregirlos es racional. Hacer que ignores los problemas deliberadamente para no tener que tomar esta decisión es una tontería. ¿Crees que el cliente no se enterará solo porque no querías saber?


-7

Este es un ejemplo específico de sesgo de confirmación , en el que las personas tienden a buscar información que confirme sus creencias existentes.

Un ejemplo famoso de esto ocurre en el juego 2,4,6.

  • Tengo una regla en mi cabeza de que cualquier serie de tres números pasará o fallará,
  • 2,4,6 es un pase
  • puede enumerar conjuntos de tres números, y le diré si pasan o fallan.

La mayoría de las personas elige una regla, dice "la brecha entre el 1er y el 2do número es la misma que la brecha entre el 2do y el 3er".

Probarán algunos números:

  • 4, 8, 12? Pasar
  • 20, 40, 60? Pasar
  • 2, 1004, 2006? Pasar

Dicen "Sí, cada observación confirma mi hipótesis, debe ser cierta". Y anuncie su regla a la persona que da el acertijo.

Pero nunca recibieron un solo 'fallo' en ningún conjunto de tres números. La regla podría haber sido 'los tres números deben ser números' para toda la información que realmente tienen.

La regla es en realidad que los números están en orden ascendente. Por lo general, las personas solo obtienen este acertijo correcto si prueban el fracaso. La mayoría de las personas se equivoca al elegir una regla más específica y solo prueba los números que cumplen con esta regla específica.

En cuanto a por qué las personas caen en el sesgo de confirmación, y pueden ver que las pruebas unitarias fallan como evidencia de un problema, hay muchos psicólogos que pueden explicar el sesgo de confirmación mejor que yo, básicamente se trata de que a las personas no les gusta estar equivocados y luchan por intentar realmente para demostrar que están equivocados.


2
¿Cómo es relevante para la pregunta? Las pruebas unitarias que fallan son evidencia de un problema, por definición.
Frax

1
Usted absolutamente puede tener pruebas de unidad que requieren el sistema que se está probando entrar en un modo de fallo. Eso no es lo mismo que nunca ver fallar una prueba. También es la razón por la que TDD se especifica como un ciclo "Rojo-> Verde-> Refactor"
Caleth
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.