O que considerar ao escrever testes E2E #frontend@twiliosendgrid

Publicados: 2020-09-19

No Twilio SendGrid, escrevemos testes de ponta a ponta (E2E) no final de um novo recurso ou ciclo de desenvolvimento de página para garantir que todas as partes estejam conectadas e funcionando corretamente entre o front-end e o back-end da perspectiva do usuário final.

Experimentamos várias estruturas e bibliotecas de teste E2E, como nossa própria estrutura interna Ruby Selenium, WebdriverIO e, principalmente, Cypress por mais de dois anos, conforme destacado na parte um e na parte dois da série de postagens do blog documentando nossa migração em todos os as soluções. Independentemente da estrutura ou biblioteca que utilizamos, nos encontramos fazendo as mesmas perguntas sobre quais recursos podemos automatizar e escrever testes E2E. Depois de identificar quais recursos podemos testar, também nos notamos aplicando a mesma estratégia geral repetidamente para escrever e configurar os testes.

Esta postagem de blog não requer nenhum conhecimento prévio de escrever testes E2E em uma determinada biblioteca ou estrutura, mas ajuda se você já viu aplicativos da Web e se perguntou sobre a melhor forma de automatizar as coisas no navegador para testar se as páginas funcionam corretamente. Nosso objetivo é orientá-lo sobre como pensar sobre os testes E2E, para que você possa aplicar essas perguntas e a estratégia geral para escrever testes em qualquer estrutura que escolher.

Perguntas a serem feitas se você pode automatizar um teste E2E

Quando se trata de escrever testes E2E, precisamos garantir que os fluxos nas páginas que estamos testando em nosso aplicativo atendam a determinados critérios. Vamos analisar algumas das perguntas de alto nível que nos fazemos para determinar se é possível automatizar um teste E2E.

1. É possível redefinir os dados do usuário para um determinado estado antes de cada teste por meio de alguma maneira confiável, como a API? Se não houver uma maneira confiável de redefinir um usuário de volta ao estado desejado, ele não poderá ser automatizado e deverá ser executado como parte de seus testes de bloqueio antes da implantação. Também é um antipadrão e geralmente não determinístico retornar um usuário de volta a um determinado estado por meio da interface do usuário porque é lento e a automação de etapas pela interface do usuário já é bastante complicada. É mais confiável fazer chamadas de API para redefinir o estado do usuário sem precisar abrir uma página no navegador. Outra alternativa, caso você tenha um serviço existente, é criar novos usuários antes de cada teste com os dados adequados. Contanto que redefinamos um usuário persistente ou criemos um usuário antes de cada teste, podemos nos concentrar nas partes que estamos testando na página.

2. Temos controle sobre o recurso, API ou sistema que pretendemos testar? Se for um serviço de terceiros no qual você está confiando para cobrança ou qualquer outro recurso, existe uma maneira de zombar deles ou fazer com que funcione de forma determinística com determinados valores? Você deseja obter o máximo de controle possível sobre o teste para reduzir a descamação. Você pode criar usuários de teste dedicados com recursos ou dados isolados por execução de teste para que não seja afetado por mais nada.

3. O serviço ou recurso em si é consistente o suficiente para funcionar dentro de um tempo limite razoável? Muitas vezes, você pode ter que implementar o polling ou esperar que certos dados sejam processados ​​e cheguem ao banco de dados (como atualizações assíncronas mais lentas e eventos de e-mail acionados). Se esses serviços ocorrerem com frequência dentro de uma janela de tempo razoável e confiável, você poderá definir tempos limite de sondagem adequados enquanto espera pela exibição de elementos DOM específicos ou pela atualização dos dados.

4. Podemos selecionar os elementos com os quais precisamos interagir em uma página? Você está lidando com iframes ou elementos gerados sobre os quais você não tem controle e não pode alterar? Para interagir com elementos em uma página, você pode adicionar seletores mais específicos como atributos `data-hook` ou `data-testid` em vez de selecionar ids ou nomes de classes. Os ids e nomes de classes são mais propensos a alterações, pois são comumente associados a estilos. Imagine tentar selecionar nomes de classes com hash ou ids de componentes com estilo ou módulos CSS de outra forma. Para elementos gerados por terceiros ou bibliotecas de componentes de código aberto, como react-select, você pode agrupar esses elementos com um elemento pai com um atributo `data-hook` e selecionar os filhos abaixo. Para lidar com iframes, criamos comandos personalizados para extrair os elementos DOM que precisamos afirmar e agir, os quais forneceremos um exemplo mais adiante.

Há mais considerações a serem levadas em consideração, mas tudo se resume a uma pergunta: podemos repetir este teste E2E de maneira consistente e oportuna e obter os mesmos resultados?

Estratégia geral para escrever testes E2E

1. Descubra os casos de teste de alto valor que podemos automatizar . Alguns exemplos incluem testes de caminhos felizes que cobrem a maior parte de um fluxo de recursos: realizar operações CRUD por meio da interface do usuário para obter informações de perfil de um usuário, filtrar uma tabela para resultados correspondentes com base em alguns dados, criar uma postagem ou configurar uma chave de API. Outros casos extremos e tratamento de erros, no entanto, podem ser melhores para cobrir com testes de unidade e integração. Faça as perguntas que mencionamos na seção anterior para ajudar a encurtar sua lista de casos de teste.

2. Pense em como repetir esses testes configurando ou desmontando a API o máximo possível. Para os casos de teste de alto valor e automatizáveis, comece a observar quais coisas você deve configurar por meio da API. Alguns exemplos são semear o usuário com dados adequados se o usuário não tiver dados filtráveis ​​suficientes para paginação, se os dados do usuário expirarem em uma janela contínua de 30 dias ou se precisarmos possivelmente eliminar alguns dados que sobraram de sucesso ou incompleto testes antes que o teste atual comece novamente. Os testes devem poder ser executados e configurados no mesmo estado repetível, independentemente de como a última execução de teste foi bem-sucedida ou falhou.

É importante pensar: como posso redefinir os dados desse usuário de volta ao ponto inicial para que eu possa testar apenas a parte do recurso que desejo?

Por exemplo, se você quiser testar a capacidade de um usuário adicionar uma postagem para que ela apareça na lista de postagens do usuário, a postagem deve primeiro ser excluída.

3. Coloque-se no lugar do cliente e acompanhe as etapas da interface do usuário necessárias para concluir totalmente um fluxo de recurso. Registre as etapas para um cliente concluir um fluxo ou ação completo. Acompanhe o que o usuário deve ou não ver ou interagir após cada etapa. Faremos verificações de sanidade e afirmações ao longo do caminho para garantir que os usuários encontrem as sequências adequadas de eventos para suas ações. Em seguida, traduziremos as verificações de sanidade em comandos e declarações automatizados.

4. Mantenha as mudanças e automatize os fluxos adicionando seletores específicos e implementando objetos de página (ou qualquer outro tipo de wrapper). Revise as etapas que você anotou sobre como manobrar e passar por um fluxo de recursos. Adicione seletores mais específicos, como atributos `data-hook` a elementos com os quais o usuário interagiu, como botões, modais, entradas, linhas de tabela, alertas e cartões. Se preferir, você pode criar objetos de página, wrappers ou arquivos auxiliares com referências a esses elementos por meio dos seletores adicionados. Você pode então implementar funções reutilizáveis ​​para interagir com os elementos acionáveis ​​da página.

5. Traduza as etapas do usuário que você gravou em testes Cypress com os auxiliares que você criou. Nos testes, geralmente fazemos login em um usuário por meio da API e preservamos o cookie de sessão antes de cada caso de teste ser executado para permanecer conectado. Em seguida, configuramos ou desfazemos os dados do usuário por meio da API para ter um ponto de partida consistente. Com tudo no lugar, visitamos a página que testaremos diretamente para nosso recurso. Prosseguimos com a execução de etapas para o fluxo, como criar, atualizar ou excluir fluxo, afirmando o que deve acontecer ou ficar visível na página ao longo do caminho. Para acelerar os testes e reduzir a instabilidade, evite redefinir ou aumentar o estado por meio da interface do usuário e ignore coisas como fazer login pela página de login ou excluir coisas pela interface do usuário para se concentrar nas partes que deseja testar. Certifique-se de sempre fazer essas partes nos ganchos `before` ou `beforeEach`. Caso contrário, se você usou ganchos `after` ou `afterEach`, os testes podem falhar no meio, fazendo com que suas etapas de limpeza nunca sejam executadas e fazendo com que execuções de teste subsequentes falhem.

6. Martele e elimine a descamação do teste. Depois de implementar os testes e passar algumas vezes localmente, é tentador configurar uma solicitação pull, mesclá-la imediatamente e fazer com que os testes sejam executados em uma programação com o restante do conjunto de testes ou acioná-los nas etapas de implantação. Antes de fazer isso:

    1. Primeiro, tente deixar o usuário em vários estados e veja se seus testes ainda passam para garantir que você tenha as etapas de configuração adequadas.
    2. Em seguida, investigue a execução de seus testes em paralelo quando acionados durante um de seus fluxos de implantação. Isso permite que você veja se os recursos estão sendo pressionados pelos mesmos usuários e se há alguma condição de corrida acontecendo.
    3. Em seguida, observe como seus testes são executados no modo headless em um contêiner do Docker para ver se pode ser necessário aumentar os tempos limite ou ajustar os seletores.

O objetivo é ver como seus testes se comportam em execuções de teste repetidas sob diferentes condições e torná-los o mais estáveis ​​e consistentes possível, para que gastemos menos tempo voltando para corrigir os testes e nos concentrando mais na captura de bugs reais em nossos ambientes.

Aqui está um exemplo de layout padrão de teste do Cypress no qual criamos um comando de suporte global de login chamado `cy.login(username, password).` Definimos o cookie explicitamente e o preservamos antes de cada caso de teste para que possamos permanecer logados e ir diretamente para as páginas que estamos testando. Também realizamos algumas configurações ou desmontamos a API e ignoramos a página de login todas as vezes, conforme mostrado abaixo.

Pensamentos finais

Além de comparar qual solução E2E é a melhor para usar, é importante também adotar a mentalidade adequada para o teste E2E. É crucial primeiro fazer perguntas sobre se o recurso que você deseja testar atende ou não aos requisitos a serem automatizados. Deve haver maneiras de redefinir o usuário ou os dados de volta a um determinado estado de forma confiável (como por meio da API) para que você possa se concentrar no que está tentando validar.

Se não houver uma maneira confiável de redefinir seu usuário ou dados para o ponto de partida adequado, você deve procurar ferramentas de criação e APIs para criar usuários com determinadas configurações. Você também pode considerar zombar das coisas que você pode controlar para tornar os testes o mais estáveis ​​e consistentes possível. Caso contrário, você deve considerar o valor e as compensações com sua equipe. Este é um recurso que você deve deixar para testes de unidade ou testes de regressão manual quando novas alterações de código são enviadas?

Para esses recursos que você pode automatizar em testes E2E, geralmente é mais valioso cobrir os principais fluxos de caminho feliz de seus usuários. Mais uma vez, torne as coisas repetíveis de maneira oportuna e consistente e elimine as falhas ao escrever testes E2E com qualquer estrutura ou biblioteca que desejar.

Para obter mais informações sobre testes específicos do Cypress E2E, confira os seguintes recursos:

  • Visão geral de 1.000 pés para escrever testes de cipreste
  • TypeScript Todas as coisas em seus testes Cypress
  • Lidando com fluxos de e-mail em testes Cypress
  • Ideias para configurar, organizar e consolidar seus testes Cypress
  • Integrando testes Cypress com Docker, Buildkite e CICD