Maximize a performance dos seus testes com Jest e Vitest

Maximize a performance dos seus testes com Jest e Vitest

2 de outubro de 2024 - #jest #vitest #testes #performance - 6 min

Introdução

Se você já criou testes automatizados para seu projeto, com certeza conhece o jest. Não é por acaso: O jest é uma das ferramentas de teste mais utilizadas no mercado e amplamente difundida pela comunidade de desenvolvimento.

Mas, independentemente da tecnologia, um código mal otimizado vai rodar mal. E se você estiver em um projeto grande, o simples ato de executar testes pode demorar o tempo de tomar um café (ou vários).

Eu não tenho nada contra sua pausa pro cafezinho, estou tomando um enquanto escrevo este artigo, mas ultimamente eu entrei de cabeça em uma missão: otimizar testes para que sejam o mais rápidos possível.

Bora pro código!

O cenário caótico

Imagine um projeto em TypeScript usando Fastify como servidor web. Os testes são simples, mas mal otimizados: cada um deles cria uma nova instância do servidor e faz uma chamada HTTP. Em projetos maiores, essa abordagem pode resultar em testes lentos e desperdício de recursos.

Aqui está a configuração inicial para rodar os testes, usando o Jest:

//slow jest config import { type Config } from '@jest/types'; const config: Config.InitialOptions = { preset: 'ts-jest', transform: { '^.+\\.tsx?$': 'ts-jest' }, testMatch: [ '**/slow.*' ] }; export default config;

Você pode notar que o Jest usa uma outra biblioteca chamada ts-jest para conseguir ler nossos testes utilizando typescript. Esse já é nosso primeiro ponto de melhoria. Além disso podemos definir melhor nosso target, ou seja, onde o Jest vai buscar nossos testes.

Quanto aos testes, criei um arquivo com 300 testes iguais:

it('should pass', async () => { const app = await buildServer() const response = await app.inject({ method: 'GET', url: '/check', }) expect(response.statusCode).toEqual(200) expect(response.json()).toEqual({ hello: 'world' }); });

Nosso erro premeditado aqui é que cada teste recria a instância do Fastify para cada execução, portanto, 300 vezes. Você pode pensar que isso é muito específico mas na verdade é uma forma de mostrar problemas que acontecem de várias formas e causam lentidão, por exemplo:

  • Instâncias sendo recriadas (como no exemplo)
  • Alterações no banco de dados
  • Mocks
  • Validações desnecessárias

Otimizando o Jest

Vamos adicionar algumas configurações.

  • maxWorkers: Define quantos núcleos da CPU serão usados para os testes em paralelo, aqui configurado para usar 50%.
  • testEnvironment: Define o ambiente de execução, que neste caso é o "node".
  • testPathIgnorePatterns: Ignora diretórios como node_modules e src.

Nossa grande estrela aqui é o @swc/jest. Diferente do ts-jest, que transpila TypeScript usando o compilador do próprio TypeScript, o @swc/jest utiliza o SWC, um compilador escrito em Rust que é muito mais rápido para transformar arquivos TypeScript em JavaScript. O Rust, sendo uma linguagem de baixo nível, oferece desempenho superior, especialmente em grandes bases de código.

Além disso, configuramos também um caminho bem específico para o jest encontrar nosso teste em testMatch. Sei que não é um caso real mas você também poderia mudar para algo como '**/*.test.ts' e isso faria com que arquivos com outras extensões fossem ignorados.

// Fast jest config import { type Config } from '@jest/types'; const config: Config.InitialOptions = { cache: true, maxWorkers: '50%', testEnvironment: "node", testPathIgnorePatterns: [ 'node_modules', 'src' ], transform: { '^.+\\.tsx?$': '@swc/jest' }, testMatch: [ '**/slow.test.ts' ] }; export default config;

Feito isso, vamos fazer a comparação de ambas as configurações de Jest executando nossos testes que ainda tem o problema de otimização.

TentativaSlow JestFast Jest
13.728s2.939s
23.701s2.982s
33.687s2.904s
43.763s2.934s
53.730s2.911s

Médias:

  • Slow Jest: 3.722s
  • Fast Jest: 2.934s

Tivemos uma melhora de 21,17% com nosso Jest otimizado. Agora vamos corrigir o problema no nosso teste.

Otimizando o teste

Como disse antes, nosso caso não é tão complexo. O que vamos fazer aqui é mover a criação da instancia para nosso beforeAll e reutilizar em todos os testes. O importante aqui é frisar que diferentes projetos podem gerar situações parecidas com essa e depende de você ter o senso crítico para entender e corrigir essas falhas.

describe('fast', () => { let app: FastifyInstance; beforeAll(async () => { app = await buildServer() }) it('should pass', async () => { const response = await app.inject({ method: 'GET', url: '/check', }) expect(response.statusCode).toEqual(200) expect(response.json()).toEqual({ hello: 'world' }); }) })

Com o problema corrigido vamos a uma nova execução.

TentativaSlow JestFast Jest
13.318s2.593s
23.316s2.552s
33.302s2.538s
43.328s2.567s
53.278s2.566s

Médias:

  • Slow Jest: 3.3044s
  • Fast Jest: 2.5632s

Agora temos uma nova melhoria nos tempos. Comparando a média do nosso pior cenário (3.722s) com o melhor cenário atual (2.5632s) temos uma melhoria de 31.1%.

Podemos melhorar?

Se você não tem a opção de mudar o Jest por outra ferramenta acredito que essa melhoria seja algo ótimo para você. Mas podemos ir além utilizando o Vitest.

Expandindo os horizontes com Vitest

Se você nunca ouviu falar do Vitest vou resumir, é uma biblioteca de testes compatível com o Jest e que usa o EsBuild por baixo dos panos. Sendo assim, você quase não precisa refatorar os testes escritos pro jest e o arquivo de configuração é muito simples e no nosso caso é opcional. Mas vou deixar as configurações que adicionei no nosso projeto:

import { defineConfig } from 'vitest/config' export default defineConfig({ test: { globals: true, environment: 'node', include: ['tests/fast.test.ts'] } })

Indo direto ao ponto, vou executar apenas o teste que foi corrigido e vamos comparar os resultados.

TentativaSlow JestFast JestVitest
13.287s2.575s1.690s
23.295s2.564s1.770s
33.279s2.589s1.711s
43.311s2.555s1.708s
53.291s2.567s1.757s

Médias:

  • Slow Jest: 3.293s
  • Fast Jest: 2.570s
  • Vitest: 1.727s

Ao comparar os tempos de execução, o Vitest se destaca como uma alternativa significativamente mais rápida, superando o melhor tempo do Jest em 32%. No nosso primeiro caso de teste, essa diferença é ainda mais impressionante, alcançando um ganho de 50%. Isso demonstra como a escolha da ferramenta certa pode impactar diretamente a produtividade, reduzindo o tempo de feedback e acelerando o desenvolvimento sem comprometer a confiabilidade dos testes.