Por qué las pruebas comunes mienten y tú les crees
Imagina: escribiste una función, la cubriste con tres pruebas unitarias. Marca verde. CI pasa. Producción — error. ¿Te suena?
El problema de las pruebas unitarias clásicas es que verifican ejemplos concretos. Tú mismo inventas los datos de entrada y también escribes el resultado esperado. Es como un examen donde el estudiante prepara sus propias preguntas. Tarde o temprano, pasarás por alto un caso límite: null, una lista vacía, un número negativo o una cadena con caracteres Unicode.
Aquí entra en escena el testing basado en propiedades (o property-based testing). En lugar de escribir: «si se pasa 5, debe devolver 25», formulas una propiedad: «para cualquier número entero, el cuadrado siempre es no negativo». Y el framework genera cientos de valores aleatorios y verifica esa propiedad.
En este artículo, analizaremos cómo usar pruebas basadas en propiedades en Kotlin con la biblioteca Kotest. Aprenderás a escribir pruebas robustas, encontrar errores en etapas tempranas y dejar de temerle a la refactorización. Esto no es solo teoría aburrida: escribiremos código real que puedes aplicar de inmediato en un proyecto.
¿Qué es el property-based testing y por qué Kotlin?
La idea no es nueva: la popularizó la biblioteca QuickCheck para Haskell en 1999. Pero solo ahora, con el crecimiento de la complejidad de los sistemas backend y los microservicios, el testing basado en propiedades se está convirtiendo en un must-have en el stack del desarrollador.
Kotlin es el lenguaje ideal para este enfoque. ¿Por qué?
- Capacidades funcionales: lambdas, funciones de extensión, inmutabilidad — todo esto simplifica la escritura de propiedades limpias.
- Kotest — un framework potente que soporta de serie pruebas basadas en propiedades, generadores y Shrinking (algoritmo de minimización del ejemplo fallido).
- Compatibilidad con Java: puedes probar código heredado escrito en Java usando los mismos enfoques.
Vamos directo al grano. A continuación, un ejemplo que cambiará tu percepción sobre las pruebas.
Tu primera prueba basada en propiedades en 5 minutos
Supongamos que tenemos una función que verifica si una cadena es un palíndromo:
fun isPalindrome(s: String): Boolean { val cleaned = s.filter { it.isLetterOrDigit() }.lowercase() return cleaned == cleaned.reversed()}Una prueba común se ve así:
@Testfun `test known palindromes`() { assertTrue(isPalindrome("A man, a plan, a canal: Panama")) assertTrue(isPalindrome("racecar")) assertFalse(isPalindrome("hello"))}Todo funciona. Pero, ¿qué pasa si los datos de entrada contienen espacios, dígitos o símbolos? ¿Y si la cadena está vacía? ¿Y si consiste solo de espacios?
Ahora escribamos una prueba basada en propiedades con Kotest:
import io.kotest.core.spec.style.StringSpecimport io.kotest.property.forAllimport io.kotest.property.arbitrary.string
class PalindromePropertyTest : StringSpec({ "Cualquier cadena que pase la limpieza debe ser un palíndromo de sí misma" { forAll<String> { original -> val cleaned = original.filter { it.isLetterOrDigit() }.lowercase() isPalindrome(cleaned) == true } }})¿Qué está pasando aquí? forAll<String> genera cadenas aleatorias: vacías, con espacios, con Unicode, con dígitos. Y para cada una verifica la propiedad. Si al menos una cadena viola la afirmación, la prueba falla y Kotest muestra el ejemplo mínimo que la reproduce.
Ya en esta etapa, puedes encontrar errores. Por ejemplo, si la cadena consiste solo de espacios — filter devolverá una cadena vacía, y isPalindrome("") devolverá true. Una prueba común habría pasado esto por alto.