IA + TDD en Android: La Nueva Era del Testing
Índice de contenidos
🐢 La Paradoja del TDD
Todos conocemos la teoría de TDD (Test Driven Development):
- Red: Escribe un test que falle.
- Green: Escribe el código mínimo para pasar el test.
- Refactor: Mejora el código sin romper el test.
La realidad: Es difícil. Requiere mucha disciplina y escribir mucho código de “fontanería” (mocks, setups) antes de escribir una línea de lógica productiva. Por eso es común abandonar este enfoque a medio camino.
Este artículo es la versión completa del original de octubre de 2025. Lo he reescrito tras un año aplicándolo con juniors, viendo dónde funciona, dónde falla y cuáles son los prompts concretos que mejor resultado dan. Si quieres ver la perspectiva humana del testing (qué tests vale la pena escribir), mira Code Review con IA — son las dos caras del mismo flujo.
🚀 La IA como Catalizador de TDD
La IA resuelve el problema del “Lienzo en Blanco”. En lugar de escribir el test desde cero, le describes a la IA lo que quieres probar.
El Flujo de Trabajo “AI-First TDD”
Paso 1: Especificación (Prompt) En lugar de escribir código, escribes una especificación en lenguaje natural (o en KDoc).
// Prompt para Copilot/Gemini:
// Genera una clase de test para ShoppingCartViewModel.
// Debe probar:
// 1. Cuando se añade un item, el total se actualiza.
// 2. Cuando se aplica un cupón inválido, emite un estado de error.
// 3. Cuando el stock es 0, no permite añadir.
// Usa MockK y Turbine.
Paso 2: Generación de Tests (Red)
La IA genera ShoppingCartViewModelTest.kt con 3 tests que no compilan (porque el ViewModel no existe o está vacío).
Paso 3: Generación de Implementación (Green)
Ahora le pides a la IA: “Genera la implementación mínima de ShoppingCartViewModel para que estos tests compilen y pasen”.
Paso 4: Refactor Ahora tú, el humano, revisas la implementación. “Mmm, este cálculo de total es ineficiente”. Lo cambias. Corres los tests. Siguen pasando.
Este ciclo funciona mejor que el TDD clásico por una razón contraintuitiva: la IA te quita la fricción del paso 2. Antes, después de escribir el test rojo, tenías que decidir manualmente qué código mínimo escribir para pasarlo. Ahora le dices “pasa estos tests” y la IA propone una implementación. Tú solo revisas.
🧠 Ventajas de este enfoque
- Cobertura por Defecto: Como los tests se escriben primero (por la IA), nunca tienes código sin testear.
- Documentación Viva: Los prompts que usaste para generar los tests sirven como documentación de los requisitos. Pégalos como comentario en el archivo de test, junto al código generado.
- Menor Resistencia Mental: Es más fácil pedirle a la IA “Genera un test para X” que escribirlo tú mismo. Rompe la inercia inicial.
- Tests más completos: Un humano olvida casos edge (null, vacío, Unicode, fechas límite). Una IA con buen prompt los cubre sistemáticamente.
🛠️ Ejemplo Práctico: Validadores
Humano (Comentario):
// Test: EmailValidator
// - debe rechazar emails vacíos
// - debe rechazar emails sin @
// - debe aceptar emails válidos simples
// - debe rechazar emails con caracteres prohibidos
IA (Generación):
@Test
fun `should return false when email is empty`() {
assertFalse(validator.isValid(""))
}
@Test
fun `should return false when email has no at sign`() {
assertFalse(validator.isValid("test.com"))
}
// ... etc
Hasta aquí, el original. Lo que viene es lo que aprendí después de seis meses haciéndolo todos los días.
📋 Prompt template reutilizable
Después de muchos prompts fallidos, este es el que mejor resultado da. Lo guardo como snippet en mi editor:
Genera una clase de test para {CLASE} en {PATH/PAQUETE}.
Casos a cubrir (uno por test):
1. {CASO_HAPPY_PATH}
2. {CASO_ERROR_1}
3. {CASO_ERROR_2}
4. {CASO_EDGE_CASE_NULL}
5. {CASO_EDGE_CASE_VACIO}
Convenciones:
- MockK para mocks
- Turbine para Flow
- runTest para suspend functions
- Nombres de tests en formato "should X when Y"
- Asume JUnit 5
Lo que cambia en cada caso son los {CAMPOS}. Lo demás es fijo. Esto importa: cuando el prompt es estable, la calidad del output es estable. Los prompts “creativos” generan tests creativos (léase: inconsistentes).
🔍 Tests que la IA genera bien vs tests que la IA genera mal
No todos los tests se generan igual de bien. He identificado dos categorías claras:
✅ La IA genera tests útiles cuando:
- La clase tiene responsabilidades claras y pocas (validadores, parsers, formateadores, calculadoras).
- Los casos de uso están bien especificados en el prompt.
- Los tests son unitarios puros sin dependencias complejas (DB, red, sensores).
- El código a testear no usa reflection ni
inlineagresivo.
❌ La IA genera tests frágiles cuando:
- El código a testear depende mucho del tiempo (
System.currentTimeMillis(),LocalDateTime.now()). La IA no sabe qué hacer con ellos. - Hay race conditions legítimas en el código. La IA genera tests que pasan en serial pero fallan bajo carga concurrente.
- El código usa
Companion objectcon side effects. La IA intenta mockear lo que no se puede mockear. - Los tests requieren fixtures grandes (JSON de 500 líneas, datos sintéticos complejos). La IA inventa datos que parecen reales pero no lo son.
Para los casos ”❌”, mejor escribir el test a mano. Y revisar siempre el output de la IA: aunque el test compile y pase, puede estar verificando la implementación en lugar del comportamiento.
🚫 Anti-patterns: tests que solo verifican que la IA no mintió
El peor test que genera la IA es el que solo verifica la implementación, no el comportamiento. Mira este ejemplo:
// ❌ Test inútil: solo verifica que el método se llamó
@Test
fun `viewModel calls repository on init`() {
verify(repo).getUsers() // solo verifica la llamada
}
// ✅ Test útil: verifica el comportamiento que el usuario siente
@Test
fun `loading state is shown then replaced by users on success`() = runTest {
viewModel.uiState.test {
assertEquals(UiState.Loading, awaitItem())
assertEquals(UiState.Success(users), awaitItem())
}
}
El primer test pasa aunque la app esté rota: si el repo devuelve una lista vacía y la UI muestra una pantalla en blanco, el test sigue verde. El segundo test falla cuando la experiencia de usuario está rota.
Regla para revisar tests generados por IA: lee cada assertEquals. Pregúntate: “si este assert falla, ¿se rompe algo que el usuario sentiría?”. Si la respuesta es “no” o “no sé”, borra el test.
🔄 Integración con CI: rechazar PRs sin tests nuevos
Una técnica que me ha funcionado muy bien para equipos donde la disciplina TDD flojea: añadir un check en GitHub Actions que rechace PRs que no añadan tests.
name: Require tests in PR
on:
pull_request:
branches: [main]
jobs:
check-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check new source files have tests
run: |
NEW_SOURCES=$(git diff --name-only origin/main...HEAD -- 'src/main/**/*.kt' | grep -v '/test/' || true)
NEW_TESTS=$(git diff --name-only origin/main...HEAD -- 'src/test/**/*.kt' || true)
if [ -z "$NEW_SOURCES" ]; then
echo "No new source files. Skipping."
exit 0
fi
if [ -z "$NEW_TESTS" ]; then
echo "::error::PR adds source files but no tests. Add at least one test per new class."
exit 1
fi
echo "✓ Source and test changes detected."
El check es laxo: no exige cobertura X%, solo que haya cambios en src/test/ cuando hay cambios en src/main/. Esto fuerza el hábito sin ser draconiano.
📊 Métricas reales después de 6 meses
Esto es lo que medí en un proyecto piloto con tres devs juniors aplicando IA-first TDD durante 6 meses:
| Métrica | Antes (TDD manual) | Después (IA-first TDD) |
|---|---|---|
| Líneas de test por día | ~80 | ~250 |
| Cobertura de tests | 62% | 84% |
| Bugs en producción / mes | 8 | 3 |
| Tiempo entre “feature lista” y “PR mergeado” | 4.2 días | 2.1 días |
| Tiempo dedicado a escribir tests | 35% del tiempo de feature | 20% del tiempo de feature |
La cobertura subió, los bugs bajaron, y el tiempo de feature se redujo porque la fricción psicológica del test desapareció. Los juniors empezaron a disfrutar escribiendo tests porque dejaban de ser una tarea pesada.
Caveat: la métrica de “tiempo de feature” puede engañar. El tiempo TOTAL (incluyendo el prompt engineering y la revisión de tests IA) es mayor que el “tiempo de feature” en sentido estricto. Lo que se reduce es el tiempo hasta valor usable, no el tiempo total invertido.
🎯 Conclusión
TDD ya no es una metodología reservada para puristas con tiempo de sobra. Con la asistencia de IA, TDD se convierte en la forma más rápida y segura de escribir código. La IA elimina el trabajo pesado de escribir el boilerplate del test, dejándote a ti la tarea de definir los casos de uso y la lógica de negocio.
Pero cuidado: TDD con IA sigue siendo TDD. La disciplina del ciclo Red-Green-Refactor no cambia. Lo que cambia es quién escribe el “primer borrador” del test (la IA), no quién decide qué probar (tú) ni quién verifica el resultado (también tú).
Si tuviera que darte un único consejo: invierte una hora en refinar tu prompt template hasta que genere tests que no tengas que reescribir. Esa hora se amortiza en tres días.
Bibliografía y Referencias
- Code Review con IA: Tu Nuevo Agente Incansable — La otra cara del flujo: una vez que los tests existen, cómo revisarlos.
- StateFlow vs SharedFlow: Guía Definitiva para Android — Si vas a testear Flows, necesitas Turbine; este artículo lo cubre.
- Use Cases en Android — Los Use Cases son las unidades mínimas que mejor se testean con IA-first TDD.
- Test-Driven Development by Example — Kent Beck. El libro fundacional. Corto, denso, vale cada capítulo.
- Google Testing Blog — Posts oficiales de Google sobre testing. La serie “Testing on the Toilet” es legendaria.
- MockK documentation — La librería de mocks que uso. La documentación es corta y completa.
- Cash App: Turbine — Para testear Flows de forma legible.
También te puede interesar
Prompting Socrático: Razonamiento Maiéutico en Mobile
Evita el código casi correcto en Android e iOS. Domina el prompting socrático y maiéutico con Gemini, Copilot y Cursor mediante guías de diálogo y código.
Android CLI: Acelerando el desarrollo con agentes IA
Descubre cómo Android CLI está redefiniendo el ecosistema móvil, permitiendo que los agentes de IA desarrollen apps hasta tres veces más rápido.
Android Skills: Guía de IA para el desarrollo sin humo
Aprende cómo el repositorio Android Skills centraliza el contexto para que los agentes de IA construyan apps robustas sin alucinaciones.