TDD con agente
Propósito
Separar la escritura del test y la implementación en fases explícitas. Primero el agente confirma que el test falla por el motivo esperado, luego cambia el código hasta que pase. Los criterios fijados ayudan a notar cuándo se amaña el resultado.
También conocido como
Test-driven development con agente, red–green–refactor, test-first.
Problema
Cuando el agente escribe los tests a partir de una implementación terminada, puede tomar su comportamiento por el esperado. Un error en la lógica acaba entonces tanto en el código como en la comprobación.
Por ejemplo, un test calcula un descuento con la misma fórmula que usa la función. Si la fórmula es incorrecta, ambos resultados coincidirán. El valor esperado hay que obtenerlo de forma independiente, a partir del requisito o de un ejemplo resuelto. Incluso un test independiente se puede pasar con un stub para el caso particular. Por eso, tras el verde, conviene un control de amaño.
Separar las fases de forma explícita te permite ver el test antes de la implementación y controlar los cambios del criterio.
Solución
Lleva las fases roja y verde a prompts distintos y quédate tú con la puerta entre ellas.
- Fija el orden. El agente escribe primero el test y luego la implementación.
- Obtén el rojo. El agente ejecuta el test y muestra que falla porque falta el comportamiento.
- Fija el criterio. Revisa el test y guárdalo con un commit.
- Obtén el verde. El agente cambia la implementación y repite la comprobación. Cambiar el test requiere una decisión aparte.
- Comprueba el amaño. Un revisor evalúa si el código resuelve el caso general (ver Escritor y revisor).
- Refactoriza bajo la protección de los tests que pasan.
Recorre el ciclo de un comportamiento a la vez. El siguiente test tiene en cuenta lo que salió a la luz en el paso anterior. Antes de empezar, acuerda el límite público en el que se comprueba el resultado, para que la refactorización interna no rompa un test sin que cambie el comportamiento.
Estructura
El diagrama muestra el paso de un test que falla a la implementación y a una comprobación independiente.
El bucle de vuelta repite el ciclo para el siguiente escenario una vez confirmado el actual.
En el historial de git el mismo ciclo se ve así.
El test está en su propio commit antes de la corrección, así que el diff de la corrección muestra enseguida si el criterio cambió junto con el código. La siguiente rebanada vuelve a empezar con un commit del test. Los cuadrados marcan los commits de tests.
Participantes / Componentes
- Desarrollador fija el comportamiento esperado y aprueba los cambios de los criterios.
- Agente escribe el test y después la implementación, en secuencia.
- Test comprueba el requisito y se guarda antes de la implementación.
- Costura de testing fija la interfaz pública a través de la que se observa el comportamiento.
- Revisor busca amaños para casos particulares.
Cuándo aplicarlo
- El resultado se puede expresar con entradas concretas y salidas esperadas.
- Un bug se puede reproducir con un test antes de la corrección.
- Código donde una regresión sale cara y los tests seguirán vivos como especificación.
Para una elección visual o un prototipo exploratorio pueden encajar mejor la comprobación por capturas y un experimento desechable.
Consecuencias y compromisos
- ➕ El test se puede contrastar con el requisito antes de que exista la implementación.
- ➕ Un cambio en un criterio fijado se ve en el diff.
- ➕ Comprobar el comportamiento público depende menos de la estructura interna del código.
- ➖ Las fases explícitas y la revisión encarecen una edición pequeña.
- ➖ Hay que controlar el orden de los pasos y los motivos por los que fallan los tests.
- ➖ Una costura mal elegida vuelve frágiles los tests.
Implementación
- Declara un orden de trabajo test-first.
- Acuerda los casos esperados y pide al agente que proponga los casos límite que falten.
- Elige la interfaz pública de la comprobación antes de escribir los tests.
- Comprueba el motivo por el que falla el primer test y guárdalo con un commit.
- Pide cambiar la implementación hasta que pase, sin tocar el test. Obtén la salida de la ejecución.
- Pasa el código a un revisor fresco para buscar stubs de casos particulares y casos omitidos.
- Pide la refactorización por separado, bajo la protección de tests en verde.
- Repite el ciclo para el siguiente comportamiento. Fija el orden en la memoria del proyecto.
Superpowers incluye test-driven-development en la implementación de las tareas. En el pack de Matt Pocock, /tdd también fija las costuras de testing y el trabajo de un escenario a la vez.
Ejemplo
Cuando la sesión caduca, el usuario ve un spinner infinito. Empiezas por la reproducción.
Escribe un test que falle: ante un 401 de la API, el usuario acaba en /login
El agente comprueba cómo se comporta el cliente HTTP ante una respuesta 401. El test falla porque el cliente repite la petición sin fin. Revisas el test y lo guardas con un commit.
Ahora arréglalo
El agente corrige el interceptor común, añadiendo el manejo del 401 con redirección a la página de inicio de sesión. Cuando el test pasa, pides una revisión.
En un contexto fresco, comprueba que el manejo del 401 se aplica a todas las peticiones y no depende del endpoint concreto del test.
El revisor comprueba el interceptor común. El test conserva el escenario original y detectará si vuelve a fallar.
Antipatrones y errores comunes
- Tests a partir de la implementación. La comprobación puede fijar un error del código terminado. Deriva el comportamiento esperado del requisito.
- Saltarse el rojo. Sin un fallo observado no está claro si el test detecta el defecto original.
- Todos los tests de antemano. Una suite grande puede fijar suposiciones no verificadas. Añade escenarios uno tras otro.
- Amañar el criterio. Debilitar un test durante la corrección requiere una discusión aparte.
- Comprobar detalles internos. Los tests de métodos privados pueden romperse tras una refactorización aunque el comportamiento se conserve.
- Oráculo tautológico. Calcular la respuesta esperada de la misma forma repite el error de la implementación. Usa un ejemplo independiente o el requisito.
Usos conocidos
- Superpowers hace del TDD una parte obligatoria de la ejecución del plan.
- Los skills de Matt Pocock usan
/tddcon costuras de testing acordadas. - Kent Beck describió la práctica en el libro Test-Driven Development: By Example.
Patrones relacionados
- Bucle de retroalimentación fija el ciclo general de comprobación y corrección.
- Escritor y revisor ayuda a detectar el amaño una vez que los tests pasan.
- Cuatro fases permite acordar en el plan los escenarios a comprobar.
- Diagnóstico mediante hipótesis ayuda a establecer la causa de un defecto antes de corregirlo.
- Éxito prematuro surge cuando se toman unos tests unitarios en verde por una funcionalidad que funciona.