Independentemente do nível de habilidade, poucos desenvolvedores escrevem código de qualidade na primeira tentativa. É por isso que desenvolvedores experientes levam tempo para refatorar seu código, mesmo depois de terem uma solução inicial de trabalho. Refatores também acontecem com o tempo, conforme o aplicativo cresce e evolui. Os detalhes da implementação podem mudar regularmente.

Ao refatorar, é essencial ter bons testes para evitar regressões e evitar cometer erros simples. Mas e se seus testes não forem tão úteis quanto você pensa que são? Se você não tomar cuidado ao escrever seus testes, pode se ver nessa mesma armadilha.

Neste artigo, daremos uma olhada em um componente React simples e observaremos como nosso conjunto de testes responde a refatores simples. Usaremos a Biblioteca de teste do React para destacar o poder de testar como o usuário interage com o aplicativo. Também usaremos Enzyme para explorar as armadilhas dos detalhes de implementação de teste e as dores de cabeça esta abordagem pode trazer.

Configurando nosso aplicativo de exemplo

Primeiro, criaremos um aplicativo de contador simples, conforme mostrado abaixo. Nosso aplicativo conterá algum texto de introdução seguido por uma mensagem exibindo quantas vezes o botão foi clicado. Abaixo, incluiremos o botão.

Exemplo de contador de aplicativo

Cada vez que o usuário clica no botão, o contador é incrementado e o mesmo ocorre com o texto da mensagem na tela. Você pode ver isso na imagem abaixo.

Exemplo de aplicativo de contador com botão clicado três vezes

Bastante simples, certo? Aqui está o JavaScript necessário para implementar este componente:

 import React, {Component} de'react';
import'./App.css'; class App extends Component { estado={ contagem: 0, } handleClick=()=> { this.setState (prevState=> ({contagem: prevState.count + 1})) } render () { Retorna ( 

Exemplo de contador

Usaremos um componente de contador simples para ver como é fácil testar nosso código usando React Testing Library and Enzyme.

Também veremos como a refatoração é fácil ou difícil com cada biblioteca de teste.


Você clicou {this.state.count} vezes

); } } exportar aplicativo padrão;

Para ter certeza de que nosso contador está funcionando corretamente, escrevi testes que executo com Jest . Nosso conjunto de testes contém dois arquivos: um escrito usando a Biblioteca de teste do React e um escrito usando Enzyme .

Os testes da React Testing Library são assim:

 import {fireEvent, render, screen} de'@ testing-library/react';
importar aplicativo de'./App'; descrever ('App', ()=> { it ('renderiza o texto do cabeçalho, a contagem de cliques e um botão', ()=> { render (); expect (screen.getByText ('Exemplo de contador')). toBeInTheDocument (); expect (screen.getByText ('Você clicou 0 vezes')). toBeInTheDocument (); expect (screen.getByText ('Click me')). toBeInTheDocument (); }); it ('incrementa o contador quando clicado', ()=> { render (); expect (screen.getByText ('Você clicou 0 vezes')). toBeInTheDocument (); fireEvent.click (screen.getByText ('Click me')); expect (screen.getByText ('Você clicou 1 vezes')). toBeInTheDocument (); });
}); 

Você pode ver que verificamos a presença de texto essencial na página no primeiro teste e como testamos o comportamento de clicar no botão no segundo teste. Esses testes se concentram no que o usuário pode ver e como ele pode interagir com o aplicativo.

Por outro lado, os testes de enzima têm a seguinte aparência:

 importar {raso} da'enzima';
importar aplicativo de'./App'; descrever ('App', ()=> { it ('renderiza o texto do cabeçalho, a contagem de cliques e um botão', ()=> { invólucro const=raso (); expect (wrapper.find ('h1'). text ()). toEqual ('Exemplo de contador'); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 0 vezes'); expect (wrapper.find ('button'). text ()). toEqual ('Click me'); }); it ('incrementa o contador quando o método handleClick é chamado', ()=> { invólucro const=raso (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.instance (). handleClick (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 1 vezes'); }); it ('atualiza a mensagem de saída na IU quando o estado de contagem é alterado', ()=> { invólucro const=raso (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.setState ({contagem: 1}); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 1 vezes'); }); it ('incrementa o contador quando clicado', ()=> { invólucro const=raso (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.find ('botão'). simulate ('clique'); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 1 vezes'); });
}); 

Observe que os testes Enzyme têm uma abordagem diferente. Em vez de se concentrar na experiência do usuário final, esses testes se concentram mais nos detalhes de implementação.

Por exemplo, eles direcionam a mensagem de saída pelo atributo id do parágrafo. Eles até mesmo manipulam diretamente o valor count do estado do componente e chamam diretamente o método handleClick do botão em alguns dos testes.

Essas não são coisas que um usuário pode fazer (a menos, é claro, que seja um desenvolvedor brincando com as ferramentas de desenvolvedor do navegador!).

Se executarmos esses testes, veremos que todos os seis testes foram aprovados. Eles até nos dão 100% de cobertura de código. Legal!

Todos os testes aprovados

Para ser claro, estamos duplicando a cobertura do teste até certo ponto, escrevendo dois arquivos de teste separados para um único componente. Na realidade, uma base de código geralmente escolheria uma biblioteca de teste ou outra, Biblioteca de Teste React ou Enzyme.

A escolha da biblioteca influenciaria fortemente a estratégia de teste e como os testes são escritos. Mas, usar essas duas bibliotecas de teste lado a lado nos permitirá observar algumas tendências interessantes à medida que fazemos alterações em nosso aplicativo.

Para aqueles que estão acompanhando, todo o código deste aplicativo de contador pode ser encontrado no Github .

Agora que apresentamos o aplicativo e verificamos que ele funciona, estamos prontos para fazer algumas refatorações! É importante observar que todos esses refatores mudarão apenas os detalhes de implementação-a funcionalidade e o comportamento reais do aplicativo permanecerão os mesmos.

Refatorar 1: alterar o nome da propriedade de estado do componente

Nossa primeira refatoração mudará a propriedade de estado do componente count para counterValue . Com essa simples mudança de nomenclatura, nosso código-fonte agora se parece com isto:

 import React, {Component} de'react';
import'./App.css'; class App extends Component { estado={ counterValue: 0, } handleClick=()=> { this.setState (prevState=> ({counterValue: prevState.counterValue + 1})) } render () { Retorna ( 

Exemplo de contador

Usaremos um componente de contador simples para ver como é fácil testar nosso código usando React Testing Library and Enzyme.

Também veremos como a refatoração é fácil ou difícil com cada biblioteca de teste.


Você clicou {this.state.counterValue} vezes

); } } exportar aplicativo padrão;

Vamos ver o que acontece quando executamos nosso pacote de testes:

Um alerta de teste falhou

Oh não! Um dos nossos testes falhou! Quebramos algo em nosso aplicativo? Uma rápida verificação manual em nossa interface de usuário confirma para nós que nosso aplicativo está de fato funcionando bem. Então o que aconteceu?

Se você observar o resultado do teste acima, verá que a linha 22 de nosso arquivo de teste Enzyme modifica diretamente o estado do componente para ter um valor de {count: 1} . Isso teria funcionado antes, mas lembre-se de que, quando refatoramos nosso nome de propriedade, mudamos de count para counterValue . Para que o teste seja aprovado, podemos atualizar nosso teste da seguinte forma:

 it ('atualiza a mensagem de saída na IU quando o estado de contagem é alterado', ()=> { invólucro const=raso (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.setState ({counterValue: 1}); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 1 vezes');
}); 

Assim é melhor.

Refatorar 2: Alterar o nome do método do manipulador de cliques do botão

Vamos fazer outra mudança simples. Neste exemplo de refatoração, renomearemos o método manipulador de clique de handleClick para incrementCounter . Nosso código de aplicativo agora se parece com este:

 import React, {Component} de'react';
import'./App.css'; class App extends Component { estado={ counterValue: 0, } incrementCounter=()=> { this.setState (prevState=> ({counterValue: prevState.counterValue + 1})) } render () { Retorna ( 

Exemplo de contador

Usaremos um componente de contador simples para ver como é fácil testar nosso código usando React Testing Library and Enzyme.

Também veremos como a refatoração é fácil ou difícil com cada biblioteca de teste.


Você clicou {this.state.counterValue} vezes

); } } exportar aplicativo padrão;

Depois de fazer essa alteração, podemos executar nossos testes novamente:

Outro alerta de falha de teste

Outra falha no teste! Hmmm… mas nosso aplicativo ainda está funcionando corretamente quando verificamos a IU novamente. Parece familiar?

Desta vez, é o nosso teste Enzyme reclamando que handleClick não é uma função, o que faz sentido porque alteramos o nome do método para incrementCounter . Agora podemos atualizar isso em nosso teste:

 it ('incrementa o contador quando o método handleClick é chamado', ()=> { invólucro const=raso (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.instance (). incrementCounter (); expect (wrapper.find ('# output'). text ()). toEqual ('Você clicou 1 vezes');
}); 

Como esperado, nossos testes estão passando novamente.

Refatorar 3: Alterar um valor de atributo de ID

Vejamos mais um exemplo. O elemento de parágrafo que contém nossa mensagem de saída atualmente possui um atributo de ID com um valor de output . Em vez disso, vamos alterar esse valor para clickCountMessage . O código do aplicativo agora se parece com este:

 import React, {Component} de'react';
import'./App.css'; class App extends Component { estado={ counterValue: 0, } incrementCounter=()=> { this.setState (prevState=> ({counterValue: prevState.counterValue + 1})) } render () { Retorna ( 

Exemplo de contador

Usaremos um componente de contador simples para ver como é fácil testar nosso código usando React Testing Library and Enzyme.

Também veremos como a refatoração é fácil ou difícil com cada biblioteca de teste.


Você clicou {this.state.counterValue} vezes

); } } exportar aplicativo padrão;

E vamos fazer nossos testes mais uma vez. Você consegue adivinhar o que vai acontecer?

Quatro alertas de teste falhados

Fracasso! Todos os quatro testes de Enzyme falharam porque cada um deles depende da presença de um elemento que pode ser selecionado com o seletor CSS #output . Mas com a nossa mudança no ID do elemento, o seletor CSS adequado agora é #clickCountMessage . Vamos atualizar nossos testes para refletir essa mudança:

 importar {raso} da'enzima';
importar aplicativo de'./App'; descrever ('App', ()=> { it ('renderiza o texto do cabeçalho, a contagem de cliques e um botão', ()=> { invólucro const=raso (); expect (wrapper.find ('h1'). text ()). toEqual ('Exemplo de contador'); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 0 vezes'); expect (wrapper.find ('button'). text ()). toEqual ('Click me'); }); it ('incrementa o contador quando o método handleClick é chamado', ()=> { invólucro const=raso (); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.instance (). incrementCounter (); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 1 vezes'); }); it ('atualiza a mensagem clickCountMessage na IU quando o estado de contagem é alterado', ()=> { invólucro const=raso (); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.setState ({counterValue: 1}); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 1 vezes'); }); it ('incrementa o contador quando clicado', ()=> { invólucro const=raso (); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 0 vezes'); wrapper.find ('botão'). simulate ('clique'); expect (wrapper.find ('# clickCountMessage'). text ()). toEqual ('Você clicou 1 vezes'); });
}) 

Lá vamos nós. Agora tudo voltou ao normal. Que alívio!

Lições aprendidas na refatoração

Se você tem prestado atenção, notará alguns padrões em todas as nossas falhas de teste.

Primeiro, os únicos testes que falharam foram aqueles que faziam parte do nosso conjunto de testes Enzyme. Você deve se lembrar que isso ocorre porque os testes Enzyme se concentram nos detalhes de implementação, enquanto os testes da React Testing Library se concentram na experiência do usuário.

Em segundo lugar, mesmo quando tínhamos falhas de teste, o aplicativo ainda estava funcionando bem. Isso diminui nossa confiança em nossos testes. Quando um teste falha, devemos nos perguntar: “Há algo realmente errado ou apenas temos um teste que precisa ser atualizado?”

Terceiro, é uma grande dor atualizar nossos testes apenas porque renomeamos uma variável ou função. Não seria bom se nossos testes não se preocupassem com esses tipos de detalhes de implementação?

A boa notícia é que existe uma maneira melhor. Na verdade, essa é a filosofia central por trás da React Testing Library: “ Quanto mais seus testes se assemelham à forma como o software é usado, mais confiança eles podem lhe dar. ”

Ao escrever testes que enfocam o que os usuários podem ver e fazer, podemos desenvolver um conjunto de testes mais confiável. Economizamos horas com a depuração de testes frágeis e podemos refatorar com sucesso nossos aplicativos com confiança.

A postagem Refatoração bem-sucedida com a biblioteca de teste React e Enzyme apareceu primeiro no LogRocket Blog .

Source link