Pruebas basadas en propiedades en Rust: el arte de encontrar errores que no buscabas
\n\n¿Alguna vez has escrito pruebas unitarias, las has ejecutado, todo en verde, y luego la aplicación falló con la primera entrada no estándar de un usuario? Te suena. Las pruebas clásicas son como comprobar si una cerradura funciona insertando solo tu propia llave. ¿Y qué pasa si un atacante (o simplemente el azar) trae un destornillador, un clip o un trozo de alambre?
\n\nEn Rust, un lenguaje famoso por su seguridad, existe una herramienta poderosa que no solo permite verificar escenarios específicos, sino demostrar que el código se comporta correctamente para todos los posibles datos de entrada. Esto es property-based testing. Y hoy lo analizaremos en la práctica, con ejemplos reales, para que puedas aplicar estos conocimientos de inmediato en tus proyectos.
\n\n¿Qué son las pruebas basadas en propiedades y en qué se diferencian de las normales?
\n\nImagina que pruebas una función de ordenamiento. Una prueba unitaria normal se ve así:
\n\nfn test_sort() {\n let mut arr = vec![3, 1, 2];\n arr.sort();\n assert_eq!(arr, vec![1, 2, 3]);\n}\n\n¿Funciona? Funciona. Pero, ¿y si el array ya está ordenado? ¿Y si está vacío? ¿Y si tiene 10,000 elementos en orden inverso? Escribir pruebas para cada caso manualmente es un camino hacia el infinito.
\n\nProperty-based testing invierte el enfoque. En lugar de especificar datos de entrada concretos, describes una propiedad (property) que debe cumplirse para cualquier entrada válida. Y una biblioteca especial (por ejemplo, proptest o quickcheck) genera cientos y miles de casos de prueba aleatorios, intentando encontrar el único que rompa tu propiedad.
Para el ordenamiento, la propiedad podría ser: «El resultado del ordenamiento tiene la misma longitud que el array original, y cada elemento del array original aparece exactamente una vez en el resultado». O más simple: «El resultado está ordenado de forma ascendente».
\n\nPor qué Rust es el entorno ideal para las pruebas basadas en propiedades
\n\nRust ya detecta muchos errores en tiempo de compilación (gestión de memoria, condiciones de carrera). Pero hay una clase de errores que el compilador no ve: errores lógicos, manejo incorrecto de casos límite, comportamiento inesperado con combinaciones específicas de datos. Aquí es donde entran en juego las pruebas basadas en propiedades en Rust.
\n\nComplementan perfectamente el sistema de tipos: los tipos garantizan que los datos tengan la estructura correcta, y las pruebas basadas en propiedades garantizan que la lógica de procesamiento de esos datos sea correcta para todo el espacio de valores posibles.
\n\nHerramientas: proptest vs quickcheck
\n\nEn el ecosistema de Rust hay dos frameworks principales: proptest y quickcheck. Ambos hacen lo mismo, pero con filosofías diferentes.
\n\n- \n
- quickcheck — es más antiguo y minimalista. Funciona a través del trait
Arbitrary. Escribes una función que recibe datos aleatorios y la prueba se ejecuta muchas veces. \n - proptest — es moderno, potente, con muchas estrategias de generación de datos. Permite ajustar finamente qué datos generar y sabe cómo «reducir» (shrink) un ejemplo fallido al caso mínimo reproducible. Es el estándar de oro hoy en día. \n
Usaremos proptest porque da más control y está mejor documentado. Lo instalamos:
\n\n# Cargo.toml\n[dev-dependencies]\nproptest = "1.4"\n\nEscribiendo nuestra primera prueba basada en propiedades en Rust
\n\nVolvamos a nuestro ordenamiento. Escribamos una prueba que verifique que el resultado realmente está ordenado:
\n\nuse proptest::prelude::*;\n\nfn is_sorted<T: Ord>(slice: &[T]) -> bool {\n slice.windows(2).all(|w| w[0] <= w[1])\n}\n\nproptest!