¿Por qué no escribir todas las pruebas a la vez al hacer TDD?

55

El ciclo Rojo - Verde - Refactor para TDD está bien establecido y aceptado. Escribimos una prueba de unidad que falla y la hacemos pasar de la manera más simple posible. ¿Cuáles son los beneficios de este enfoque sobre escribir muchas pruebas unitarias reprobadas para una clase y hacer que todas pasen de una vez?

El conjunto de pruebas aún lo protege contra escribir código incorrecto o cometer errores en la etapa de refactorización, entonces, ¿cuál es el daño? A veces es más fácil escribir todas las pruebas para una clase (o módulo) primero como una forma de 'descarga de cerebro' para escribir rápidamente todo el comportamiento esperado de una sola vez.

RichK
fuente
20
Haz lo que funcione mejor para ti (después de experimentar un poco). Seguir ciegamente el dogma nunca es algo bueno.
Michael Borgwardt
66
Me atrevo a decir que escribir todas sus pruebas a la vez es como escribir todo el código de su aplicación a la vez.
Michael Haren
1
@MichaelHaren Todas las pruebas para una clase (o módulo funcional), perdón por la confusión
RichK
3
Abordar el problema del "volcado del cerebro": a veces hay puntos en la prueba / codificación cuando te das cuenta de la necesidad de varias pruebas de entrada específicas diferentes, y hay una tendencia a querer capitalizar la claridad de esa realización antes de distraerte con el minucias de la codificación. Por lo general, lo manejo manteniendo una lista separada (por ejemplo, Mylyn), o bien con una lista de comentarios en la clase Prueba de diferentes cosas que quiero recordar probar (por ejemplo, // prueba de caso nulo). Sin embargo, todavía solo codifico una prueba a la vez, y en su lugar trabajo en la lista sistemáticamente.
Sam Goldberg
1
bueno, no sé por qué nadie mencionó esto, pero NO PUEDES escribir todas las pruebas a la vez. Escribir todas las pruebas de antemano es exactamente lo mismo que hacer BDUF . ¿Y qué nos ha enseñado la historia sobre BDUF? Es casi nunca funciona.
Songo

Respuestas:

50

El diseño basado en pruebas se trata de hacer que su API sea correcta, no el código.

El beneficio de escribir primero las pruebas de fallas más simples es que obtienes tu API (que esencialmente es que estás diseñando sobre la marcha) lo más simple posible. En la delantera.

Cualquier uso futuro (que son las próximas pruebas que escriba) irá desde el diseño simple inicial, en lugar de un diseño subóptimo frente a casos más complejos.

usuario1249
fuente
Excelentes puntos! A veces estamos tan inmersos en probar el código que a veces ignoramos lo importante que puede ser la API y el modelo de dominio incluso antes de escribir su primera prueba.
maple_shaft
+1 para abordar realmente la intención del desarrollo impulsado por pruebas .
Joshua Drake
76

Cuando escribes una prueba, te concentras en una cosa.
Con muchas pruebas, concentra su atención en muchas tareas, por lo que no es una buena idea.

Abyx
fuente
8
¿Quién votaría en contra de esto?
CaffGeek
66
@Chad Yo no estaba en la votación negativa, pero creo que esta respuesta pasa por alto lo obvio. El desarrollo Test Driven se trata de usar las Pruebas para conducir el diseño del código. Usted escribe la prueba individualmente para evolucionar el diseño, no solo por la capacidad de prueba. Si solo se tratara de los artefactos de la prueba, esta sería una buena respuesta, pero como le falta información crucial.
Joshua Drake
77
No desestimé esto pero; He pensado en ello. Es una respuesta demasiado breve para una pregunta compleja.
Mark Weston el
2
+1 por concentrarse en una cosa a la vez, nuestra capacidad de realizar múltiples tareas está sobrevalorada.
cctan
Es la respuesta más simple que podría funcionar.
ADN
27

Una de las dificultades al escribir pruebas unitarias es que está escribiendo código, y eso en sí mismo puede ser propenso a errores. También existe la posibilidad de que termine necesitando cambiar sus pruebas más adelante como resultado de un esfuerzo de refactorización a medida que escribe su código de implementación. Con TDD, esto significa que podría terminar llevándose un poco demasiado lejos con sus pruebas y encontrando que necesita reescribir una gran cantidad de código de prueba esencialmente "no probado" a medida que su implementación madura en el transcurso del proyecto. Una forma de evitar este tipo de problema es simplemente enfocarse en hacer una sola cosa a la vez. Esto asegura que minimice el impacto de cualquier cambio en sus pruebas.

Por supuesto, esto se reducirá en gran medida a cómo escribir su código de prueba. ¿Está escribiendo una prueba unitaria para cada método individual, o está escribiendo pruebas que se centran en características / requisitos / comportamientos? Otro enfoque podría ser utilizar un enfoque basado en el comportamiento con un marco adecuado y centrarse en escribir pruebas como si fueran especificaciones. Esto significaría adoptar el método BDD o adaptar las pruebas BDD si desea seguir con TDD más formalmente. Alternativamente, puede seguir completamente con el paradigma TDD, pero alterar la forma en que escribe las pruebas para que, en lugar de centrarse por completo en los métodos de prueba individualmente, pruebe los comportamientos de manera más general como un medio para satisfacer los detalles de las características de requisitos que está implementando.

Independientemente del enfoque específico que adopte, en todos los casos que he descrito anteriormente, está utilizando un enfoque de prueba primero, por lo que si bien puede ser tentador simplemente descargar su cerebro en un conjunto de pruebas encantador, también desea luchar contra el La tentación de hacer más de lo absolutamente necesario. Cada vez que estoy a punto de comenzar un nuevo conjunto de pruebas, comienzo a repetirme YAGNI y, a veces, incluso lo incluyo en un comentario en mi código para recordarme que me mantenga enfocado en lo que es inmediatamente importante y que solo haga el mínimo requerido para satisfacer el requisitos de la función que estoy a punto de implementar. Cumplir con Red-Green-Refactor ayuda a garantizar que lo haga.

revs S.Robins
fuente
Me alegra que haya señalado la distinción entre cómo se escribe su código de prueba. A algunos les gusta escribir una sola prueba de unidad maestra que cubra todas las posibilidades realistas de entradas a una sola función o método. Otros toman un enfoque más BDD con sus pruebas unitarias. Esta distinción es importante para determinar si escribir un conjunto completo de pruebas es necesariamente una mala práctica o no. Gran idea!
maple_shaft
17

Creo que al hacer esto, se pierde el proceso de TDD. Simplemente escribiendo todas sus pruebas al principio, realmente no está pasando por el proceso de desarrollo utilizando TDD. Simplemente está adivinando por adelantado qué pruebas necesitará. Este será un conjunto de pruebas muy diferente de las que terminas escribiendo si las haces una a la vez a medida que desarrollas tu código. (A menos que su programa sea trivial por naturaleza).

ZweiBlumen
fuente
1
La mayoría de las aplicaciones empresariales y empresariales son técnicamente triviales por naturaleza y, dado que la mayoría de las aplicaciones son empresariales y empresariales, la mayoría de las aplicaciones también son triviales por naturaleza.
maple_shaft
55
@maple_shaft: la tecnología puede ser trivial, pero las reglas comerciales no lo son. Pruebe y cree una aplicación para 5 gerentes, todos los cuales tienen diferentes requisitos y se niegan a escuchar algunas BS sobre su diseño simplista, elegante, menos es más, minimalismo.
JeffO
55
@JeffO 1) No es BS. 2) Un diseño elegante y minimalista requiere buenas habilidades de desarrollo de software. 3) La capacidad de mitigar los requisitos de 5 gerentes diferentes que no tienen más de 5 minutos a la semana para desperdiciar con usted y aún así lograr un diseño minimalista requiere un excelente desarrollador de software. Consejo profesional: el desarrollo de software es más que solo habilidades de codificación, es negociación, conversación y apropiación. Tienes que ser un perro Alfa y morder a veces.
maple_shaft
1
Si entiendo correctamente, esta respuesta está planteando la pregunta.
Konrad Rudolph el
1
@maple_shaft Creo que a eso se refería Jeff O con su comentario, ¿no?
ZweiBlumen
10

Sí "escribo" todas las pruebas que puedo pensar por adelantado mientras hago "tormenta de ideas", sin embargo, escribo cada prueba como un solo comentario que describe la prueba.

Luego convierto una prueba a código y hago el trabajo para que se compile y pase . A menudo decido que no necesito todas las pruebas que pensé que necesitaba, o necesito diferentes pruebas, esta información solo proviene de escribir el código para que las pruebas pasen.

El problema es que no puede escribir una prueba en el código hasta que haya creado el método y las clases que prueba, de lo contrario solo obtendrá muchos errores del compilador que le ayudarán a trabajar en una sola prueba a la vez.

Ahora, si está utilizando un sistema como flujo de especificaciones cuando las pruebas están escritas en "inglés", es posible que desee que los clientes acepten un conjunto de pruebas mientras tiene su tiempo, en lugar de crear una sola prueba.

Ian
fuente
1
Sí, aunque estoy de acuerdo con las respuestas anteriores que señalan los problemas con la codificación de todas sus pruebas primero, me resulta muy útil volcar mi comprensión general de cómo debe comportarse el método actual como un conjunto de descripciones de prueba sin ningún código. El proceso de escribir esto tiende a aclarar si entiendo completamente lo que se requiere del código que estoy a punto de escribir, y si hay casos extremos en los que no he pensado. Me siento mucho más cómodo codificando la primera prueba y luego aprobándola después de describir mi "visión general" de cómo debería funcionar el método.
Mark Weston el
10

La idea detrás de TDD es iteraciones rápidas.

Si tiene grandes extensiones de pruebas que deben escribirse antes de tener que escribir su código, es difícil refactorizarlo de forma iterativa.

Sin una fácil refactorización del código, pierde muchos de los beneficios de TDD.

linkerro
fuente
5

En mi experiencia (limitada) con TDD, puedo decirle que cada vez que he roto la disciplina de escribir un examen a la vez, las cosas han ido mal. Es una trampa fácil para caer. "Oh, ese método es trivial", piensas para ti mismo, "así que simplemente eliminaré estas otras dos pruebas relacionadas y seguiré adelante". ¿Bien adivina que? Nada es tan trivial como parece. Cada vez que caí en esta trampa, terminé depurando algo que pensé que era fácil, pero resultó tener extraños casos de esquina. Y desde que escribí varias pruebas a la vez, fue mucho trabajo rastrear dónde estaba el error.

Si necesita un volcado de información del cerebro, tiene muchas opciones:

  • Pizarra
  • Historias del usuario
  • Comentarios
  • Buena pluma y papel

Tenga en cuenta que en ninguna parte de esta lista está el compilador. :-)

Kristo
fuente
5

Está asumiendo que sabe cómo se verá su código antes de escribirlo. TDD / BDD es tanto un proceso de diseño / descubrimiento como un proceso de control de calidad. Para una característica dada, usted escribe la prueba más simple que verificaría que la característica está satisfecha (a veces esto puede requerir varias debido a la complejidad de una característica). La primera prueba que escriba está cargada de suposiciones de cómo se verá el código de trabajo. Si escribe todo el conjunto de pruebas antes de escribir la primera línea de código para admitirlo, está haciendo una gran cantidad de suposiciones no verificadas. En cambio, escriba una suposición y verifíquela. Luego escribe el siguiente. En el proceso de verificación de la siguiente suposición, es posible que rompa una suposición anterior para tener que retroceder y cambiar esa primera suposición para que coincida con la realidad o cambiar la realidad para que esa primera suposición aún se aplique.

Piense en cada prueba unitaria que escriba como una teoría en un cuaderno científico. A medida que llena el cuaderno, prueba sus teorías y forma otras nuevas. A veces, probar una nueva teoría refuta una teoría anterior, por lo que debe corregirla. Es más fácil demostrar una teoría a la vez en lugar de intentar decir 20 a la vez.

Michael Brown
fuente
TDD asume que usted sabe cómo se verá su código antes de escribirlo, solo en partes más pequeñas.
Michael Shaw
4

TDD es un enfoque altamente iterativo, que (en mi experiencia) se adapta mejor a las formas de desarrollo del mundo real. Por lo general, mi implementación toma forma gradualmente durante este proceso, y cada paso puede traer más preguntas, ideas e ideas para la prueba. Esto es ideal para mantener mi mente enfocada en la tarea real, y es muy eficiente porque solo necesito mantener un número limitado de cosas en la memoria a corto plazo en cualquier momento. Esto a su vez reduce la posibilidad de errores.

Su idea es básicamente un enfoque Big Test Up Front, que en mi humilde opinión es más difícil de manejar y puede ser más derrochador. ¿Qué sucede si se da cuenta a mitad de su trabajo de que su enfoque no es bueno, su API es defectuosa y necesita comenzar de nuevo, o usar una biblioteca de terceros? Entonces, una gran parte del trabajo realizado al escribir sus exámenes por adelantado se convierte en un esfuerzo inútil.

Dicho esto, si esto funciona para ti, está bien. Me imagino que si trabaja desde una especificación técnica fija y detallada, en un dominio con el que tiene experiencia íntima y / o en una tarea bastante pequeña, puede tener la mayoría o todos los casos de prueba necesarios listos y su implementación clara desde el comienzo. Entonces podría tener sentido comenzar escribiendo todas las pruebas a la vez. Si su experiencia es que esto lo hace más productivo a largo plazo, no necesita preocuparse demasiado por los libros de reglas :-)

Péter Török
fuente
4

Más allá de solo pensar en una cosa, un paradigma de TDD es escribir el menor código posible para pasar la prueba. Cuando escribe una prueba a la vez, es mucho más fácil ver la ruta para escribir el código suficiente para pasar esa prueba. Con un conjunto completo de pruebas para pasar, no se llega al código en pequeños pasos, sino que se debe dar un gran salto para que todos pasen de una vez.

Ahora, si no se limita a escribir el código para que todos pasen "de una vez", sino que escriba el código suficiente para pasar una prueba a la vez, aún podría funcionar. Sin embargo, tendría que tener más disciplina para no solo seguir adelante y escribir más código del que necesita. Una vez que comienzas por ese camino, te dejas abierto a escribir más código del que describen las pruebas, lo que puede no probarse , al menos en el sentido de que no es conducido por una prueba y tal vez en el sentido de que no es necesario (o ejercido) por cualquier prueba.

Entender qué debe hacer el método, como comentarios, historias, una especificación funcional, etc., es perfectamente aceptable. Sin embargo, esperaría traducir esto en pruebas de una en una.

La otra cosa que puede perderse al escribir las pruebas de una vez es el proceso de pensamiento por el cual pasar una prueba puede incitarlo a pensar en otros casos de prueba. Sin un banco de pruebas existentes, debe pensar en el próximo caso de prueba en el contexto de la última prueba aprobada. Como dije, tener una buena idea de lo que se supone que debe hacer el método es muy bueno, pero muchas veces me he encontrado encontrando nuevas posibilidades que no había considerado a priori, pero que solo ocurrieron en el proceso de escribir el pruebas Existe el peligro de que pueda omitirlos a menos que tenga el hábito específico de pensar qué pruebas nuevas puedo escribir que aún no tengo.

tvanfosson
fuente
3

Trabajé en un proyecto donde los desarrolladores que escribieron las pruebas (fallidas) eran diferentes de los desarrolladores que implementaron el código necesario para hacerlos pasar y lo encontré realmente efectivo.

En ese caso, solo las pruebas relacionadas con la iteración actual se escribieron una vez. Entonces, lo que sugieres es perfectamente posible en ese tipo de escenario.

usuario2567
fuente
2
  • Luego intentas concentrarte en demasiadas cosas a la vez.
  • Mientras se implementa para hacer pasar todas las pruebas, no tiene una versión funcional de su aplicación. Si tiene que implementar mucho, no tendrá una versión funcional durante mucho tiempo.
magomi
fuente
2

El ciclo Rojo-Verde-Refactor es una lista de verificación destinada a desarrolladores nuevos en TDD. Diría que es una buena idea seguir esta lista de verificación hasta que sepa cuándo seguirla y cuándo puede romperla (es decir, hasta que sepa no tiene que hacer esta pregunta en stackoverflow :)

Después de haber realizado TDD durante casi una década, puedo decirle que rara vez, si alguna vez, escribo muchas pruebas fallidas antes de escribir el código de producción.

Torbjörn Kalin
fuente
1

Está describiendo BDD, donde algún interesado externo tiene una especificación ejecutable. Esto puede ser beneficioso si hay una especificación inicial predeterminada (por ejemplo, una especificación de formato, estándar industrial o donde el programador no es el experto en el dominio).

El enfoque normal es cubrir gradualmente más y más pruebas de aceptación, que es el progreso visible para el gerente del proyecto y el cliente.

Por lo general, tiene estas pruebas especificadas y ejecutadas en un marco BDD como Cucumber, Fitnesse o algo así.

Sin embargo, esto no es algo que mezcle con sus pruebas unitarias, que están mucho más cerca de los detalles de implementación esenciales con una gran cantidad de casos límite relacionados con API, problemas de inicialización, etc., muy centrados en el elemento bajo prueba , que es un artefacto de implementación .

La disciplina del refactor rojo-verde tiene muchos beneficios, y la única ventaja que puede esperar al escribirlos desde el principio es alcanzar el punto de equilibrio.

Tormod
fuente
1

Una prueba a la vez: la principal ventaja es centrarse en una cosa. Piense en un diseño profundo: puede profundizar y mantenerse enfocado con un ciclo de retroalimentación rápido. ¡Sin embargo, puede perder el alcance de todo el problema! Ese es el momento en que entra en juego la refactorización (grande). Sin ella, TDD no funciona.

Todas las pruebas: el análisis y el diseño pueden revelarle más sobre el alcance del problema. Piense en un diseño amplio primero. Usted analiza el problema desde más ángulos y agrega información de la experiencia. Es intrínsecamente más difícil, pero puede generar beneficios interesantes, menos refactorización, si hace 'lo suficiente'. ¡Cuidado, es fácil analizar en exceso y, sin embargo, errar por completo!

En general, me resulta difícil recomendar uno u otro, porque los factores son muchos: experiencia (especialmente con el mismo problema), conocimiento y habilidades de dominio, amabilidad del código para la refactorización, complejidad del problema ...

Supongo que si nos enfocamos más estrechamente en las aplicaciones comerciales típicas, entonces TDD con su enfoque rápido de prueba y error generalmente ganaría en términos de efectividad.

Mar
fuente
1

Suponiendo que su marco de prueba lo admite, lo que sugeriría es que, en lugar de implementar las pruebas que desea realizar braindump, escriba pruebas pendientes descriptivas que luego implementará. Por ejemplo, si su API debe hacer foo and bar pero no biz, simplemente agregue el siguiente código (este ejemplo está en rspec) para su conjunto de pruebas, luego atacarlos uno por uno. Tienes tus pensamientos rápidamente y puedes abordar todos tus problemas uno por uno. Cuando pasen todas las pruebas, sabrá cuándo ha abordado todos los problemas que tuvo durante su braindump.

describe "Your API" do

  it "should foo" do
    pending "braindump from 4/2"
  end

  it "should bar" do
    pending "braindump from 4/2"
  end

  it "should not biz" do
    pending "braindump from 4/2"
  end

end
Rescate Briggs
fuente