Herramientas de usuario

Herramientas del sitio


clase:daw:daw-proyecto:tema05

¡Esta es una revisión vieja del documento!


5. Automatizando el proyecto con IA

Objetivo del tema: que la calidad la vigilen tests que genera la IA en los cuatro niveles (arquitectura, unitarios, API y E2E) y revisiones que hace la IA antes de cerrar un issue. Al acabar, /git-flow finish no cierra nada que no tenga los tests en verde y la revisión limpia, y todo lo demás ocurre solo.

Una skill nueva, /test, y un verbo nuevo para /code, con la gramática del tema 1:

Skill Sustantivos Verbos
/test unit, arch, api <Entidad>, e2e create, run, repair. Siempre con el tipo delante: no hay un «todos»
/code el issue, implícito review (nuevo) e impl <plan> (tema 4)

Los cuatro tipos de tests

Tipo Qué prueba Con qué Necesita Lo ejecuta
arch Que cada clase está en su capa y depende de lo que debe ArchUnit Nada mvn test, dentro del build
unit La lógica del dominio, servicio a servicio JUnit 5 y Mockito, sin Spring Nada mvn test, dentro del build
api Cada endpoint de verdad: HTTP, código de estado y JSON Playwright (fixture request) La aplicación del worktree levantada y la base de datos reseteada npm run test:api, fuera del build
e2e La aplicación completa desde el navegador, criterio a criterio Playwright con Chromium Lo mismo npm run test:e2e, fuera del build

Los dos primeros forman parte del build de Maven: corren en cada compilación y no necesitan nada levantado. Los dos últimos no forman parte del build: son lentos, necesitan la aplicación y se lanzan cuando se quiere. Los comandos son los mismos para el alumno, para el agente y para CI (tema 6); lo que añade /test es no tener que recordarlos y, cuando algo sale en rojo, que el agente lo explique.

Crear los tests: /test <tipo> create

arch: tests de arquitectura con ArchUnit

Las reglas de arquitectura (las de Programación y DWS: capas, nombres, sin ciclos, domain sin Spring) las hace cumplir ArchUnit con tests que fallan cuando una clase está donde no debe. Lo nuevo es que los escribe la IA a partir de las reglas en castellano. Es el único create que se lanza una vez por proyecto, no por issue:

/test arch create
- Las clases de controller solo dependen de domain.
- Las de persistence solo dependen de domain.
- Nada en domain importa org.springframework ni jakarta.persistence.
- Los controladores acaban en Controller y están en ..controller..
- Los repositorios de persistence acaban en RepositoryImpl e implementan una interfaz de domain.
- Sin ciclos entre paquetes.

Cada test lleva en su @DisplayName y en un comentario qué comprueba, por qué y las excepciones:

@Test
@DisplayName("domain no depende de Spring: el dominio tiene que compilar y probarse sin el framework")
void domainSinSpring() {
    noClasses().that().resideInAPackage("..domain..")
        .should().dependOnClassesThat().resideInAPackage("org.springframework..")
        .check(clases);
}

No hay un documento aparte con la arquitectura. Mantener un Markdown sincronizado con los tests es un trabajo que nadie hace, así que la fuente de verdad son los tests con su descripción. Para cambiar una regla se le pide a la skill que cambie el test. Cuando se añade una regla que el código actual aún no cumple, FreezingArchRule.freeze(regla) congela las violaciones existentes y solo falla con las nuevas.

unit: tests unitarios con JUnit

Los tests unitarios prueban la lógica del dominio sin Spring y sin base de datos: un servicio con sus repositorios sustituidos por mocks de Mockito. Son los más rápidos y los que mejor localizan un error.

/test unit create PedidoService

Lo que hace: lee la clase, identifica cada comportamiento (lo que devuelve, lo que valida, lo que lanza) y escribe un test por comportamiento en backend/src/test/java, con el mismo paquete, @DisplayName en castellano que diga el caso y Mockito para las dependencias. Los ejecuta y se para si alguno falla por un error del código, en vez de retocar el test para que pase.

Qué no prueban: controladores ni repositorios. Eso es de los tests de API.

api: tests de API con Playwright

Los tests de API prueban cada endpoint de verdad: peticiones HTTP contra el backend del worktree, comprobando el código de estado y el JSON. Se escriben con Playwright, igual que los E2E (mismo lenguaje, misma configuración, misma carpeta): Playwright trae para esto el fixture request, que hace peticiones sin abrir ningún navegador.

/test api create Pedido

Por cada endpoint del controlador, en frontend/tests/api/pedidos.spec.ts, un test del caso bueno y uno por cada error que la spec o las validaciones prevén: 400 con un campo obligatorio vacío, 404 con un id que no existe, 409 con un valor único repetido. Los datos de partida son los del R__ de la entidad; los tests que escriben se limpian a sí mismos (borran lo que crean) para que se puedan pasar dos veces seguidas.

test('POST /api/pedidos sin número devuelve 400 y el nombre del campo', async ({ request }) => {
  const res = await request.post('/api/pedidos', { data: { fecha: '2026-10-14', clienteId: 1 } });
  expect(res.status()).toBe(400);
  expect((await res.json()).errores.numero).toBeDefined();
});

Los de API y los E2E son dos proyectos del mismo playwright.config.ts, separados por carpeta; el de API no necesita navegador:

export default defineConfig({
  use: { baseURL: process.env.BASE_URL ?? 'http://localhost:4200' },
  projects: [
    { name: 'api', testDir: 'tests/api' },
    { name: 'e2e', testDir: 'tests/e2e', use: { browserName: 'chromium' } },
  ],
});

e2e: tests E2E con Playwright

Los tests E2E prueban la aplicación completa desde el navegador, como lo haría el usuario. Los criterios de aceptación de requirements.md son frases comprobables; e2e las convierte en tests de Playwright en TypeScript, frontend/tests/e2e/<entidad>.spec.ts, y los deja pasando. No necesita argumento: la spec es la del worktree.

/test e2e create

Cómo trabaja: lee los criterios, abre la aplicación del worktree con el Playwright MCP para ver qué hay en pantalla, escribe el test y lo ejecuta; si falla por un selector o un texto, lo arregla y lo vuelve a pasar. La IA interviene al generar, no al ejecutar: lo que queda en el repositorio es un .spec.ts normal, determinista, que CI ejecuta sin ninguna IA.

Reglas que la skill tiene que respetar (están en convenciones-frontend):

  • Selectores por data-testid y por rol (getByRole, getByLabel), nunca por clase CSS. Un test que depende del CSS se rompe al primer cambio de estilo.
  • Un test por criterio de aceptación, con el criterio como nombre del test.
  • Los tests cuentan con los datos de prueba del tema 3 y se pasan sobre la base de datos recién reseteada.

Ejecutarlos: /test <tipo> run

Ejecuta los tests de un tipo y, si algo sale en rojo, lo explica: qué test, qué esperaba, qué ha pasado y dónde mirar. Por dentro son los comandos del tema 3, que es lo que ejecuta CI sin ninguna skill:

Verbo Comando
/test arch run mvn test -Dtest='*ArchTest' desde backend/
/test unit run mvn test -Dtest='!*ArchTest' desde backend/
/test api run npm run test:api desde frontend/ (resetea la base de datos y levanta la aplicación antes)
/test e2e run npm run test:e2e desde frontend/ (igual)

No hay un «todos» a propósito: los tipos se lanzan uno a uno, en ese orden, para saber en cada momento qué se está probando y contra qué. Para que arch y unit se puedan separar, los tests de ArchUnit acaban en ArchTest y los unitarios en Test. En /sdd spec impl es /test unit run lo que pasa después de cada tarea.

Repararlos: /test <tipo> repair

Pasa mucho: se cambia algo del código, se refactoriza, y se rompen tests, sobre todo los E2E. repair pasa los tests de un tipo y, por cada uno que falla, decide qué está mal, el test o el código, y arregla lo que corresponda:

/test e2e repair

El criterio es la intención:

  • Un test está desactualizado solo si un cambio intencionado del código (un commit, o un cambio sin commitear que el alumno ha hecho a propósito) modificó o eliminó lo que el test comprueba: el texto de un mensaje, un campo, un flujo. Entonces se actualiza el test.
  • Sin un cambio intencionado que lo justifique, el fallo es una regresión: se arregla el código y el test no se toca.
  • Si no se sabe, no se toca nada y se pregunta.

Y tres reglas que no se negocian, porque sin ellas «reparar» acaba siendo «hacer que pase»:

  • Nunca quita, comenta ni relaja una aserción, salvo que el cambio intencionado lo obligue. Nunca crea tests nuevos: para eso está create.
  • Borra un test solo si lo que comprobaba ya no existe en la aplicación, y entero, no vaciado ni comentado.
  • Un test cada vez, sin commits, y al final una tabla: test, diagnóstico (desactualizado / regresión / sin determinar) y qué se ha cambiado. El alumno la lee y decide si está de acuerdo.

repair deja un fichero de estado junto a la spec, repair.md, con una línea - [x] / - [ ] por test; si se interrumpe, al relanzarlo sigue por donde iba.

La revisión: /code review

Sin pull request nadie mira el código antes de que llegue a develop. Lo mira la IA:

/code review

Una sola revisión que mira dos cosas, el código y los tests, y que no toca nada y no bloquea: deja cada hallazgo como una tarea - [ ] en review.md, junto a la spec. Después, el alumno lee, decide, y las correcciones las hace /code impl review.md (abajo). Se puede relanzar las veces que haga falta: marca como resueltos los hallazgos que ya no aparecen y añade los nuevos.

El código: contra la spec y las reglas

Compara el diff de la rama contra develop con tres cosas:

  • La spec del issue: ¿hace lo que se pidió, y como se pidió? ¿Falta algún criterio de aceptación? ¿Ha hecho cosas que no estaban en la spec?
  • Las convenciones (convenciones-backend, convenciones-frontend, que remiten al steering): ¿está en las capas correctas, con los nombres correctos, con migración y datos de prueba, con data-testid?
  • Las reglas de calidad de calidad-codigo (abajo): ¿es código bueno, además de correcto?

Cada hallazgo lleva la regla incumplida, el sitio (fichero:línea) y el coste del código tal como está (qué se duplica, qué no se puede probar, qué se rompe al cambiarlo). Sin coste no hay hallazgo: eso separa un defecto de una preferencia. Antes de proponer cambiar la forma de algo sin cambiar lo que hace (extraer, mover, renombrar), mira cómo lo resuelven tres clases hermanas; si lo hacen igual, es una convención del proyecto y no se reporta. Y clasifica en grave (no cumple la spec, rompe una convención, falta un test, o una regla con coste claro) y mejorable.

Un hallazgo bien hecho y dos mal hechos:

GRAVE  PedidoService.java:48  metodos.md «cálculo puro y efectos secundarios»
       calcularTotal() lee los pedidos de la base de datos y los suma en el mismo método.
       Coste: no se puede probar la suma sin base de datos; el test unitario actual la monta entera.

MAL    "extraer las líneas 20-24 a un método privado para mejorar la legibilidad"
       (no mira las clases hermanas: todos los servicios lo escriben así; y no dice qué cuesta)
MAL    "el método es demasiado largo"
       (sin regla ni coste: es una opinión)

Los graves se corrigen. Los mejorables los decide el alumno: o se dejan abiertos para que se corrijan con los graves, o se cierran a mano con un «no se hace, es así a propósito». Un hallazgo cerrado así no bloquea nada.

El alumno lee la revisión. El código lo ha escrito la IA, pero el commit lo firma él y es responsable de lo que hay en develop. La revisión es una ayuda para leer, no una excusa para no leer.

calidad-codigo: las reglas

review sabe si el código es bueno porque carga una skill de conocimiento con las reglas de calidad: métodos que hacen tres cosas, clases que se conocen demasiado, comentarios que repiten el código, tests que copian los valores que prueban. Se escribe una vez y se va completando cuando una revisión saca algo que no estaba. SKILL.md lleva la máxima y el índice; cada grupo de reglas, en su fichero:

.claude/skills/calidad-codigo/
├── SKILL.md          <- la máxima, el orden de prioridades y la tabla de ficheros
├── metodos.md
├── clases.md
├── comentarios.md
└── tests.md

La máxima: el código se escribe para que una persona lo entienda al leerlo y para que un agente lo pueda modificar sin equivocarse. Consecuencia que lo cambia todo: una repetición pequeña y visible es aceptable; una indirección para ahorrar dos líneas (un método que solo reenvía, un genérico) no lo es, porque el agente no olvida los dos sitios que hay que tocar y la persona sí pierde el hilo.

Cuando dos reglas chocan, gana la primera de esta lista: mantenibilidad, testabilidad, determinismo, separación de responsabilidades.

Fichero Reglas (las que entran; se amplía con lo que salga en las revisiones)
metodos.md El método público cuenta la historia entera y se lee de arriba abajo. Se trocea por cosa que hace, no por fase. Con pocos casos (hasta 4), un método lineal por caso en vez de un genérico. El nombre dice qué hace, no cómo. Cabe en una pantalla y tiene como mucho tres parámetros. El cálculo puro se separa de la entrada/salida (base de datos, HTTP) para poder probarlo sin montar nada
clases.md Una clase, una responsabilidad. Composición antes que herencia. Un DTO o record agrupa solo lo homogéneo y sus campos se nombran por lo que son, no por quién los usa. Nada estático con estado. La interfaz del Repository en domain dice lo que el dominio necesita, no lo que la base de datos ofrece
comentarios.md El código se explica solo: si hace falta un comentario para entender qué hace, el nombre está mal. El único comentario que se escribe es el porqué que leer el código no revela (una decisión, una restricción externa). Nada de comentarios que repiten la línea de debajo ni de código comentado
tests.md Un test por comportamiento, con el caso en el nombre. El test no copia los valores del código que prueba (si el IVA es una constante, el test la lee de producción y comprueba la relación, no escribe 21 otra vez). Sin lógica en el test: ni if ni bucles. Lo que se prepara se ve en el propio test

Y en SKILL.md, cuatro olores de diseño que vienen antes del código y que el agente tiene que buscar aunque no haya regla concreta: una decisión que se toma en dos sitios; un método que vive en una clase que no es la dueña del concepto; un if que no cubre todos los casos; un retorno defensivo (return null, lista vacía) que tapa un error en vez de fallar.

La description de la skill dice cuándo cargarla: «Reglas de calidad del código del proyecto. Cargar al escribir o revisar cualquier clase Java o TypeScript, y siempre desde /code review».

Los tests: contra los criterios

La misma revisión mira después los tests del issue: que cada criterio de aceptación de requirements.md tiene su test E2E y cada caso de error su test de API; que no hay tests que no comprueban nada (sin aserciones, o con aserciones que siempre pasan); que cumplen tests.md de calidad-codigo (no copian los valores del código, sin lógica dentro). Lo que falta es un hallazgo grave más en review.md («falta el test E2E del criterio 3»).

Las correcciones: /code impl <plan>

/code impl .kiro/specs/12-alta-clientes/review.md

El mismo /code impl del tema 4: review.md es una lista de tareas como plan_<algo>.md, así que lo hace igual, una detrás de otra, marcándolas, con los tests unitarios y de arquitectura después de cada una y parándose si uno falla por el código.

Lo que comprueba /git-flow finish

Con los tests y la revisión, finish deja de ser solo git. Antes de fusionar comprueba, y si algo falla lo dice y no hace nada:

  1. tasks.md o plan_<algo>.md sin tareas pendientes.
  2. review.md sin hallazgos abiertos (los graves corregidos; los mejorables, corregidos o cerrados por el alumno).
  3. Los cuatro tipos de tests en verde: finish ejecuta por dentro los comandos de /test run, uno a uno, y se para en el primero que falla.

Si está todo, hace lo del tema 2: update, –squash en develop con Closes #N, push, ./app.sh stop -v y borrar el worktree. Es la regla del tema 1: ningún comando espera al alumno; los que generan trabajo lo dejan en ficheros, y el que hace algo irreversible solo comprueba que esos ficheros están como tienen que estar.

Ejercicios

Ejercicio 1

  • Crea un issue «Skill /test: create» e inícialo.
  • Instala Playwright en frontend/ (npm init playwright@latest) con los proyectos api y e2e y los scripts test:api y test:e2e del tema 3.
  • Escribe la skill /test con los cuatro sustantivos y el verbo create: arch (reglas en castellano → tests ArchUnit en backend/src/test acabados en ArchTest, con @DisplayName y comentario), unit (clase o paquete → un test JUnit 5 por comportamiento, con Mockito y sin Spring), api (entidad → tests/api/<entidad>.spec.ts con un test por endpoint y por error previsto) y e2e (criterios de requirements.md de la spec del worktree → un .spec.ts por criterio, usando el MCP de Playwright).
  • /test arch create con las seis reglas del tema. Mueve a mano el servicio del CRUD al paquete controller y comprueba que mvn test falla con el mensaje del test. Deshazlo.
  • /test unit create con el servicio del CRUD del tema 4, /test api create con su entidad y /test e2e create. Comprueba que mvn test no ejecuta los de Playwright y que npm run test:api y npm run test:e2e pasan dos veces seguidas.
  • Finaliza el issue.

Ejercicio 2

  • Crea un issue «Skill /test: run y repair» e inícialo.
  • Añade el verbo run con la tabla de comandos del tema: en rojo, explica qué test, qué esperaba y qué ha pasado.
  • Introduce un error a mano en una validación del servicio, reconstruye el backend y lanza /test unit run y /test api run: tiene que caer al menos un unitario y uno de API, y la explicación tiene que señalar la validación. Deshazlo.
  • Añade el verbo repair con el criterio de la intención, las tres reglas y el fichero repair.md.
  • Cambia a propósito el texto de un mensaje de error del formulario y lanza /test e2e repair: tiene que actualizar el test y decir que era un cambio intencionado. Después rompe una validación del servicio sin tocar ningún test y lanza /test api repair: tiene que arreglar el código y dejar el test como estaba.
  • Finaliza el issue.

Ejercicio 3

  • Crea un issue «Skill /code» e inícialo.
  • Escribe la skill de conocimiento calidad-codigo con los cuatro ficheros de la tabla y los olores de diseño en SKILL.md. Pide al agente un primer borrador a partir de la tabla y corrígelo: cada regla con una frase de ejemplo de código que la cumple y otra que no.
  • Añade a la skill /code el verbo review, que revisa primero el código y luego los tests: solo lectura más escribir review.md, el formato de hallazgo del ejemplo y la regla de las clases hermanas.
  • /code review sobre el CRUD del tema 4. Lee review.md: para cada hallazgo, decide si es un defecto o una preferencia y por qué. Cierra a mano uno mejorable.
  • /code impl con review.md y vuelve a pasar /code review: no deben salir los graves.
  • Finaliza el issue.

Ejercicio 4

  • Crea un issue «finish comprueba» e inícialo.
  • Amplía /git-flow finish con las tres comprobaciones del tema (las tareas, en tasks.md o en plan_<algo>.md, según el issue). Si alguna falla, tiene que decir cuál y no tocar nada.
  • Deja una tarea sin marcar en tasks.md y lanza finish: se tiene que parar. Márcala, deja un hallazgo grave abierto en review.md y vuelve a lanzar: se tiene que parar. Ciérralo y finaliza.

Ejercicio 5

  • Crea un issue para la segunda entidad de tu proyecto e inícialo. Es el primer issue que se hace con el arnés entero.
  • /sdd spec create. En design.md compáralo con el CRUD del tema 4: cada diferencia es una línea que falta en el steering. Corrígela y vuelve a generar hasta que salga igual.
  • /clear, /sdd spec impl, /test unit create, /test api create y /test e2e create. Los cuatro tipos en verde, uno a uno.
  • /code review. Quita a mano de requirements.md un caso de error sin quitar su código y vuelve a pasarla: tiene que marcarlo como grave. Restaura el fichero y /code impl con review.md.
  • /git-flow commit y /git-flow finish.
clase/daw/daw-proyecto/tema05.1791976215.txt.gz · Última modificación: por claude