Test-Driven Development (TDD) es una metodología de desarrollo en la que las pruebas se escriben antes de la implementación del código. El desarrollador primero formula el comportamiento esperado como una prueba fallida, luego escribe el código mínimo para que pase y, después, refactoriza el resultado. Según Martin Fowler (2023), TDD no es una técnica de prueba, sino una técnica de diseño que disciplina la arquitectura y reduce la cantidad de defectos en la etapa de escritura del código.
Puntos clave
Test-Driven Development es una práctica de desarrollo de software en la que las pruebas automatizadas determinan la escritura del código de producción. A diferencia del enfoque tradicional, donde el código se escribe y luego se prueba, TDD invierte la secuencia: primero se escribe la prueba, luego el código que la pasa.
El fundador de TDD es Kent Beck, quien formuló esta práctica a finales de la década de 1990 como parte de la metodología Extreme Programming (XP). En el libro “Test-Driven Development: By Example” (2002), Beck describió cinco reglas de TDD que se volvieron canónicas: escribe la prueba antes del código de producción, escribe solo el código necesario para pasar la prueba y refactoriza después de cada ciclo.
El primer principio es que la prueba define la interfaz. El desarrollador se ve obligado a pensar en cómo se usará el componente antes de pensar en cómo se implementa. Esto forma una API limpia desde el principio.
El segundo principio es la implementación mínima. Cuando la prueba está escrita, el desarrollador escribe exactamente el código de producción necesario para que pase, ni una línea más. Esto evita la abstracción prematura y la complejidad excesiva, que Martin Fowler denomina Speculative Generality.
La diferencia clave entre TDD y las pruebas a posteriori es la disciplina de secuencia. En TDD, la prueba no solo verifica el código, sino que guía su estructura. Según un estudio de Microsoft Research (Nagappan et al., 2008), los equipos que aplican TDD muestran una reducción del 40–90% en la densidad de defectos en comparación con los equipos que utilizan el enfoque tradicional.
El ciclo Red-Green-Refactor es una secuencia de tres pasos que se repite para cada nueva prueba. Red: escribir una prueba que no pasa. Green: escribir el código mínimo para que la prueba pase. Refactor: mejorar el código sin cambiar su comportamiento.
El desarrollador escribe una prueba que verifica una funcionalidad aún no implementada. En esta etapa, la prueba debe fallar, lo que confirma que la prueba realmente verifica algo. En el entorno de desarrollo de Android, el framework JUnit 5 muestra un indicador rojo para las pruebas fallidas, lo que dio nombre a la fase.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
En esta etapa se escribe el código de producción mínimo suficiente para que la prueba pase. Sin redundancia, solo lo necesario para el indicador verde. Si la implementación puede ser una constante, que sea una constante. La refactorización ocurrirá en el siguiente paso cuando aparezcan nuevas pruebas.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
La prueba verde es un seguro para la refactorización. El desarrollador puede reescribir la implementación, optimizar el rendimiento o mejorar la legibilidad, con la confianza de que la prueba detectará de inmediato cualquier desviación del comportamiento esperado. En el desarrollo móvil Android, esta fase es especialmente importante para extraer interfaces comunes y reducir la duplicación de código.
La aplicación de TDD en proyectos móviles ofrece beneficios medibles, confirmados tanto por la investigación académica como por la práctica de los principales estudios de desarrollo.
Un estudio de IBM (Bhat & Nagappan, 2006) en cuatro proyectos industriales mostró que los equipos que usan TDD producen un 40% menos de defectos en comparación con equipos similares que trabajan con el enfoque tradicional. Para el desarrollo móvil, donde el costo de corregir un error después del lanzamiento en Google Play es significativamente mayor que en la etapa de escritura del código, esta métrica es crítica.
Las pruebas escritas con TDD sirven como documentación viva de la API. Un desarrollador que se incorpora al proyecto puede leer las pruebas y entender cómo debe usarse cada componente. Esto es especialmente valioso en situaciones de alta rotación del equipo, un desafío típico de los estudios móviles.
Una cobertura de código que supera el 90% permite a los desarrolladores refactorizar sin miedo a romper algo. Google, en su libro “Software Engineering at Google” (2020), califica la cobertura de pruebas como un factor clave para mantener la base de código limpia en proyectos con millones de líneas de código.
El ecosistema de TDD en el desarrollo móvil incluye herramientas para pruebas unitarias, mocking y verificación de componentes de UI, tanto para Android como para iOS.
| Herramienta | Plataforma | Propósito |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Framework básico para pruebas unitarias |
| Mockito | Android | Creación de objetos mock y verificación de llamadas |
| MockK | Android (Kotlin) | Mocking con sintaxis Kotlin-first y soporte para corrutinas |
| Turbine | Android | Pruebas de Kotlin Flow y flujos reactivos |
| XCTest | iOS (Swift) | Framework de pruebas estándar |
Para proyectos Android con Kotlin, el stack estándar incluye JUnit 5 + MockK. MockK es preferible a Mockito porque admite características de primera clase de Kotlin (clases selladas, corrutinas y funciones suspend) sin configuración adicional.
En el desarrollo iOS, TDD se implementa a través de XCTest, el framework integrado de Apple que proporciona aserciones, clases de prueba e integración con CI/CD mediante Xcode Server o GitHub Actions. Para mocking en iOS se utilizan librerías como Cuckoo y OHHTTPStubs.
Veamos un escenario real de TDD en Kotlin para Android: pruebas de un repositorio de usuarios. Primero escribimos la prueba, luego la implementación que la supera.
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val dao = mockk<UserDao>()
private val repo = UserRepository(api, dao)
fun `when api returns user then cache and emit`() = runTest {
val user = User(1, "Alice")
coEvery { api.getUser(1) } returns user
every { dao.insert(user) } returns Unit
val result = repo.getUser(1)
assertEquals(user, result)
verify { dao.insert(user) }
}
}
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUser(id: Int): User {
val user = api.getUser(id)
dao.insert(user)
return user
}
}
Después de superar la primera prueba, añadimos una segunda que verifica el comportamiento durante un error de red. Ahora la prueba determina que cuando la API falla, el repositorio debe devolver datos de la caché.
fun `when api fails then return cached user`() = runTest {
val cached = User(1, "Cached Alice")
coEvery { api.getUser(1) } throws IOException()
every { dao.getById(1) } returns cached
val result = repo.getUser(1)
assertEquals(cached, result)
}
La transición a TDD conlleva errores típicos que pueden anular todos los beneficios de la metodología. Comprender estos errores ayuda a los equipos a adoptar la práctica de manera más efectiva.
El primer y más común antipatrón es probar demasiada funcionalidad en una sola prueba. Una prueba debe verificar exactamente una afirmación. Si una prueba falla, el desarrollador debe saber exactamente qué se rompió sin necesidad de depuración adicional.
El segundo error es escribir una prueba que pasa desde el principio. Si la prueba nunca fue roja al menos una vez, no hay certeza de que realmente verifique algo. Regla: nunca confíes en una prueba que no hayas visto fallar.
El tercer error común es detenerse en la fase verde. La refactorización no es opcional, sino un paso obligatorio del ciclo. Sin ella, la base de código se degrada, las pruebas se vuelven frágiles y se pierden los beneficios de TDD.
Preguntas frecuentes
TDD es ante todo una técnica de diseño, no de prueba. Las pruebas en TDD desempeñan el papel de una especificación: definen la API del componente antes de su implementación. El propio Kent Beck llama a TDD “una disciplina de diseño, no de prueba”.
Según estudios de Microsoft Research, los equipos necesitan de 3 a 6 meses de práctica continua para que TDD se convierta en un hábito. En las primeras 2–3 semanas, la productividad cae entre un 15–30%, pero después de la adaptación vuelve al nivel original o lo supera gracias a la reducción del tiempo de depuración.
Sí, pero con limitaciones. Para la lógica de UI (ViewModel, State), TDD es directamente aplicable. Para componentes visuales (Compose UI, SwiftUI Views), las pruebas de instantáneas (snapshot testing) complementan a TDD pero no lo reemplazan. Se recomienda separar la lógica de negocio de la presentación.
Para código heredado, la estrategia recomendada son las “pruebas de caracterización”, donde las pruebas se escriben sobre el comportamiento existente y luego se refactoriza el código. Este enfoque se describe en el libro de Michael Feathers “Working Effectively with Legacy Code” (2004) y permite introducir TDD de forma gradual.
TDD y Clean Architecture se refuerzan mutuamente. La arquitectura limpia requiere límites claros entre capas, y TDD obliga al desarrollador a diseñar esos límites a través de pruebas. La capa de dominio se prueba de forma aislada con dependencias mock, y la capa de datos mediante pruebas de integración.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también