Skip to content

Testes para aplicações web baseadas em componentes

Objetivo

Testes são uma parte importante do desenvolvimento, sabemos que ela nos auxilia a manter a aplicação consistente e mais resistente a bugs, entretanto fazer testes manualmente é custos e um tanto quanto entediante. Considerando isso, o objetivo deste estudo é agregar à nossa stack web LINK PARA NOSSA STACK com um conjunto de testes que facilitem seu desenvolvimento e manutenção, ao mesmo tempo que cubram os testes de vários browsers e responsividade.

Tipos de testes

Existem diversos tipos de testes de software, geralmente quando nos referimos a teste aqui estamos tradando de testes de regressão. Testes de regressão são aqueles que podemos replicar diversas vezes com o objetivo de garantir que uma mudança no código não alterou o funcionamento correto de outras partes.

Podemos realizar testes de regressão de diversas formas, mas a grosso modo, iremos listar alguns aqui, e entender mais a frente quais utilizaremos e o porque.

E2E (end to end):

  • Consiste em renderizar o site e realizar um teste como se fosse o usuário acessando a plataforma, realizando ações e esperando que a aplicação reaja de alguma forma (mudando algo na tela, mudando de rota, fazendo um request, ...).
  • Para isso é necessário algum tipo de ferramenta que permita interagir com o navegador como se fosse um usuário, como por exemplo o selenium.
  • O site que fica rodando em um servidor para ser testado pode ser o mesmo que irá para produção, ou ter algum tipo de mockup, como por exemplo mockup das requests, ou alteração de uso dos endpoints para utilizar um servidor de testes.
  • Pode ser testado em diversos browsers e tamanhos de tela.
  • É um teste computacionalmente custoso, requer a aplicação rodando, um browser, e um servidor que manipule o browser.
  • Frequência de uso: Fazer poucos destes. O objetivo é testar algum fluxo muito importante, ou algum caso que os demais testes não são capazes de cobrir.

Testes unitários:

  • Consiste em testar o funcionamento daquela unidade de forma isolada, removendo suas dependencias, como por exemplo outro componente, outra chamada de função, uma camada de banco, um estado global.
  • Uma unidade pode ser um componente, uma classe ou uma função.
  • Se tratando de UI o objetivo pode ser testar a aparência do componente e também o como a saída dele se comporta.
  • É um teste barato, deve cobrir casos de borda e o caso de uso normal.
  • Se tratando de UI podemo fazê-lo utilizando a abordagem do E2E (com um browser renderizando só aquele componente), ou com um fake DOM (a renderização é realizada em Node, semelhante a um SSR). Porém por ser um teste que deve ser feito com maior frequência, normalmente é visto a abordagem usando o fake DOM, que é mais rápida de ser executada.
  • Frequência de uso: Muitos. Deve ser feito para tudo que for possível e cobrindo tanto o caso de uso quando normal, quando os casos de bordas.

Testes de integração:

  • Consiste assim como o teste unitário, testar o funcionamento daquela unidade, porém acoplada a suas dependências.
  • Pode ser feito com um component, uma classe ou uma função.
  • Se tratando de UI, o conceito na prática se mistura muito com o unitário, muitas vezes não temos como, ou não tem necessidade criar um mock para um componente, sendo assim aquele componente não tem teste unitário e somente de integração. Outras vezes testamos o componente removendo somente certas dependências.
  • É um testa barato, e se difere do unitário basicamente devido as dependências.
  • Se tratando de UI, esse tipo de teste se difere do E2E pois não testa todo o app junto, e sim pequenos agrupados que fazem sentidos juntos.
  • Se tratando de classes e funções, esse tipo de teste se difere do E2E, pois geralmente o E2E na web cobre componentes também, e aqui seria somente um conjunto de funções e classes que fazem sentidos juntos. Claro que se um componente faz uso de alguma classe ou função, o teste desse componente sem o mock dessas dependências também é um teste de integração.
  • Frequência de uso: Muitos. Deve ser feito para tudo que for possível e cobrindo tanto o caso de uso quando normal, quando os casos de bordas.

Visual Regression ou Screenshot Test

  • Consiste em tirar prints automaticamente da tela e comparar com prints anteriores, para verificar o que mudou visualmente.
  • Este teste pegará mudanças como, sombras, fonte, posicionamento, ...
  • Podemos testar em vários níveis: na página toda, como no E2E, em alguns componentes, em todos os componentes, em um caso de uso específico de um componente, antes, durante ou depois de alguma interatividade.
  • Requer assim como no E2E uma aplicação rodando para renderizar a página/componente, um browser e um servidor e ferramenta para manipular o browser. A diferença para o E2E está no fato do screenshot e de comparar com outro screen shot feito anteriormente naquele mesmo estado da aplicação. Se feito a nível do site todo pode ser considerado E2E também.
  • Pode ser testado em diversos browsers e tamanhos de tela.
  • Frequência de uso: Não tem bem um senso de quantos fazer, já que pode ser utilizado como um teste E2E, ou um teste unitário/integração. Mas o fato é, salvar várias imagens de vários estados diferentes é bastante custoso, deve ser usado com parcimônia.

Snapshot Test

  • Consiste em automaticamente renderizar um componente e salvar em um arquivo o resultado.
  • É semelhante ao "Screenshot Test", porém ele não tira um print da tela, e sim salva o texto resultado da renderização do componente, ou seja, o html (normalmente o css e estado interno não são salvos, mas isso varia de ferramenta).
  • Geralmente não requer um browser rodando, a renderização é realizada em Node, semelhante a um SSR, utilizando um fake DOM.
  • O snapshot, assim como no "Screenshot Test", salva um estado em um dado momento e pode ser realizado após simular alguma interação do usuário, como por exemplo o clicar de um botão. Entretanto por não ser feito geralmente em cima de um fake DOM, limita um pouco as opções de interação.
  • Considerando que o site nada mais é que um componente raiz, que possui outros componentes, podemos utilizar este teste como se fosse um E2E, ou como teste unitário ou de integração.
  • Frequência de uso: Assim como o Screenshot teste, não há um consenso de frequência, como podemos utilizar como teste de unitário, de integração ou e2e. Mas o fato é que é bem mais rápido de desenvolver, e executar os testes se comparado ao "Screenshot Test".

Testes e ferramentas selecionadas

Trouxemos nesta seção nossos objetivos com cada teste e seus fluxos, o que nos guiou na busca e seleção de ferramentas. Na seção mais a frente haverá o comparativo e listagem das ferramentas consideradas caso tenha interesse.

Trazendo novamente nossos objetivos:

  • Saber quando uma feature quebrou outra
  • Cobrir testes visuais, pensando no que o usuário irá ver de fato e não somente no html que será gerado
  • Simular interações do usuário
  • Cobrir testes de funções e classes bem como de componentes
  • Não passar o dia todo só fazendo testes, ou arrumando testes porque algo mudou
  • Rodar no CI para garantir que não vá para produção sem estar tudo devidamente testado

Antes de trazer o compilado do que selecionamos e o porque, é importante que você entenda o que é o storybook e o que são stories, pois muito do que foi selecionado gira em torno disso. Recomendamos que leia o estudo que fizemos sobre o storybook e porque optamos traze-ló para nossa stack (COLOCAR LINK P/ STORYBOOK TEST AQUI).

Storybook é um framework para guiar o desenvolvimento orientado a componentes, ele nos permite criar os componentes de forma isolada, visualizando o componente isoladamente durante sua construção (sem ter que inserir no projeto real) e documentando o funcionamento do componentes com stories. Uma story é o resultado de um componente dado um conjunto de props passados. E.x. de story: Imagine um botão que tem as props color e size, uma story pode ser um botão com color=red e size=medium. Podemos ter infinitas stories para cada componente, mas o objetivo é indicar as props que revelam os principais estados que um componente pode assumir, no exemplo do botão poderia ser grande, pequeno ou médio, não precisamos mostrar que ele aceita 10000 cores diferentes. Outro exemplo, poderiamos para um modal criar 2 stories, uma onde ele está aberto e outra onde ele está fechado, isso não testa se o mecanismos de abrir e fechar funciona, mas indica estados possíveis que o modal pode assumir.

Também é importante que você tenha em mente que em nossa stack um componente pode ser uma página, ou um componente normal. um componente normal, também chamado de stateless ou dummy, é um componente que não lida com fetch ou estados globais (na maioria das vezes). Já a página orquestra a organização dos componentes, realiza fetch e pode fazer uso de estados globais para se comunicar com outras páginas. Isso é importante pois em testes queremos que o estado de um componente seja previsível, e como páginas possuem dependências externas que podem fugir do nosso controle, requerem uma atenção especial. Para isso utilizamos das técnicas de mock, adição de variáveis de ambiente e outros.

Dado nosso atual uso do storybook, nosso sistema de componentes e visando cumprir os objetivos listados, optamos por trazer para a stack o testes abaixo:

E2E

  • Ferramenta: utilizando AINDA NÃO DECIDIDO
  • Objetivo/Justificativa/Quando usar:
    • Testar algum fluxo muito importante, como por exemplo policies, navegação
    • Algo que os demais testes não consigam cobrir por usar estado global, sistema de rotas, paralelismo ...
    • AINDA A DECIDIR COMO MOCKAR, SERVIDOR DE TESTE? MOCK DE FETCH?
  • Quantidade: Serão poucos destes, uma vez que a regra de negócio a baixo nível será testado pelos outros testes

Snapshot

  • Ferramenta: addon "storyshot" do storybook
  • Objetivo/Justificativa/Quando usar:
    • Fazer testes unitário/integração dos componentes, sem ter que escrever nada, ou muito pouco, dado que as stories já vãos estar criadas.
    • Sendo realizado snapshots para todas as stories automaticamente.
    • Isto é, serão snapshots a nível de testes unitários/integração.
    • Só não chega a ser e2e pois só chega a nível de página e não testa a interação entre elas, navegação, estados globais, ...
  • Quantidade: Serão muitos e gerados automaticamente
  • Desvantagem Gerenciar tantos snapshots e lidar com a regressão (imagine um botão utilizado em 100 componentes, se o botão mudar, mesmo que intencionalmente, todos os 100 componentes irão falhar)

Screenshot Tests

  • Ferramenta: addon "creevey" do storybook
  • Objetivo/Justificativa/Quando usar:
    • Garantir que tudo estará como o usuário tem que ver, levando em consideração a responsividade.
    • Será realizado também em cima da stories, porém somente das stories de páginas inteiras.
    • Como irá gerar várias imagens, e as páginas irão fazer uso dos demais componentes é mais interessante testar neste nível para garantir (com menor custo computacional), que o usuário não está vendo algo incorretamente.
    • Essa abordagem não permite testar como ficaria a imagem após alguma interação do usuário, mas tirar print para cada possível interação que gere mudanças visíveis seria muito custoso.
    • Como será feito em cima de páginas, elas devem ter o mock de dados como request, ou estados globais.
    • Escolhemos o creevey no lugar dos demais devido a quantidade de browsers que ele cobre, e sua integração com o storybook, não sendo necessário códigos extras.
    • Só não chega a ser e2e pois só chega a nível de página e não testa a interação entre elas, navegação, estados globais, etc.
    • Ele não cobre tudo que queremos, mas com um pouco de código foi possível utilizar ele. Veja mais detalhes na seção de testes de ferramentas.
  • Quantidade: Teremos automaticamente gerados, alguns por página.
  • PENDENTE TESTAR SE FUNCIONA NO CI

Testes de integração/unitários para funções e classes:

  • Ferramenta: Jest + TS
  • Objetivo/Justificativa/Quando usar:
    • Testar os módulos fora dos componentes, validando as regras de negócio.
    • Por mais que os componentes façam uso desses módulos, e os componentes são testados, isso ajuda a identificar, caso um componente falhe, que o problema está em um módulo específico, auxiliando na manutenção e debug.
    • Também auxilia na documentação e desenvolvimento desses modulos, uma vez que eles não são renderizados no storybook.
  • Quantidade: Muitos, feitos a mão, preferencialmente utilizando a abordagem do TDD.

Testes de integração/unitários para componentes:

  • Ferramenta: Jest + TS + Processador Svelte
  • Objetivo/Justificativa/Quando usar:
    • Testar aqueles componentes que tem muitas ações, ou realizam alguma regra de negócio, que não gerem resultados visuais (não alterem o html).
    • Esse tipo de componente não são cobertos por snapshots ou "Screenshot Tests", pois possuem diversos estados que devem ser testados, e cobrir todos eles no storybook seria custoso.
    • E.x.:
      • Podemos testar um modal com snapshots e "screen test", bem como o abrir e fechar, mas o abrir e fechar geram 3 prints, uma para cada estado após uma ação ("nenhuma", "abrir", "fechar").
      • Agora imagine um componente de formulário, onde gostariamos de testar se o preencher dele e clicar em um botão retorna pela função de callback um objeto com as respostas.
      • Ou então um componente que possui 10 ações possíveis, ou ações com ramificações.
      • Para os ultimos 2 casos utilizaremos os testes integração/unitários dos componentes.
  • Quantidade: Teremos uma quantidade mediana de testes, uma vez que componentes mais simples, como um botão, uma lista que colapsa, um modal, já estarão cobertos pelos testar gerados automaticamente.

Testes de ferramentas

Testes de integração/unitários

Aqui juntamos integração e unitário pois se tratando de UI, muitas vezes é dificil separar ambos, é melhor seguirmos sem utilizar a definição ao pé da letra.

Se tratando de testes de modulos e classes que são externos aos componentes svelte, testamos o mocha e jest, ambos funcionam muito bem, mas como jest é mais famoso e possui mais features seguiremos com ele.

Se tratando de testes de componentes, o jest funciona muito bem com o svelte, bastando adicionar um transformer, que consiga entender os arquivos sveltes e aceite preprocessor, por conta do TS e SCSS do nosso componente. Neste ponto foi um pouco dificil a configuração, pois as duas libs de transformação testadas não funcionavam muito bem. Após quebrar um pouco a cabeça descobrimos que a versão do node era o problema, utilizando a versão 14.x tudo funcionava normalmente (mesmo as libs dizendo explicitamente que funcionavam com a versão 12.x). Além disso foi adicionado o jsdom para termos mais expects e o test-library para facilitar as buscas dentro do componente.

Ainda se tratando de testes de componentes, temos que levar em conta o scss. Para alguns asserts precisamos tanto de scss do componente quanto de algum scss global, como por exemplo ver se algo está visível. Os scss que vão dentro do style do componente é renderizado corretamente pelo preprocessor, mesmo os imports que vão dentro da tag de style do componente funciona. Porém, o import de scss via javascript, como é normal no rollup ou webpack, necessitam de mais configuração, esse import via javascript é útil para code split e imports globais pois não geram duplicatas como os imports dentro das tags de estilo do svelte.

Após algumas pesquisas, tudo que encontramos foi como fazer mock do css/scss, e não como dar load dele para dentro do jsdom. Achamos uma issue a respeito: https://github.com/testing-library/jest-dom/issues/70, mas a conclusão é que o desenvolvedor deve se virar caso precise testar um componente que tem dependência de algo global.

Por fim, temos os mocks e stubs que não escolhemos libs e nem fizemos testes, uma vez que estamos no jest, temos várias libs e meios para fazer algum mock, cabendo o desenvolvedor adicionar o que for preciso. Porém para mocks de requests fizemos algumas escolhas que serão necessários para os testes de paginas, tanto para o storybook quando screenshot test e documentação.

Snapshot

Para snapshots, poderiamos utilizar o próprio jest e alguma lib que transforme svelte em json, entretanto o objetivo é não ter de escrever testes, sendo assim, testamos somente o addon storyshots, que utiliza das stories do storybook que já estão prontas,.

storyshots

https://storybook.js.org/addons/@storybook/addon-storyshots/

É uma ferramenta oficial dos mesmos mantedores do storybook, funciona oficialmente com svelte.

Uma vez rodando jest com o svelte corretamente, é bem simples de configurar, e funciona muito bem.

O maior desafio é lidar com problemas de regressão e lidar com os merge requests. Mas isso é algo que resolveremos com o fluxo de desenvolvimento.

Visual Regression ou Screenshot Test

No ref. 2, temos algumas ferramentas que entregam essa funcionalidade, mas como o objetivo é não ter de criar os testes, e utilizar as "stories" já criadas no storybook para isso, vamos nos atentar as ferramentas que já temos no storybook.

Loki

https://storybook.js.org/addons/loki

Por essa ferramenta utilizar o pupperteer, ela só consegue executar em cima do chrome, devido a isso descartamos ela.

Creevey

https://storybook.js.org/addons/creevey/

Essa ferramenta utiliza o selenium por baixo dos panos, o que nos permite utilizar vários browsers. Foi testado com 3 (chrome, mozila e safari), testamos (opera e edge) também, mas não funcionaram.

Se comparado ao loki, possui menor popularidade, e é também 2 anos mais nova. Entretanto seguindo os gráficos de download, loki está estagnado e creevey está em uma crescente.

Funcionou bem com nossa stack svelte, e a configuração foi facil devido ao docker, entretanto no wsl2 (que é onde foi testado), ficou bem pesado a execução. Rodar ele em um CI também é um desafio (AINDA NÃO TESTADO), uma vez que ele usa docker. A solução para o CI é montar uma imagem com tudo e utilizar a feature de browser local e não utilizar o docker (assim como aponta as docs deles).

Ele possui um sistema de desabilitar as stories o que tornou possível termos automaticamente ele executando somente para os componentes do tipo "página", com base no nome da pasta que eles ficam inseridos.

Ele entrega um bom ferramental de diff, mas todas as imagens ficam salvas localmente, o que pode pesar o projeto ao longo prazo. A ferramenta de diff funciona muito bem na maioria dos casos, mas em alguns momentos ele renderiza o componente incorretamente, o que pode dificultar o uso em CI por não ser determinístico.

Outro desafio nesta ferramenta é a responsividade, ela utiliza das stories do storybook, mas não herda a gestão de viewport dele, sendo necessário fazer isso direto na ferramenta que não dá suporte ainda (é uma feature que está mapeada por eles para ser feita no futuro). Para contornar este problema, temos 2 caminhos:

1. Na configuração de navegadores, definir o viewport inicial para cada viewport de cada navegador que queremos,
2. ou na configuração das stories de páginas, colocar manualmente testes mudando o viewport por meio do selenium

Preferimos seguir com a segunda abordagem, pois a primeira implica em ter várias imagens de docker em memória, e cada uma pesa cerca de 200MB.

Para facilitar a criação dos testes, podemos criar um helper que gere eles.

Com a configuração de viewport que pretendemos utilizar, cobrindo para cada navegador 6 dispositivos (mobile, tablet, desktops MD, HD, FullHD e UltraWide), teremos 18 imagens por página, que devem gerar juntas entre 500Kb - 4MB, a depender da quantidade de cores que a página tem. É um valor aceitável considerando que um projeto deva ter em média 40 páginas.

E2E Testes

Segundo o comparativo da ref. 1:

  1. se não for javascript, é recomendado o uso do selenium
  2. se for javascript, usar pupperteer + selenium
    • podemos usar o webdriver.io para isso, pois ele já tem integração para usar o selenium + pupperteer + Appium
  3. ou usar playwright

Exitem também ferramentas pagas que abstraem qual framework usar e entregam um nível mais acima para testar, como é o caso do testim (https://www.testim.io/), mas é pago.

Abaixo tem os 4 grandes frameworks encontrados.

Pupperteer

https://github.com/puppeteer/puppeteer

Pupperteer foca somente no chromium, não é cross-browser. Sua principal diferença, segundo eles, é a velocidade, segurança, estabilidade e simplicidade.

Suporte ao firefox chengado, ainda em estado experimental e com o firefox nightly.

Mais facil de setar que o selenium, e tem uma sintax limpa;

dá para usar com o storyshots: - https://github.com/storybookjs/storybook/tree/main/addons/storyshots/storyshots-puppeteer

PlayWright

https://playwright.dev/

  • Mantido pela microsoft.
  • Tambem tem suporte ao safari.
  • network mocking
  • pensado para javascript
  • mas é bem nova
  • tem addon no storybook:
    • https://storybook.js.org/addons/storybook-addon-playwright
    • testei esse addon e deixei documentado junto com o restante das coisas de storybook.
  • poucos tutoriais e docs, muito por conta das mundaças na API
  • parece um pouco experimental

Selenium

https://www.selenium.dev/selenium/docs/api/javascript/index.html

  • mais popular e maduro
  • multi linguagem
  • multi browser
  • tem como testar mobile (Appium)
  • sintax verbosa, mas tem libs para encapsular isso
  • em comparação com os outros, é mais dificil de fazer o setup

Cypress

https://www.cypress.io/

Segundo o comparativo ref. 1, não é nada bom.

Mock de requests

Em nossa stack front-end os componentes de páginas principalmente possuem dependências externas, de apis, o objetivo dos mocks é poder testar páginas levando em consideração as respostas que as apis podem entregar.

Considerando que a api é nossa, o ideal para fazer mock de requests seria um sandbox na api. Pois isso permite que testemos se a query feita pelo app para posts/patchs e outros também funcionam. Entretanto nem sempre a api é nossa, fazer requests de verdade são lentos, e mais do que testar se estamos enviado os dados certos, queremos saber se, dado uma api que responde como esperamos, nossos componentes vão se comportar corretamente, seja em um POST/GET/PATCH/...

storybook addon fixtures

https://storybook.js.org/addons/storybook-fixtures

Não possui suporte oficial a svelte. Não chegamos a testar ele, pois de forma simples o que ele faz é entregar uma interface para poder selecionar presets de props. Isso força com que os componentes de página sejam dummy components, ou seja, não fazem fetchs, o que pode ser interessante em termos de modularização, mas pode dificultar o desenvolvimento e comprometer a flexibilidade.

Imagine um formulário que tem diversos selects ditados por fetchs, poderiamos fazer os fetchs aos poucos, somente quando o usuário abrir o um select, mas como ele seria um dummy component, um wraper que não a página e nem o formulário deve fazer todos os fetchs e passar para os componentes em cascata.

json server

https://github.com/typicode/json-server

O json server entrega de forma fácil uma api RestFull, ele funciona de forma que declaramos uma base de dados em json e a api surge em cima, entregando queries e todos os métodos de uma api Rest.

A modificação das rotas para uso de alguma query diferente ou retorno diferente, é bastante oneroso, se assemelha a criar de fato uma api. Como muitas das APIs que utilizamos não segue o padrão entregue pela ferramenta json-server, ela não é tão útil neste momento.

Referências

  1. Comparativo puppeteer, selenium, playwright e cypress
    • https://www.testim.io/blog/puppeteer-selenium-playwright-cypress-how-to-choose/
    • publicado em: 04/06/2020
  2. Teste de regressão visual
    • https://medium.com/loftbr/visual-regression-testing-eb74050f3366

Pesquisas futuras