Hay algunas preguntas estratégicas básicas que necesitan respuesta antes de poder hablar sobre los detalles de como probar:
• ¿Qué significa probar?
• ¿Cuando probar?
• ¿Cómo elegir qué lógica probar?
• ¿Cómo elegir qué datos probar?
Probar N
La manera de probar el software es escribir pruebas automatizadas.
Cuando se desarrolla una nueva funcionalidad se pueden probar los cambios pero probar esos cambios no es lo mismo que tener pruebas. Por ejemplo, las pruebas se ejecutan automáticamente mientras que probar se hace de manera manual, por ejemplo buscar un botón en una interfaz.
Las pruebas aportan seguridad cuando se realizan cambios ya que advierten si se ha producido un error. Cuanto más miedo de producir un error más veces habrá que ejecutar las pruebas, de esta manera se reducen el número de errores y además se reduce el estrés de los programadores.
Aislar las pruebas
Las pruebas deberían ser independientes entre sí de manera que el resultado de unas no influya en otras. Se podría dar el caso de que, al compartir algunos datos, unas pruebas dejaran inconsistente el sistema haciendo fallar otra o, lo que es peor, validando las pruebas cuando no debería hacerlo.
Por esta razón las pruebas deberían estar aisladas. Esto conlleva a que el orden de ejecución puede ser independiente.
Las pruebas deberían ejecutarse lo más rápido posible de manera que se puedan ejecutar a menudo.
Listado de pruebas
El listado de pruebas es, tal y como indica, un listado donde se van a incluir todas las pruebas que, inicialmente, hay que hacer.
Esto tiene una ventaja y es que una vez seleccionada la prueba que se quiere desarrollar se puede centrar toda la atención en esa prueba. Si surge una prueba nueva o una nueva tarea, se apunta en el listado para más tarde valorar cuando desarrollarlo.
Así que en la lista se colocan los ejemplos de cada operación que se sabe que se se necesita implementar. Seguido se incluyen aquellas operaciones que no existen todavía pero que se necesitarán en un futuro. Para finalizar se incluyen las refactorizaciones que se piensa que se tendrán que realizar para limpiar el código.
Una vez terminado el listado de pruebas inicial se podría plantear el implementar todas ellas aunque no se recomienda en absoluto. La primera razón es porque con cada prueba que se implementa hay que hacer una refactorización. Si se hacen diez pruebas la refactorización va a ser más complicada. La segunda razón viene dada cuando hay diez pruebas rotas. Es mucho más complejo tratar de conseguir solucionar diez pruebas que una. Hay que tratar de evitar estar en rojo lo menos posible.
Una vez que la prueba se ejecuta correctamente, es posible que la implementación implique nuevas pruebas. Estas deben incluirse al final del listado de pruebas. Lo mismo para las refactorizaciones, como por ejemplo «esto se está viendo feo».
Cuando se termina una sesión de programación hay que tener cuidado de aquellas pruebas que están pendientes y tienen prioridad en la lista. Se pueden usar varias listas, dependiendo de las prioridades, por ejemplo, se puede tener una lista «now» para las pruebas más prioritarias y otra lista «later» para pruebas que lo son menos o no es el momento de abordarlas. Una refactorización de cierta envergadura que no impacte en la prueba que se está desarrollando debería incluirse en la lista «later» para abordarla en un paso posterior.
En aquellos casos en los que se piensa que puede haber una prueba que no está implementado aún y que podría no funcionar entonces esta prueba debería tener prioridad incluso sobre desplegar el código.
Probar primero
¿Cuando habría que escribir la prueba? Siempre se deberían la prueba antes de escribir el código que tiene que probarse.
En los casos en los que de desarrolla la implementación sin realizar las pruebas se empieza a añadir un estrés a la hora de probar de forma manual. A medida que el sistema va añadiendo funcionalidades el estrés va aumentado dado que, posiblemente, no se haya probado lo suficiente.
Para romper ese patrón se puede escribir una prueba antes de escribir el código que tiene que probarse. De esta manera se reduce el estrés de probar.
La ventaja inmediata es para el TDD es que se empieza y se mantiene haciendo bajo un estrés moderado.
Afirmación primero
¿Cuando debería escribirse la afirmación? Debería ser lo primero en escribirse.
Escribir la afirmación primero simplifica el proceso de escribir una prueba. Cuando se escribe una prueba se están resolviendo varios problemas a la vez incluso si no se ha pensado sobre la implementación.
• ¿Dónde pertenece la funcionalidad? ¿Es una modificación de un método existente, un método nuevo en una clase que ya existe, en una clase nueva?
• ¿Qué nombres deberían utilizarse?
• ¿Cómo se va validar la respuesta correcta?
• ¿Qué es la respuesta correcta?
• ¿Qué otros pruebas sugiere esta prueba?
Tratar de abordar todos estos problemas a la vez es, cuanto menos, una osadía para la mayoría de la gente por lo que separar el problema en partes más pequeñas es una buena estrategia. Las preguntas «¿qué es la respuesta correcta?» y «¿cómo se va a validar la respuesta correcta?» son dos problemas que se pueden separar fácilmente del resto y pueden servir como punto de partida para el resto.
Por ejemplo, en el caso de querer comunicarnos con otro sistema a través de un «socket» y que, una vez hecha la comunicación el «socket» debería cerrarse y deberíamos haber leído la cadena «abc».
El primer paso sería escribir las afirmaciones.
Una vez que están escritas, resolvemos el problema de dónde viene «reply» qué será del «socket» claro está.
¿Y «socket»? Este vendrá dado por una conexión a un servidor.
Y antes de esto necesitamos crear un servidor.
De esta manera se crea el guión de la prueba en pequeños pasos.
Datos de prueba
El objetivo de los datos de prueba es que hay que sean fáciles de leer y entender.
Si hay diferencia entre datos entonces estos deberían tener un significado, o sea, que si no hay una diferencia conceptual entre 1 y 2 entonces se debería usar 1.
Si un sistema tiene múltiples entradas de datos y con 3 datos se cubren las distintas posibilidades, o sea, los distintos conceptos entonces no tiene sentido tener un lista de 10 datos como entrada.
Un truco es tratar de que la misma constante signifique más de una cosa.
La alternativa a los datos de prueba son los datos reales y estos son útiles cuando:
• Se hacen pruebas en sistemas de tiempo real usando trazas de eventos externos.
• Si se está comparando la salida del sistema actual con la salida de un sistema previo («Parallel Testing»).
• Si se está refactorizando una simulación y se espera precisamente la misma respuesta cuando ha finalizado, particularmente si la precisión de los números con coma flotante puede ser un problema.
Datos evidentes
Los datos que se utilizan en la prueba deben representar su intención de una manera clara. Las pruebas se escriben para personas.
El valor esperado y el valor actual deben tener relación aparente. Por ejemplo, en el caso de hacer un cambio de una moneda a otra con una tasa de cambio por parte del banco de un 1,5%, si cambiamos 100USD a GBP y el cambio está a 2:1 entonces el resultado debería ser 50GBP-1,5% = 49.25GBP.
En este caso aparece el número mágico 49.25, que podemos evitar haciendo los cálculos.
En este ejemplo se puede observar cómo se calcula el resultado esperado.
Los datos evidentes tienen el beneficio de hacer la programación más fácil. Una vez que vemos la expresión en la afirmación ya sabemos que tenemos que programar. Además, tenemos la ventaja de poder utilizar datos falsos para poder pasar la prueba e ir evolucionando esta de manera incremental.
Los datos evidentes se pueden ver como una excepción a la regla de los números mágicos. Dentro del alcance de un método la relación entre estos números es obvia. En el caso de tener definidas constantes simbólicas habría que utilizar estas constantes simbólicas.