Introdução: por que os testes unitários não salvam de tudo
\n\nImagine que você escreveu uma função de ordenação de array. Você a cobriu com uma dúzia de testes unitários: array vazio, um elemento, lista já ordenada, ordem inversa. Os testes estão verdes. Você confia no código. Mas uma semana depois, a produção cai — em um array de 1000 elementos com duplicatas e números negativos. Familiar?
\n\nO teste TypeScript tradicional baseado em exemplos (example-based testing) verifica apenas os cenários que você imaginou. E se o bug estiver escondido em uma combinação que você não considerou? É aqui que entra o teste baseado em propriedades (property-based testing) — uma abordagem que gera automaticamente milhares de entradas aleatórias e verifica os invariantes do seu código.
\n\nNeste artigo, vamos explorar como aplicar testes baseados em propriedades em TypeScript com a biblioteca fast-check, quais propriedades verificar e como isso muda a abordagem de desenvolvimento.
\n\nO que é teste baseado em propriedades?
\n\nEm vez de escrever:
\nit('ordena [3, 1, 2] → [1, 2, 3]', () => {\n expect(sort([3, 1, 2])).toEqual([1, 2, 3]);\n});\n\n\nVocê descreve uma propriedade — uma afirmação que deve ser verdadeira para todas as entradas válidas:
\nit('array ordenado não altera a ordem dos elementos', () => {\n fc.assert(\n fc.property(fc.array(fc.integer()), (arr) => {\n const sorted = sort(arr);\n // Propriedade 1: o comprimento é preservado\n expect(sorted.length).toBe(arr.length);\n // Propriedade 2: cada elemento do array original está presente\n // Propriedade 3: o array está ordenado em ordem crescente\n for (let i = 1; i < sorted.length; i++) {\n expect(sorted[i - 1]).toBeLessThanOrEqual(sorted[i]);\n }\n })\n );\n});\n\n\nA biblioteca fast-check gera centenas de arrays aleatórios (incluindo vazios, com NaN, com números decimais, com valores enormes) e executa seu teste em cada um. Se algum falhar — você descobre o problema.
\n\nPor que isso é necessário especificamente em TypeScript?
\n\nTypeScript oferece tipagem estática, mas ela não garante a correção da lógica. O teste baseado em propriedades em TypeScript preenche a lacuna entre "os tipos coincidem" e "o código funciona corretamente". Isso é especialmente útil para:
\n\n- \n
- Funções de transformação de dados (map, filter, reduce) \n
- Parsers e validadores \n
- Algoritmos com lógica não trivial \n
- Funções que trabalham com valores limite \n
Biblioteca fast-check: início rápido
\n\nInstale o fast-check no projeto:
\nnpm install --save-dev fast-check\n\n\nSintaxe básica:
\nimport fc from 'fast-check';\n\ndescribe('propriedades da função 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\nAqui, fc.array(fc.anything()) é um gerador de arrays arbitrários. Você pode combinar geradores:
- \n
fc.integer()— números inteiros \n fc.string()— strings \n fc.record({ name: fc.string(), age: fc.integer({ min: 0, max: 120 }) })— objetos \n fc.oneof(...)— escolha aleatória entre vários geradores \n
Quais propriedades verificar? 5 exemplos práticos
\n\n1. Invariância (reversibilidade)
\nQualquer operação que tenha uma inversa deve ser reversível:
\nit('serialização e desserialização 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<