Capítulo 10 de 10 · 10 min de lectura

Calidad continua

Un test puede estar en verde, intacto, nombrar su criterio y no detectar nada. La forma de saberlo es romper el código a propósito y ver quién se queja.

Quién prueba los tests

El tutorial lleva nueve capítulos apoyándose en los tests. Nacen de la spec, se ven fallar, nombran su criterio, no se tocan en el verde, y la matriz dice que ningún criterio se queda sin uno. Falta una pregunta, y es la que más cuesta contestar mirando: ¿detectaría ese test el fallo que debería?

Hay una forma de contestarla sin mirar. Se rompe el código a propósito, un cambio pequeño cada vez (un < por un <=, un 32 por un 33, un && por un ||) y se ejecuta la suite. Si algún test falla, el cambio está muerto: ese comportamiento tiene quien lo vigile. Si todos siguen en verde, está vivo, y eso significa una de dos cosas: falta un test, o el cambio no cambia nada. A cada cambio se le llama mutante, y a la técnica, mutation testing.

Muta el código

Diez mutantes sobre la función del capítulo 4, con sus once tests. Elige uno y mira qué tests se ponen en rojo; los dos que sobreviven están explicados. Después edita el código y prueba un mutante tuyo.

8 de 10 mutantes muertos con los once tests del capítulo 4. Elige uno y mira quién lo caza; edita el código para probar el tuyo.

Muerto: lo cazan 1 test.

Los once tests1 en rojo
  1. ALI-01 alias válido

    errorDeAlias("oferta-otono") → null (vale)

  2. ALI-02 2 caracteres

    errorDeAlias("ab") → "alias: debe tener entre 3 y 32 caracteres"

  3. ALI-02 3 caracteres se acepta

    errorDeAlias("abc") → null (vale)

  4. ALI-02 33 caracteres

    errorDeAlias("abcdefghijklmnopqrstuvwxyz0123456") → "alias: debe tener entre 3 y 32 caracteres"

  5. ALI-03 mayúscula

    errorDeAlias("Oferta") → "alias: solo admite minúsculas, números y guiones"

  6. ALI-03 guion bajo

    errorDeAlias("mi_enlace") → "alias: solo admite minúsculas, números y guiones"

  7. ALI-04 empieza por guion

    errorDeAlias("-oferta") → "alias: no puede empezar ni terminar por guion"

  8. ALI-04 termina por guion

    errorDeAlias("oferta-") → "alias: no puede empezar ni terminar por guion"

  9. ALI-05 api

    errorDeAlias("api") → 'alias: "api" está reservado'

  10. ALI-05 assets

    errorDeAlias("assets") → 'alias: "assets" está reservado'

  11. un alias con varios fallos da solo el primero (caracteres antes que guion)

    errorDeAlias("-Abc") → "alias: solo admite minúsculas, números y guiones"

Un mutante muerto es un comportamiento vigilado. Uno vivo, o falta un test, o el cambio no cambia nada.

Los dos supervivientes son los dos casos que hay que distinguir siempre:

  • Falta un test. Reservar una palabra de más (admin) no lo nota nadie, porque ningún test comprueba que una palabra parecida a una reservada se acepta. acorta sí lo tiene: ALI-05 no es reservado si solo contiene la palabra, con apis. El mutante vivo dice exactamente qué test escribir.
  • Mutante equivalente. Recortar el alias antes de medirlo no cambia nada observable, porque la spec dice que llega ya recortado. Ningún test puede matarlo porque no hay diferencia que detectar. Hay que leerlo para saberlo, y es el coste de la técnica: los supervivientes se revisan a mano.

Fíjate también en cuántos tests matan cada mutante. El del < 3 lo mata uno. Si ese test no existiera, el mutante viviría y la validación aceptaría alias de dos letras sin que la suite dijera nada.

Cincuenta mutantes sobre acorta

Lo mismo sobre el código real, con un programa de cien líneas que aplica un cambio, ejecuta los tests del paquete y apunta qué pasó. Cincuenta mutantes sobre tres ficheros de acorta: el dominio (link/link.go), la autenticación (server/auth.go) y la CLI (cmd/acorta/main.go). Límites (< por <=), lógica (&& por ||), negaciones quitadas, constantes movidas una unidad, comparaciones invertidas.

Fichero Mutantes Muertos Vivos No compilan
link/link.go 28 27 0 1
server/auth.go 7 5 1 1
cmd/acorta/main.go 15 15 0 0

Los dos que no compilan no cuentan: uno porque go vet, que go test ejecuta antes, rechaza la condición resultante como sospechosa, y otro porque ! no se puede aplicar a un texto. De los 48 válidos, 47 muertos.

El superviviente merece la historia entera. Es esta línea de server/auth.go, tal como la deja el mutante:

return subtle.ConstantTimeCompare([]byte(got), []byte(s.token)) == 1

Lo que ha desaparecido es el guard s.token != "" &&, que dice que un servidor arrancado con token vacío no acepta nada. Ningún test lo comprueba, y la spec 008 lo dice con todas las letras: no es un criterio aparte porque desde fuera del paquete no se puede provocar, la CLI impide que el token llegue vacío. El agente del paso verde lo anotó, el orquestador lo aceptó y quedó escrito. El mutante vivo señala exactamente ese párrafo. No es un fallo descubierto: es una decisión confirmada, y así es como debería terminar la revisión de cada superviviente, con una de las dos etiquetas de arriba.

Y el dato que conecta con el capítulo 5: siete de los 47 mutantes muertos los mató un solo test. Subir el límite del alias de 32 a «32 no vale» lo caza únicamente ALI-02 32 caracteres se acepta. Es el test del borde, el que el criterio pedía con «con 3 y con 32 se acepta». Sin él, el mutante vive. Los dos tests por límite no eran una manía.

La suite entera, en cada cambio

Todo lo anterior vale si la suite se ejecuta. Entera, y cada vez.

Un agente ejecuta los tests de su paquete, porque es lo que tiene delante. En acorta la regla 4 de AGENTS.md dice otra cosa: terminado es go test ./... limpio en todo el repo, y si tocas web/, también sus tests y su build. Es make check, y el orquestador lo ejecuta antes de cada commit porque un cambio en link puede romper server sin que el agente de link lo sepa.

Lo que no puede depender de que alguien se acuerde se automatiza. acorta tiene desde este capítulo un fichero de veinticinco líneas que hace que GitHub ejecute make check en cada push y en cada pull request:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: web/package-lock.json
      - run: cd web && npm ci
      - run: make check

La misma orden que en local, sin una persona en medio. Y la spec 001 lo dice en su definición de terminado, para que el siguiente agente lo sepa.

Este tutorial sigue la misma regla consigo mismo. Cada capítulo lleva tests que fijan sus afirmaciones contra los ficheros reales, y antes de cada despliegue se ejecutan sus más de 150 tests y un recorrido con un navegador real por cada demo, en local y después contra la web publicada. Dos cifras mal escritas y un bloque que se veía cuando no debía no llegaron a publicarse por eso.

Cuándo fiarse del verde

Es la lista que han ido dejando los capítulos. Un verde dice algo cuando:

  1. Los tests salen de la spec, no del código, y nombran su criterio (capítulos 2, 4 y 5).
  2. Se han visto en rojo, por el motivo correcto, antes del código (capítulo 4).
  3. Ningún criterio se queda sin test, y se ha comprobado con la matriz, no con el informe (capítulo 5).
  4. El verde no tocó ningún test, y la suite original ejecutada por ti da lo mismo que dice el agente (capítulo 8).
  5. Los mutantes mueren, y los que sobreviven tienen etiqueta: falta un test, o es equivalente (este capítulo).
  6. La suite entera corre en cada cambio, en local y en el repositorio (este capítulo).
  7. Alguien ha leído el informe con la spec al lado, porque hay desviaciones que ningún test ve (capítulo 8).

Ninguna de las siete es cara. Las cinco primeras son órdenes; la sexta, un fichero; la séptima, unos minutos. Juntas convierten «la IA dice que funciona» en «funciona, y aquí está por qué».

Y ahora

Se acabó el tutorial. Lo que queda es tuyo.

La plantilla te deja las reglas puestas y la spec empezada, en un proyecto nuevo o en uno que ya tienes. acorta es un proyecto entero hecho así, con cada encargo y cada informe guardados, para copiar lo que te sirva. Y si algo de lo que has leído no se sostiene, el sitio para decirlo es el repositorio: cada afirmación de estas páginas tiene un fichero y un test detrás.