¿Cuál es el mejor curso de acción en TDD si, después de implementar la lógica correctamente, la prueba todavía falla (porque hay un error en la prueba)?
Por ejemplo, suponga que desea desarrollar la siguiente función:
int add(int a, int b) {
return a + b;
}
Supongamos que lo desarrollamos en los siguientes pasos:
Prueba de escritura (aún no funciona):
// test1 Assert.assertEquals(5, add(2, 3));
Resultados en el error de compilación.
Escriba una implementación de función ficticia:
int add(int a, int b) { return 5; }
Resultado:
test1
pases.Agregue otro caso de prueba:
// test2 -- notice the wrong expected value (should be 11)! Assert.assertEquals(12, add(5, 6));
Resultado:
test2
falla,test1
aún pasa.Escribir implementación real:
int add(int a, int b) { return a + b; }
Resultado:
test1
todavía pasa,test2
todavía falla (desde11 != 12
).
En este caso particular: ¿sería mejor:
- correcto
test2
, y ver que ahora pasa, o - elimine la nueva parte de la implementación (es decir, regrese al paso 2 anterior), corríjala
test2
y deje que falle, y luego reintroduzca la implementación correcta (paso 4 anterior).
¿O hay alguna otra forma más inteligente?
Si bien entiendo que el problema del ejemplo es bastante trivial, estoy interesado en qué hacer en el caso genérico, que podría ser más complejo que la suma de dos números.
EDITAR (en respuesta a la respuesta de @Thomas Junk):
El foco de esta pregunta es lo que TDD sugiere en tal caso, no lo que es "la mejor práctica universal" para lograr un buen código o pruebas (que podrían ser diferentes a la forma TDD).
Respuestas:
Lo absolutamente crítico es que vea que la prueba pasa y falla.
Ya sea que elimine el código para que la prueba falle, luego vuelva a escribir el código o lo escabulle al portapapeles solo para pegarlo más tarde, no importa. TDD nunca dijo que tenías que volver a escribir nada. Quiere saber que la prueba pasa solo cuando debe pasar y falla solo cuando debe fallar.
Ver la prueba pasar y fallar es cómo prueba la prueba. Nunca confíes en una prueba que nunca has visto hacer ambas cosas.
Refactoring Against The Red Bar nos da pasos formales para refactorizar una prueba de trabajo:
Sin embargo, no estamos refactorizando una prueba de trabajo. Tenemos que transformar una prueba de errores. Una preocupación es el código que se introdujo mientras solo esta prueba lo cubría. Dicho código debe revertirse y reintroducirse una vez que se repara la prueba.
Si ese no es el caso, y la cobertura del código no es una preocupación debido a otras pruebas que cubren el código, puede transformar la prueba e introducirla como una prueba verde.
Aquí, el código también se revierte, pero lo suficiente como para hacer que la prueba falle. Si eso no es suficiente para cubrir todo el código introducido mientras solo está cubierto por la prueba de errores, necesitamos una mayor reversión del código y más pruebas.
Introducir una prueba verde
Romper el código puede ser comentar el código o moverlo a otro lugar solo para pegarlo más tarde. Esto nos muestra el alcance del código que cubre la prueba.
Para estas dos últimas carreras, estás de vuelta en el ciclo rojo verde normal. Simplemente está pegando en lugar de escribir para descifrar el código y hacer que la prueba pase. Por lo tanto, asegúrese de pegar solo lo suficiente para pasar la prueba.
El patrón general aquí es ver cómo cambia el color de la prueba de la manera que esperamos. Tenga en cuenta que esto crea una situación en la que brevemente tiene una prueba verde no confiable. Tenga cuidado de que lo interrumpan y olvide dónde se encuentra en estos pasos.
Mi agradecimiento a RubberDuck por el enlace Abrazando la barra roja .
fuente
¿Cuál es el objetivo general que quieres lograr?
Haciendo buenas pruebas?
¿Hacer la implementación correcta ?
¿Hacer TTD religiosamente correcto ?
¿Ninguna de las anteriores?
Quizás pienses demasiado en tu relación con las pruebas y las pruebas.
Las pruebas no garantizan la corrección de una implementación. Tener todas las pruebas aprobadas no dice nada acerca de si su software hace lo que debería; no hace declaraciones esenciales sobre su software.
Tomando tu ejemplo:
La implementación "correcta" de la adición sería el código equivalente a
a+b
. Y mientras su código haga eso, diría que el algoritmo es correcto en lo que hace y está implementado correctamente .A primera vista , ambos estaríamos de acuerdo en que esta es la implementación de una adición.
Pero lo que estamos haciendo realmente no es decir, que este código es la implementación
addition
, solo se comporta en cierto grado como uno: piense en el desbordamiento de enteros .El desbordamiento de enteros ocurre en el código, pero no en el concepto de
addition
. Entonces: su código se comporta en cierta medida como el concepto deaddition
, pero no lo esaddition
.Este punto de vista más bien filosófico tiene varias consecuencias.
Y una es que, podría decirse, las pruebas no son más que suposiciones del comportamiento esperado de su código. Al probar su código, podría (quizás) nunca asegurarse de que su implementación sea correcta , lo mejor que podría decir es que sus expectativas sobre los resultados que entrega su código se cumplieron o no; ya sea que su código esté equivocado, que su prueba esté equivocada o que ambos estén equivocados.
Las pruebas útiles lo ayudan a fijar sus expectativas sobre lo que debe hacer el código: siempre que no cambie mis expectativas y mientras el código modificado me dé el resultado que estoy esperando, podría estar seguro de que las suposiciones que hice sobre Los resultados parecen funcionar.
Eso no ayuda, cuando hiciste las suposiciones equivocadas; ¡pero hey! al menos previene la esquizofrenia: espera resultados diferentes cuando no debería haber ninguno.
tl; dr
Sus pruebas son suposiciones sobre el comportamiento del código. Si tiene buenas razones para pensar que su implementación es correcta, repare la prueba y vea si esa suposición se cumple.
fuente
datatype
es claramente la elección incorrecta. Una prueba revelaría que: su expectativa sería »funciona para grandes números« y en varios casos no se cumple. Entonces la pregunta sería cómo lidiar con esos casos. ¿Son casos de esquina? Cuando sí, ¿cómo lidiar con ellos? Quizás algunas cláusulas de quard ayudan a prevenir un mayor desorden. La respuesta está vinculada al contexto.Debe saber que la prueba fallará si la implementación es incorrecta, lo cual no es lo mismo que aprobar si la implementación es correcta. Por lo tanto, debe volver a colocar el código en un estado en el que espera que falle antes de corregir la prueba, y asegurarse de que falle por la razón que esperaba (es decir
5 != 12
), en lugar de otra cosa que no predijo.fuente
assertTrue(5 == add(2, 3))
proporciona un resultado menos útil queassertEqual(5, add(2, 3))
aunque ambos estén probando lo mismo).En este caso particular, si cambia el 12 a un 11, y la prueba ahora pasa, creo que ha hecho un buen trabajo probando la prueba y la implementación, por lo que no hay mucha necesidad de pasar por aros adicionales.
Sin embargo, el mismo problema puede surgir en situaciones más complejas, como cuando tiene un error en su código de configuración. En ese caso, después de arreglar su prueba, probablemente debería intentar mutar su implementación de tal manera que esa prueba en particular falle y luego revertir la mutación. Si revertir la implementación es la forma más fácil de hacerlo, está bien. En su ejemplo, puede mutar
a + b
aa + a
oa * b
.Alternativamente, si puede mutar ligeramente la afirmación y ver que la prueba falla, eso puede ser bastante efectivo para probar la prueba.
fuente
Yo diría que este es un caso para su sistema de control de versiones favorito:
Organice la corrección de la prueba, manteniendo los cambios de código en su directorio de trabajo.
Comprometerse con un mensaje correspondiente
Fixed test ... to expect correct output
.Con
git
, esto podría requerir el uso degit add -p
si la prueba y la implementación están en el mismo archivo, de lo contrario, obviamente, solo puede organizar los dos archivos por separado.Comprometer el código de implementación.
Retroceda en el tiempo para probar la confirmación realizada en el paso 1, asegurándose de que la prueba realmente falle .
Verá, de esa manera no confía en su destreza de edición para mover su código de implementación fuera del camino mientras prueba su prueba fallida. Emplea su VCS para guardar su trabajo y para asegurarse de que el historial registrado de VCS incluya correctamente tanto la prueba reprobatoria como la aprobada.
fuente