¿Cuáles son las desventajas de escribir código antes de escribir pruebas unitarias?

33

Siempre he visto la recomendación de que primero deberíamos escribir pruebas unitarias y luego comenzar a escribir código. Pero siento que ir hacia el otro lado es mucho más cómodo (para mí): escribir código y luego las pruebas unitarias, porque siento que tenemos mucha más claridad después de haber escrito el código real. Si escribo el código y luego las pruebas, es posible que tenga que cambiar mi código un poco para que sea comprobable, incluso si me concentro mucho en crear un diseño comprobable. Por otro lado, si escribo las pruebas y luego el código, las pruebas cambiarán con bastante frecuencia a medida que el código se forma.

Como veo muchas recomendaciones para comenzar a escribir pruebas y luego pasar a la codificación, ¿cuáles son las desventajas si lo hago de otra manera: escribir código y luego las pruebas unitarias?

k25
fuente
77
+1 por preguntar por qué cierta práctica es una "mejor práctica" antes de
aceptarla

Respuestas:

37

El rojo es la respuesta. Rojo es lo que obtienes del ciclo refactor rojo-verde de TDD que no puedes obtener, prueba al final. Primero, escribe una prueba reprobatoria. Míralo fallar. Ese es tu rojo, y es importante. Dice: Tengo este requisito y sé que mi código no lo cumple. Entonces, cuando va al paso 2 (verde), sabe, con la misma certeza, que su código ahora cumple con ese requisito. Sabe que ha cambiado su base de código de tal manera que satisfaga el requisito.

Los requisitos (pruebas) desarrollados después del código, basados ​​en el código, lo privan de ese tipo de certeza, esa confianza.

Carl Manaster
fuente
+1 - excelente punto y punto tomado! ¡Gracias por su opinión!
k25
77
Pruebas! = Requisitos. Tanto las pruebas como el código deben derivarse de los requisitos.
Bart van Ingen Schenau
2
@Bart van Ingen Schenau: La fuerza de TDD es precisamente que prueba los requisitos de ARE. Además, son requisitos ejecutables.
Mouviciel
1
@Bart: Las pruebas unitarias a menudo son demasiado detalladas para los requisitos del cliente (de alto nivel), pero la idea definitivamente es válida, especialmente si también consideramos las pruebas de nivel superior, como las pruebas de aceptación automatizadas que, una vez escritas, deberían ser requisitos definitivos. Esa es la esencia del "test de aceptación ágil".
Martin Wickman
3
TDD no se trata de pruebas, se trata de especificaciones. Las pruebas integradas en un enfoque TDD son un medio de comunicación entre el desarrollador y el cliente para acordar qué producto se debe hacer.
Mouviciel
18

Si escribe el código, y luego las pruebas, es demasiado fácil caer en la trampa de escribir las pruebas para que el código pase, en lugar de escribir las pruebas para garantizar que el código cumpla con la especificación.

Dicho esto, definitivamente no es la única forma de hacer las cosas, y no hay una "mejor" forma de desarrollar software. Si pone mucho trabajo por adelantado en el desarrollo de casos de prueba, no sabrá si su arquitectura propuesta tiene algún defecto hasta mucho después, mientras que si desarrolló el código primero, se encontrará con ellos antes y podrá rediseñarlos con menos hundimiento. esfuerzo.

Luego.
fuente
Sí, tienes razón sobre el primer punto, pero siempre me aseguro de no hacerlo. Si la prueba falla, siempre voy al código y me aseguro de que esté correcto y luego veo si mi prueba es correcta, y luego modifico lo que sea incorrecto. Gracias por su opinión, tendré esto en mente .. +1 de mí para el primer punto y el último punto ...
k25
2
¿Pero qué pasa si la prueba pasa? La prueba puede pasar porque en realidad no está ejerciendo el código de interés; eso no puede suceder realmente bajo TDD, porque se supone que la prueba falla inicialmente, y lo hace; si no lo hace, no continúe con el paso 2 hasta que lo haya solucionado. Entonces, hay un modo de falla en la última prueba que no existe en la prueba primero.
Carl Manaster
@Carl Manaster - Sí, tienes un punto válido. Después de escribir el código, soy perfectamente consciente de los requisitos y, por lo tanto, mi caso de prueba de unidad sería correcto (idealmente). Si mi caso de prueba pasa, diría que el código es correcto, si la prueba falla, seguiré lo que dije. Pero, estoy 100% de acuerdo en que tienes un punto válido allí.
k25
@ k25: El punto es que si su caso de prueba pasa, aún no sabe si el código es correcto o no. El caso de prueba podría estar equivocado.
Anon
@Luego. - Sí, tienes razón, tomaré este caso también en cuenta.
k25
12

En realidad, las personas se obsesionan con TDD se trata de pruebas, aunque se olvidan de las otras dos letras en el acrónimo. Algo que se puede leer aquí: TDD sin T o TDD no se trata de pruebas .

La cosa es que he aprendido una gran cantidad de otras cosas que están estrechamente unidas con TDD. No importa si prueba primero: lo que importa es pensar en el diseño de software .

Para poder incluso escribir pruebas unitarias "de la manera correcta", es decir, para que estén aisladas, sean rápidas y automatizadas, notará que se necesita un replanteamiento acerca de cómo organizar su código de una manera que sea más fácil. Probar.

Personalmente, aprendí los principios SÓLIDOS sin saber que había tal cosa escrita. Esto se debe a que escribir pruebas unitarias me obligó a reescribir clases para que no se vuelvan demasiado complejas para probar. Condujo a cosas como:

  • Tuve que mover la funcionalidad, que no tenía sentido o residía en métodos privados, a clases separadas para poder probarlas por separado. (Principio de responsabilidad única).
  • Tuve que evitar grandes estructuras de herencia y extender implementaciones con composición en su lugar (prominente en principio abierto-cerrado).
  • Tenía que ser inteligente sobre la herencia, usaba clases abstractas cada vez que veía un código común que podía compartirse y usaba métodos de código auxiliar (Principio de sustitución de Liskov).
  • Tuve que escribir interfaces y clases abstractas para poder probar las clases por separado. Lo que sin darse cuenta te lleva a escribir objetos simulados. (Principio de segregación de interfaz)
  • Debido a que escribí muchas interfaces y clases abstractas, comencé a declarar variables y parámetros para usar el tipo común (principio de inversión de dependencia).

Aunque no hago primero las pruebas todo el tiempo, sí sigo los buenos principios y prácticas de OO que empiezas a seguir, solo para facilitar un poco las pruebas. Ahora no estoy escribiendo código por sí mismo. Escribí el código para que pueda probarse fácilmente o, lo que es más importante; De fácil mantenimiento .

Spoike
fuente
1
+1 para SOLID se te ocurre naturalmente cuando piensas en el diseño de software.
ocodo
+1 (en realidad quería dar +10 pero no puedo). Exactamente mis pensamientos: tu lista de puntos fue extremadamente buena. Esa es una razón por la que hice esta pregunta. Sentí que las clases se hicieron mucho más cuando comencé a escribir las pruebas unitarias después de escribir el código. Pero quería ver las ventajas / desventajas de ambos lados, ¡gracias por sus opiniones!
k25
10

Todas las otras respuestas son buenas, pero hay un punto que no se mencionó. Si escribe la prueba primero, se asegura de que las pruebas se escriban. Es tentador, una vez que ha escrito el código de trabajo, omitir las pruebas y simplemente verificarlo a través de la interfaz de usuario. Si tiene la disciplina de tener siempre una prueba fallida antes de escribir el código, puede evitar esta trampa.

RationalGeek
fuente
4

Si escribe sus pruebas primero, le da otra oportunidad de pensar en su diseño, antes de que ese diseño se "eche en piedra".

Por ejemplo, puede pensar que necesita un método que tome un determinado conjunto de parámetros. Y si escribió el código primero, lo escribiría de esa manera y haría que la prueba se ajustara a los parámetros especificados. Pero si escribe la prueba primero, puede pensar "espere un minuto, no me gustaría usar este parámetro en el código de la línea principal, así que tal vez debería cambiar la API".

Luego
fuente
+1 para el primer punto. Pero, al no llegar al nivel de parámetros, ¿qué pasa si el diseño se discutió con otros y se aceptó?
k25
@ k25: si algo es difícil de usar según lo diseñado, necesita más reflexión. A veces, muy raramente, es solo una tarea difícil. Pero más a menudo, se puede reducir a tareas más simples. No tengo un enlace, pero Gosling o Goetz hicieron una entrevista sobre el diseño de API hace unos años ... vale la pena buscar en Google.
Anon
Seguro, gracias por los consejos, que sin duda mirar a ellos ...
k25
2

Como veo muchas recomendaciones para comenzar a escribir pruebas y luego pasar a la codificación,

Hay una muy buena razón para esto.

Si dices "haz lo que se siente bien", la gente hace las cosas más tontas y locas.

Si dices "escribe las pruebas primero", al menos las personas podrían intentar hacer lo correcto.

¿Cuáles son las desventajas si lo hago de otra manera: escribir código y luego las pruebas unitarias?

Por lo general, una prueba pésima y un diseño que tiene que ser modificado para ser comprobable.

Sin embargo, eso es solo un "por lo general". Algunas personas desarrollan los diseños y las pruebas en paralelo. Algunas personas implementan códigos comprobables y escriben pruebas sin reprocesos.

La regla "Prueba primero" está específicamente allí para enseñar e instruir a las personas que no tienen ni idea.

De manera similar, se nos dice que siempre miremos "en ambos sentidos" antes de cruzar la calle. Sin embargo, en realidad no lo hacemos. Y no importa Vivo en un país con volante a la derecha y solo necesito mirar a la izquierda al comenzar a cruzar.

Cuando visito un país con el volante a la izquierda, mirar hacia la izquierda solo podría hacer que me maten.

Las reglas se establecen muy fuertemente por una razón.

Lo que haces es tu propio problema.

S.Lott
fuente
2

el punto de escribir la prueba primero es que te hace pensar en

  • como probar el código
  • la interfaz que debe presentar el código para que sea comprobable

si está haciendo algo simple, probablemente no importa cuál escriba primero (aunque es bueno cultivar el hábito de probar primero) ya que la prueba será simple y la interfaz será obvia

pero TDD se amplía a pruebas de aceptación, no solo pruebas unitarias, y luego la interfaz deja de ser trivial.

Steven A. Lowe
fuente
1

Primero, si no escribe sus pruebas primero, entonces no está haciendo Test Driven Development (TDD). Los beneficios son numerosos y, a menudo, difíciles de creer hasta que lo practique varias veces. Estos son los beneficios que he recibido haciendo TDD sobre el desarrollo tradicional:

  1. Una red de seguridad de pruebas: le permite realizar grandes cambios sin temor a romper algo sin saberlo
  2. Diseño orgánico: el diseño con el que termino suele ser diferente al diseño que habría hecho desde cero y siempre ha sido mejor
  3. Productividad: trabajar hacia objetivos pequeños (pasar esta prueba) y lograrlo (pasar todas las pruebas) me funciona muy bien y me mantiene motivado. Agregue un par y mi productividad alcanza nuevos máximos.

Libros: Beck, K. Test-Driven Development by Example

Buen ejemplo: http://jamesshore.com/Blog/Lets-Play/

Mike Polen
fuente
+1 - buenos puntos (especialmente el primero) y gracias por los enlaces!
k25
0

Cuando escribe una prueba, ¿cómo sabe que detectará una condición de falla? La respuesta es "prueba la prueba". Lo que debe hacer es escribir primero la prueba, verla fallar y solo verla pasar cuando la unidad bajo prueba se haya codificado con éxito (el ciclo rojo / verde / refactor mencionado en una de las otras respuestas).

Escribir el código primero y luego la prueba deja abierta la pregunta de si la prueba mostraría un fallo honesto.

Recuerde que sus pruebas expresan especificación. Si tiene que revisar sus pruebas a medida que su código se "forma", sugiere que sus especificaciones están cambiando. Eso puede o no ser algo bueno. Podría significar que su comprensión del problema no fue inicialmente correcta. Por otro lado, podría significar que está probando "cómo" la unidad está haciendo su trabajo en lugar de lo que se supone que está logrando.

Zenilogix
fuente