Free

Deja que Claude escriba los tests, tú solo revisa: 1.562 líneas en la práctica

Casi las 1.562 líneas de tests de TopicReview las escribió Claude. Yo solo reviso — ya más de dos semanas en producción, todos los commits posteriores añaden tests, ninguno los reescribe. Este post trata de por qué los tests son el blanco ideal para delegar, qué mirar y qué ignorar en la review (incluido un edge case real faltante en el spec), y la configuración por defecto para este reparto.


El post anterior cerraba con "119 specs en verde" para TopicReview. La pregunta real que sigue: ¿quién escribió esos tests?

Respuesta: Claude escribió casi las 1.562 líneas de código de test. Yo solo reviso. Llevamos más de dos semanas en producción, y el patrón de mantenimiento de esas 1.562 líneas ha sido solo añadir tests nuevos, nunca reescribir los viejos.

Este post trata de por qué los tests son el mejor candidato para delegar en Claude, qué mirar y qué ignorar al revisar, y hasta dónde aguanta este reparto en la práctica.

Primero, los números

Los tests de TopicReview se reparten en 7 archivos:

spec/services/topic_review_service_spec.rb   760 líneas (88 tests)
spec/requests/topic_reviews_spec.rb          281 líneas (32 tests)
spec/requests/review_appeals_spec.rb         152 líneas (16 tests)
spec/requests/review_votes_spec.rb           127 líneas
spec/policies/topic_review_policy_spec.rb    109 líneas
spec/jobs/close_topic_review_job_spec.rb      71 líneas (7 tests)
spec/models/topic_review_spec.rb              62 líneas
───────────────────────────────────────────
                                          1.562 líneas

Cubre cuatro tipos de test: service (lógica de negocio), request (controller + integración), policy (autorización con Pundit), job (tareas programadas).

El commit inicial d162f1e lleva Co-Authored-By: Claude Sonnet 4.6 y aterriza 1.100+ de esas líneas de una sola vez. Todos los commits de spec posteriores son "Add test for..."—ni uno es refactor o rewrite:

00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning
3b185da Update specs to use PROVISIONAL_PENALTY constant

Parches de huecos, no rehacer. Este detalle vuelve después.

Por qué los tests son lo mejor para delegar

Cuatro razones duras:

1. Entradas y salidas explícitas. Un test es "dado este estado → espera este comportamiento". Ese es el punto fuerte de Claude: traducir especificación a aserción. El código de negocio a veces exige sopesar compromisos; los tests, casi nunca.

2. Mecánico × alto volumen. Un solo describe .open! tiene que cubrir "con jurados elegibles / sin jurados / sin topic / review ya activo": cuatro context, 2–5 it cada uno. El humano empieza a recortar esquinas en el tercer context. Claude escribe el it número 88 con la misma diligencia que el primero.

3. Ciclo de feedback brevísimo. Escribes un test, corres rspec, sabes en segundos si pasa. El código de negocio tarda días de uso real en exponer problemas. Ciclo corto = cualquier error de Claude lo atrapa rspec al momento—no necesitas vigilar.

4. Naturalmente paralelo. Los bloques it son independientes, sin acoplamiento oculto, trivialmente escalables. Generar decenas de tests aislados a la vez es exactamente para lo que Claude es bueno.

Qué mirar en la review y qué ignorar

Aquí está el pivote del reparto.

Ignorar:

  • Si la sintaxis RSpec es correcta → Claude casi nunca falla
  • Calidad del mock → salvo over-mock evidente, bien
  • Estética de factories → no importa, si corre corre
  • Consistencia de estilo → si algo falla, Claude lo arregla en masa con una instrucción

Mirar:

  • Si los edge cases están realmente cubiertos
  • Si los nombres de los tests describen el comportamiento esperado real
  • Si falta algún test que debería existir

Lo último es el verdadero valor de la review. Claude cubre los tests "que se le ocurren", pero los que no se le ocurren no se escriben solos. Ahí encaja exactamente la review humana: retroceder de las reglas de negocio a la cobertura faltante.

Un ejemplo concreto: qué caza la review

Abrimos el inicio del describe ".open!" de spec/services/topic_review_service_spec.rb:

describe ".open!" do
  context "when there are eligible jurors" do
    # estado del review ok / post under_review / assignments creados / author notificado / jurors notificados / no se abre dos veces
  end
  context "when there are no eligible jurors" do
    # se crea el review pero no los assignments
  end
  context "when post has no topic" do
    # devuelve nil
  end
end

Parece completo. Pero la regla real de eligible_jurors en el model excluye a tres grupos:

def eligible_jurors
  excluded_ids = [ post.user_id ] + post.reports.pluck(:user_id) + review_votes.where(stage: :initial).pluck(:user_id)
  User.jurors_and_judges.where.not(id: excluded_ids.uniq)
end

Ahora miramos los tests—¿qué test afirma que "el autor del post nunca es seleccionado como jurado"?

Buscas en service_spec.rb y model_spec.rb: no hay. model_spec.rb solo testea unos casos del scope pending_vote_by; no cubre eligible_jurors directamente. En service_spec.rb solo hay un comentario: # Jurors must NOT be the post author—es parte del setup, no una aserción.

Esto es lo que atrapa la review: tres reglas de exclusión (autor / denunciante / ya-votó-en-inicial), y ninguna está protegida por un test. Si alguien refactoriza eligible_jurors y sin querer quita post.user_id de la lista de exclusión, todos los tests existentes pasan—y la producción deja calladamente que los autores se sienten en su propio jurado.

Claude no se equivocó—testeó lo que le pidieron testear. Solo que no preguntó por su cuenta: "¿cada una de estas tres reglas necesita cobertura de test?". Esa pregunta—retroceder de reglas a cobertura—es lo que hace la review.

(Con honestidad: yo mismo me lo pasé por alto en la review original. Solo lo detecté en una segunda auditoría mientras escribía este post. Así que la review tampoco es one-shot—pero sigue siendo 10× mejor que no revisar.)

Los commits posteriores demuestran que el reparto funciona en la práctica

Si "Claude escribe + humano revisa" fuese perfecto, no habría commits de test nuevos tras el inicial. Lo que pasó es más interesante—parches sin reescrituras:

00393fc Add test for finalize! with zero votes (expired review)
3f53304 Add test for finalize! with legacy votes missing reasoning

El primero es regression test post-bug—e8cb2db Default to keep verdict when review expires with zero votes fue el fix, 00393fc el test emparejado. Mismo patrón para el segundo, siguiendo a abaa22e Fix CloseTopicReviewJob failing due to reasoning validation on old votes.

Estos dos commits prueban dos cosas a la vez:

  • La review no pilló el 100 % de los casos—por eso producción expuso dos bugs
  • Pero la arquitectura de tests aguantó y pudimos seguir añadiendo tests sin reestructurar—por eso los commits dicen "Add test for..." y no "Rewrite ... spec"

"Suficientemente bueno + se puede seguir parcheando" es una vara mucho más realista que "perfecto". Buscar review perfecta es lo que impide delegar tests a Claude. Aceptar "suficientemente bueno" es lo que pone en marcha el reparto.

Tests que no conviene delegar del todo a Claude

No todos los tests están bien para handoff total:

  • E2E happy-paths—necesitan mirada de producto. Claude puede escribirlos pero tiende a cubrir "técnicamente completable", perdiendo "dónde se atasca el usuario en realidad"
  • Tests de seguridad—necesitan mentalidad de atacante. Claude es conservador y pierde superficies de ataque no estándar (inyección de palabras clave SQL, strings enormes, unicode alterno)
  • Baselines de rendimiento—necesitan números del entorno de despliegue real. Claude dispara umbrales al azar
  • Reestructuraciones grandes de fixture / factory—son cambios de nivel arquitectónico; vuelve a plan mode, no son algo que caza la review

En esos casos, el humano lidera y Claude asiste.

La configuración por defecto

Para convertir este reparto en un default ejecutable:

  1. Antes de empezar la feature, yo explico las reglas de negocio (no las convenciones de RSpec)
  2. Claude escribe implementación y tests
  3. Corremos los tests. Pasa = seguir. Falla = Claude se arregla solo
  4. Yo reviso:
    • Nada de sintaxis / mock / factory
    • Cobertura: ¿cada regla de negocio tiene al menos un test que la proteja?
    • Interrogar edge cases: "0 filas / null / concurrencia / autz rota"—uno a uno
    • Leer nombres de tests—si el nombre no me dice qué se testea, que Claude lo renombre
  5. Los bugs descubiertos en producción vuelven como regression tests—es el desgaste normal del reparto, no un fracaso

Cierre

Los programadores tienen menos energía mental para leer tests que para leer código. Los tests son repetitivos, mecánicos, agotadores, necesarios. Todo eso describe exactamente el sweet spot de Claude—no se aburre, no se cansa, no recorta en el it número 50.

Tu trabajo no es "escribir tests"—es "asegurarte de que toda regla de negocio esté cubierta por un test". Lo uno es implementación, lo otro es juicio. El juicio se queda contigo; la implementación se va a Claude.

119 specs / 1.562 líneas entregadas en un commit y sobreviviendo más de dos semanas sin rework—no pasó porque yo escriba mejor los tests, sino porque no escribí ninguno. Yo solo hago una cosa que Claude no: decido qué reglas de negocio merecen protección.