Refatorando código legado em projetos React — Parte III

Bruno Nardini
Bemobi
Published in
8 min readJul 18, 2019
Fotografia disponibilizada por Pixabay

No mundo real é bem mais complexo, como diria qualquer desenvolvedor ao ler um tutorial na internet. Para adentrar a complexidade de um projeto real, este artigo abordará uma alteração com um grande impacto no projeto de exemplo.

Testes solitários

O teste de unidade pode ser sociável ou solitário. O teste sociável (sociable test) testa uma unidade com suas dependências reais, ou seja, outras unidades do código. O teste solitário (solitary test) usa doubles no lugar destas dependências para isolar o código a ser testado.

Figura 1 — Testes sociáveis e solitários

O Test Double é um termo genérico para qualquer caso em que você substitui um objeto real para fins de teste. No livro xUnit Test Patterns, Gerard Meszaros o separa em diferentes tipos:

  • Dummy Object: seu único propósito é preencher um argumento, ou atributo de algum argumento, do código testado para não ficar vazio, mas nunca é usado.
  • Test Stub: é um objeto que substitui um componente real que o código depende para que o teste possa ter controle de suas entradas. Permite o teste forçar o código a passar por caminhos específicos de seu fluxo.
  • Test Spy: é uma versão mais elaborada do stub, é capaz de verificar saídas indiretas do código, pois dá ao teste uma forma de inspecioná-las depois de executar o código.
  • Mock Object: é um objeto que substitui um componente real que o código depende para que o teste possa verificar suas saídas indiretas.
  • Fake Object: é um objeto que substitui a dependência do código por uma implementação alternativa da mesma funcionalidade.

A palavra double vem de stunt double, que é uma definição em inglês para dublês de cenas perigosas, mas acabou caindo no popular o termo mock para se referir a qualquer tipo de double, apesar de ser um tipo específico como mencionado acima.

Alteração complexa

Com a evolução do negócio, além dos cursos, surgiu a necessidade de vender conteúdo de palestras pela plataforma. A modificação a ser feita no projeto de exemplo é demonstrada na imagem abaixo:

Figura 2 — Wireframe com a mudança da página inicial

A mudança parece ser muito simples, embaixo da lista existente terá uma nova lista utilizando o mesmo componente e cada lista terá um título. Porém, há algumas questões a serem consideradas:

  • Os dados para a nova lista será entregue por um novo endpoint URL da API. Então a página terá que fazer uma nova requisição para este novo endpoint.
  • O retorno do novo endpoint é um objeto com duas listas, session e workshop, onde cada lista possui um objeto diferente do objeto utilizado na lista de cursos. As duas listas terão que convergir em uma lista só para serem exibidas na tela.
  • A API ainda não está pronta para a nova versão, o novo endpoint ainda não existe, então o client terá que ser desenvolvido usando apenas o contrato de como será o retorno quando estiver pronto. Enquanto isso será exibido o texto “Em breve conteúdos novos de eventos e palestras.”.

A complexidade descrita acima impacta no componente HomePage, que representa a página inicial, e nos seus componentes internos CourseList e CourseCard que representam a lista de cursos. A ligação entre esses componentes é demonstrado na figura abaixo:

Figura 3 — Relação atual dos componentes da página inicial

Como foi demonstrado na Parte II desta série de artigos, o CourseCard está acoplado ao modelo de cursos, com suas regras de negócio, isso dificulta sua reutilização já que a lista não pode ser usada por outros modelos de dado.

Então ao manter esse padrão que foi estabelecido, adicionar uma lista com dois tipos de modelos diferentes, como os modelos session e workshop, resultaria em algo semelhante à figura abaixo:

Figura 4 — Componentes com baixa coesão

Os componentes CourseCard, SessionCard e WorkshopCard possuem a mesma aparência, conforme demonstrado na Figura 2, então um único componente deveria ser o suficiente.

Fica mais fácil visualizar a estrutura ideal se ignorar a estrutura dos dados e pensar somente na organização da tela, é uma página com duas listas iguais com itens iguais, então se são apenas 3 elementos, então deve ser organizado em 3 componentes. Ao invés de criar novos componentes, será necessário refatorar os componentes existentes.

O projeto

Caso você não tenha acompanhado o desenvolvimento feito na Parte I e II, você pode baixar o código pronto através da versão v0.1.2 disponível no endereço:

https://github.com/megatroom/refactoring-react-legacy-code/releases/tag/v0.1.2

Ou utilizando o git pelo comando:

git clone git@github.com:megatroom/refactoring-react-legacy-code.git
cd refactoring-react-legacy-code
git checkout v0.1.2

Refatoração com grande impacto

Na Parte II as regras do curso para o CourseCard (clique no link para ver o arquivo) foram transferidas para a função transformCourseToViewModel(), então ficou mais fácil removê-las do componente.

Crie o diretório adapters dentro de /cliente/src/ e nele crie o arquivo courseAdapter.js com o seguinte código:

Além de transferir a funçãotransformCourseToViewModel() para o arquivo courseAdapter.js, foi necessário um ajuste para deixar de usar o objeto classes, e ao invés de retornar a string postExtraClass passou a retornar o booleano postHighlight. Essa mudança está comentada no código acima. Foi usado também a palavra reservada export para deixar a função disponível fora do arquivo.

Antes de continuar é necessário algumas observações sobre este código:

  • Adapter é um design pattern com características próprias na programação orientada a objetos, mas como o JavaScript nos permite uma programação funcional e sem tipagem, uma função é mais do que necessário neste caso.
  • View Model é um modelo de dados que representa a interface do usuário. Martin Fowler também o descreve como Presentation Model.
  • Esta função precisa de uma refatoração. Porém, o foco deste artigo é a arquitetura front-end, que já é tão complexo que foi necessário dividir em uma série de artigos, então fica como dever de casa. 😉

Depois de removido a função, e feito o ajuste nela, o CourseCard passa a ficar assim:

Essa mudança afeta nenhum teste já criado. Como extraímos a regra de negócio para outro módulo, é possível criar testes exclusivos. No diretório de /cliente/src/adapters, crie o arquivo courseAdapter.test.js com os seguintes testes:

Com o isolamento das regras, agora os testes do componente pode focar no próprio componente. Substitua todo o código do arquivo CourseCard.test.js pelo código abaixo:

O novo teste do código acima fica falhando. Para o teste passar, no componente CourseCard, remova a função transformCourseToViewModel(), todas as propriedades do view model antes retornado pela função passam a ser propriedades do componente. O resultado fica assim:

Neste momento todos os testes de unidade passam com sucesso. Porém, se for executado o teste de integração ele não vai passar, pois a alteração nas propriedades do CourseCard quebrou a integração com outros componentes. A transformação feita pelo transformCourseToViewModel() que estava dentro do CourseCard, agora deve ser feita em outro lugar.

O lugar mais óbvio a se colocar esta transformação é no componente HomePage, que é o componente raiz da tela inicial. Mas lembra que foi feito todo um trabalho de removê-lo do CourseCard? Então não faz sentido repetir o problema em outro componente, ou seja, essa transformação não deve ficar em nenhum componente.

Faz mais sentido fazer esta transformação assim que a informação chega da API, então isso pode ser feito na função onGetCourses() do arquivo /client/src/store/sagas.js:

O Redux será abordado com mais detalhes no próximo artigo.

Agora os testes de integração passam com sucesso e tudo continua funcionando.

Implementação da nova funcionalidade

Com a refatoração feita, o próximo passo é implementar a funcionalidade proposta. Começaremos pelo componente que irá representar o título, então no diretório /client/src/components crie o arquivo Heading.test.js com o teste para o novo componente:

Depois do teste vem a implementação no arquivo Heading.js:

Para exibir a mensagem que a lista nova ainda não está disponível, será criado o componente LeadMessage, segue abaixo seu teste e sua implementação:

O primeiro teste a ser fazer do componente HomePage é testar o que já está funcionando:

Ao importar o componente HomePage foi usado chaves, pois a importação padrão está usando o connect() da biblioteca react-redux que conecta o componente à store do Redux. Para testar assim, teríamos que fazer uma implementação do Provider do Redux no teste, que é uma complexidade desnecessária já que os componentes do Redux devem ter seus próprios testes. Então a alternativa para simplificar é fazer uma exportação direta do componente, isto pode ser feito na sua declaração usando o export:

export class HomePage extends React.Component {

Além da propriedade courses, o componente HomePage possui também a propriedade loadCourses, e a não declaração desta propriedade faz o componente lançar uma exceção. Um componente quebrar pela falta de uma propriedade demonstra uma grave problema de design, o ideal é que ele saiba lidar com sua ausência. Um jeito bem simples de resolver isso é utilizar a propriedade defaultProps para definir os valores padrões de cada um:

HomePage.defaultProps = {
loadCourses: () => {},
courses: []
};

O teste agora passa. Além de testar a renderização de um curso, é preciso testar também que a função loadCourses() será chamada assim que o componente for montado:

O jest.fn() retorna uma função que é utilizada para validar se ela foi chamada pela unidade testada. O teste acima valida se a função foi chamada exatamente uma vez.

Os próximos testes são para inclusão da nova funcionalidade, iniciado pela inclusão dos títulos das listas:

Com o teste falhando, é implementado a inclusão dos títulos, o componente fica assim:

Depois dos títulos, vem os testes para a nova lista:

O primeiro teste valida se a palestra foi renderizada através da lista lectures. O segundo teste valida se o aviso que as palestras não estão disponíveis aparece se a propriedade isLectureDisabled for verdadeira. O terceiro teste valida se a função loadLectures é chamada ao montar o componente. Todas as novas propriedades foram definidas ao criar os testes.

Por fim, a implementação final para atender os novos testes:

As propriedades novas não foram mapeadas para o connect pois será feito na Parte IV, onde será abordado o Redux.

Grandes vitórias

O tempo investido na refatoração trouxe algumas vitórias para a arquitetura do projeto:

  • Utilizando uma função isolada para transformar o modelo recebido pela API em um modelo de apresentação (view model) trouxe flexibilidade para reutilização de componentes.
  • Ao utilizar uma função simples para encapsular a transformação, isolada, com entrada e saída usando objeto simples (plan objects), foi possível fazer testes que cobrem 100% a regra de negócio.
  • Com componentes sem regra de negócio foi possível criar testes mais limpos e focados somente em sua apresentação e eventos.
  • Os testes de unidade não só ajudaram a garantir o funcionamento do código, mas ajudaram também a guiar o desenvolvimento.

Essa separação de responsabilidades feita ao refatorar o código ajuda a focar os testes nas unidades mais críticas, reduzindo drasticamente a chance de ter um bug recorrente.

Um bom termômetro para identificar se a arquitetura do seu projeto está saudável é a testabilidade do seu código. Quanto mais fácil é testar uma unidade, seja ela qual for, melhor é a arquitetura do projeto.

Na próxima parte continua a implementação da nova funcionalidade, abordando o gerenciamento do estado e a integração com a API. Mas antes de seguir, deixe sua opinião sobre os assuntos abordados aqui, seu feedback é muito importante para esta jornada.

--

--

Bruno Nardini
Bemobi
Writer for

Staff Software Engineer at Pipefy | Teacher NardiniAcademy.com | Blogger BrunoNardini.com | Husband & Father | Guitar Player | Build and teach the WEB