Introducción: por qué las pruebas unitarias no salvan de todo
\n\nImagina que escribiste una función de ordenamiento de arrays. La cubriste con una docena de pruebas unitarias: array vacío, un elemento, lista ya ordenada, orden inverso. Las pruebas pasan en verde. Confías en el código. Pero una semana después, la producción falla — con un array de 1000 elementos con duplicados y números negativos. ¿Te suena familiar?
\n\nLas pruebas en TypeScript tradicionales basadas en ejemplos (example-based testing) solo verifican los escenarios que imaginaste. ¿Y si el error se esconde en una combinación en la que no pensaste? Aquí entra en escena el testing basado en propiedades (property-based testing) — un enfoque que genera automáticamente miles de datos de entrada aleatorios y verifica los invariantes de tu código.
\n\nEn este artículo, analizaremos cómo aplicar property-based testing en TypeScript con la librería fast-check, qué propiedades vale la pena verificar y cómo esto cambia el enfoque del desarrollo.
\n\n¿Qué son las pruebas basadas en propiedades?
\n\nEn lugar de escribir:
\nit('ordena [3, 1, 2] → [1, 2, 3]', () => {\n expect(sort([3, 1, 2])).toEqual([1, 2, 3]);\n});\n\n\nDescribes una propiedad (property) — una afirmación que debe ser verdadera para todos los datos de entrada válidos:
\nit('el array ordenado no cambia el orden de los elementos', () => {\n fc.assert(\n fc.property(fc.array(fc.integer()), (arr) => {\n const sorted = sort(arr);\n // Propiedad 1: la longitud se conserva\n expect(sorted.length).toBe(arr.length);\n // Propiedad 2: cada elemento del array original está presente\n // Propiedad 3: el array está ordenado de forma ascendente\n for (let i = 1; i < sorted.length; i++) {\n expect(sorted[i - 1]).toBeLessThanOrEqual(sorted[i]);\n }\n })\n );\n});\n\n\nLa librería fast-check generará por sí misma cientos de arrays aleatorios (incluyendo vacíos, con NaN, con números decimales, con valores enormes) y ejecutará tu prueba en cada uno. Si alguna falla — te enterarás del problema.
\n\n¿Por qué es necesario específicamente en TypeScript?
\n\nTypeScript proporciona tipado estático, pero no garantiza la corrección de la lógica. El testing basado en propiedades en TypeScript cierra la brecha entre «los tipos coinciden» y «el código funciona correctamente». Es especialmente útil para:
\n\n- \n
- Funciones de transformación de datos (map, filter, reduce) \n
- Analizadores sintácticos y validadores \n
- Algoritmos con lógica no trivial \n
- Funciones que trabajan con valores límite \n
Librería fast-check: inicio rápido
\n\nInstala fast-check en el proyecto:
\nnpm install --save-dev fast-check\n\n\nSintaxis básica:
\nimport fc from 'fast-check';\n\ndescribe('propiedades de la función reverse', () => {\n it('reverse(reverse(arr)) === arr', () => {\n fc.assert(\n fc.property(fc.array(fc.anything()), (arr) => {\n expect(reverse(reverse(arr))).toEqual(arr);\n })\n );\n });\n});\n\n\nAquí fc.array(fc.anything()) es un generador de arrays arbitrarios. Puedes combinar generadores:
- \n
fc.integer()— números enteros \n fc.string()— cadenas de texto \n fc.record({ name: fc.string(), age: fc.integer({ min: 0, max: 120 }) })— objetos \n fc.oneof(...)— selección aleatoria entre varios generadores \n
¿Qué propiedades verificar? 5 ejemplos prácticos
\n\n1. Invariancia (reversibilidad)
\nCualquier operación que tenga una inversa debe ser reversible:
\nit('serialización y deserialización de JSON', () => {\n fc.assert(\n fc.property(fc.object(), (obj) => {\n const json = JSON.stringify(obj);\n const parsed = JSON.parse(json);\n expect(parsed).toEqual(obj);\n })\n );\n});\n<