escrito por Thayse Onofrio e Marilene Lourenço
Quando falamos em desenvolvimento ágil de software, é impossível não falar em testes.
O princípio de “fail fast” (falha rápida) também envolve desenvolvimento orientado a testes e entrega contínua. Não esperamos mais para ter o aplicativo em um ambiente de teste e alguém responsável por clicar em um monte de botões até que descubram que o software não funciona como esperado. Agora temos pipelines que nos permitem executar testes automatizados antes que nosso aplicativo chegue à produção, diminuindo a necessidade de testes manuais e tornando as pessoas desenvolvedoras igualmente responsáveis pela qualidade do software que estão desenvolvendo.
No entanto, ainda há muitas dúvidas sobre como e quando testar. Com tantos tipos diferentes de testes, quais devemos ter em nosso sistema? Quantos testes são suficientes? O que exatamente devemos testar? Se as métricas nos dizem que nosso código está 100% coberto, isso significa que não temos bugs?
Tentando responder a essas perguntas, Mike Cohn publicou um livro intitulado Succeeding with Agile, há muito tempo, no qual apresentava o conceito de The Test Pyramid — que é um “guia” de como escrever testes assertivos, nos lugares certos e o contexto certo. Desde então, a pirâmide foi elogiada, repudiada, outras pirâmides foram criadas, assim como alguns antipadrões. De qualquer forma, é um ótimo ponto de partida para entendermos como equilibrar a estratégia de teste em nossos sistemas.
Queremos ter certeza de que todos os envolvidos no processo de desenvolvimento de software são capazes de entender este assunto — não apenas Devs e QAs. Portanto, da mesma forma que temos o manifesto ágil, temos o Manifesto de Teste, que nos mostra coisas importantes para ter em mente quando falamos de qualidade:
Vamos evitar a criação de bugs em vez de encontrá-los. Vamos escrever testes enquanto codificamos e não apenas quando o sistema está “pronto”. Vamos entender os detalhes do que estamos desenvolvendo em vez de verificar a funcionalidade. Vamos construir o melhor sistema que pudermos e não apenas encontrar maneiras de quebrá-lo. E, finalmente, a qualidade do nosso software depende da equipe e não apenas de uma pessoa.
Agora, antes de entrarmos no mundo das pirâmides, precisamos entender quais são os tipos de testes de que mais ouvimos falar:
Tipos de testes
Testes Unitários
Podemos considerar uma unidade como a menor parte de nosso código, ou mesmo a menor parte testável da nossa aplicação. Dependendo da linguagem que usamos, pode ser uma função, um método, uma sub-rotina ou uma propriedade. Podemos encontrar diferentes definições para testes de unidade, mas é importante lembrar que eles testam partes pequenas e independentes do código e verificam se essas partes fazem o que se espera que façam. Se nossa unidade precisa se comunicar com outra parte do código, podemos usar Mocks e Stubs. Podemos escrever testes de unidade para qualquer parte do sistema, como classes de repositório, controladores e domínio. É ideal ter uma classe de teste para cada classe de código de produção.
Testes de Integração
Você se lembra de quando fazíamos o trabalho escolar e tínhamos uma pessoa diferente responsável por cada parte do trabalho? Aí, no dia da entrega, juntávamos todas as peças e nada fazia sentido. Sim, é por isso que, no mundo do software, precisamos de testes de integração. Já sabemos que as pequenas partes do nosso código estão funcionando conforme o esperado, uma independente da outra. Precisamos ter certeza de que, quando eles se comunicarem, as coisas funcionem como pretendido.
Testes de Contrato
Quando saímos do mundo dos monólitos e começamos a falar sobre microsserviços e comunicação com APIs externos, é importante falar sobre outro tipo de teste: o teste de contrato. Este tipo de teste irá garantir que não haja quebra de contratos entre fornecedor e cliente. Precisamos ter certeza de que não houve qualquer tipo de mudança no tipo de estrutura ou tipo de dados fornecidos.
Diferente dos testes de integração, os testes de contrato são focados na comparação de tipos de dados trocados entre cliente e provedor com um arquivo de contrato. Se o teste for interrompido, sabemos que há uma mudança no provedor, ou que as modificações feitas pelo cliente agora esperam algo diferente.
Os testes de contrato devem estar em um pacote separado, pois podemos lidar com a instabilidade da API e, se eles fizerem parte do mesmo processo, podemos estar fazendo solicitações excessivas.
Testes de UI (Interface com o Usuário)
Esse tipo de teste garante que a interface do usuário do seu aplicativo funcione corretamente. Garantimos que a entrada do usuário acione as ações corretas, que os dados sejam apresentados ao usuário e que o estado da UI mude conforme o esperado. Às vezes, diz-se que os testes de UI e os testes ponta a ponta são a mesma coisa.
Sim, testar seu aplicativo de ponta a ponta geralmente significa conduzir seus testes por meio da interface do usuário. O inverso, entretanto, não é verdade. Os testes de UI podem verificar se, ao clicar em um botão, o sistema retorna o comportamento correto ao usuário, mas não se importa onde exatamente o botão está.
Testes E2E (Ponta a Ponta)
Esse tipo de teste nos dá mais confiança para garantir que o software funcione conforme o esperado.
São várias ferramentas que nos permitem automatizar nossos testes, orientando nosso navegador/aplicativo contra nossos serviços, executando cliques, inserindo dados e verificando o estado da interface do usuário.
Ao contrário dos testes de UI, ele considera a jornada do usuário. Portanto, ele precisa ser conectado diretamente à lógica do sistema.
Contextos diferentes, pirâmides diferentes
A pirâmide de Mike Cohen fala sobre como pensar nossa estratégia de teste, pretendendo ter uma quantidade maior de testes que sejam rápidos de desenvolver e que também nos dê um feedback rápido e com menor custo. Também menciona ter menos testes, que demoram mais para serem desenvolvidos e que têm um custo elevado.
Ao pensar em Pirâmides de Teste, imaginamos automaticamente um sistema complexo com back-end e front-end. Mas, sabemos que nem sempre essa é a realidade e é importante entender que é possível adaptar as pirâmides a diferentes contextos.
Como pensaríamos em uma Pirâmide de Teste para um sistema legado, que requer mudanças nas funcionalidades que estão em produção e também a criação de novos recursos?
Para ilustrar isso, vamos falar sobre dois contextos — um projeto apenas de back-end e um projeto apenas de front-end.
Quando temos um projeto que possui apenas backend, existe a possibilidade de uma pirâmide semelhante à tradicional Pirâmide de Teste — com testes unitários, testes de integração, testes de contrato e até testes funcionais para avaliar uma jornada ponta a ponta, sem o precisa testar o front-end. Dentro desses testes, também podemos ter testes de segurança e desempenho e criar um pipeline que automatiza tudo, não sendo preciso depender de testes manuais.
No entanto, em um projeto com apenas um front-end, uma Pirâmide de Teste tradicional pode não fazer sentido. Os testes de unidade não serão a maioria. É necessário garantir a jornada do usuário e também a acessibilidade, por meio de e2e e testes de integração, para garantir que os pontos importantes para o negócio sejam contemplados. Para este cenário, o conceito Test Trophy (Troféu de testes)pode fazer mais sentido. Este conceito é apresentado por Kent C. Dodds em seu artigo Escreva testes. Não muito. Principalmente integração.
O ponto principal é que precisamos entender o contexto em que estamos para saber se a Pirâmide de Teste pode ser aplicada a ele ou não. Além disso, devemos ter em mente que a Pirâmide de Teste é um conceito que nos ajuda a criar estratégias para entregar produtos melhores, mas nada está escrito em pedra — tudo deve ser experimentado e evoluído com o tempo.
A Pirâmide de Teste Estendida
Além de olhar para a pirâmide de diferentes contextos, também podemos vê-la de diferentes ângulos. Para isso, podemos falar sobre a pirâmide de teste expandida.
Esse conceito foi apresentado no livro More Agile Testing, escrito por Janet Gregory e Lisa Crispin.
Traz uma visão mais completa da pirâmide, ampliando-a em diferentes dimensões. Mostra-nos uma visão abrangente das possibilidades e necessidades que devem ser levadas em consideração na construção de uma estratégia de qualidade para o desenvolvimento de software.
Imagem baseada no livro More Agile Testing
No centro, a visualização tradicional nos diz o que cada camada significa e o que foi projetado para ser testado. Isso ajuda a criar uma estratégia de teste apropriada para o software.
Imagem baseada no livro More Agile Testing
À direita, vemos a dimensão que engloba as ferramentas e ambientes que podemos usar para realizar esses testes. Cada ferramenta/ambiente é escolhida para um tipo específico de teste. Por exemplo, na camada de teste funcional e de regras de negócios, estamos analisando os testes do sistema e garantindo que a funcionalidade fundamental de cada um dos elementos da arquitetura funcione individualmente e em conjunto.
Na dimensão esquerda, temos os CRFs (Requisitos Transversais, não relacionados diretamente a funcionalidade), que nos dão uma visão do que precisa ser definido e avaliado como critérios que interferem diretamente no usuário final e que podem ser pensados em várias camadas. Enquanto os requisitos funcionais nos dizem o que fazer, os requisitos transversais nos mostram como isso será feito. Pensamos em usabilidade, acessibilidade, desempenho, segurança e muitos outros requisitos que podem não estar relacionados ao desenvolvimento de código especificamente, mas que são igualmente importantes.
Imagem baseada no livro More Agile Testing
Visualmente, é muito difícil representar o quarto lado da pirâmide, então os testes de regressão são representados em linhas que correm ao longo do terceiro lado, indicando que a regressão pode estar em qualquer atributo do sistema que foi considerado como parte do teste .
Depois que cada tipo de teste é atribuído ao nível de teste correto, é possível entender quantos testes podem ser desenvolvidos em cada camada, o que permite trabalhar nas métricas de qualidade em vários níveis e torna a nossa visão da pirâmide muito mais completa.
O que não fazer — ou anti-padrões
Ao tentar implementar uma estratégia de teste eficiente, podemos cair em algumas armadilhas. Existe, por exemplo, o anti-padrão Ice Cream (cone de sorvete). Esse anti-padrão acontece quando não temos muitos testes de unidade e integração, mas temos muitos testes de ponta a ponta (aqueles que acontecem no nível da UI) e ainda mais testes manuais.
Esses testes tendem a falhar em qualquer mudança de UI e as equipes acabam gastando uma boa quantidade de tempo tentando consertar ou atualizar esses testes de UI e repetindo testes de regressão manualmente.
Como podemos evitar que isso aconteça? Investir mais na automação de testes em um nível inferior.
No entanto, algumas situações podem levar a outro antipadrão. Digamos que o projeto em que você trabalha tenha equipes diferentes escrevendo testes em níveis diferentes. Sua equipe é responsável por desenvolver o código e escrever testes de unidade e integração. Outra equipe é responsável por escrever testes de ponta a ponta. Ainda outra equipe é responsável pelos testes manuais. Se esses times não estiverem em sincronia, podemos acabar no antipadrão Cupcake, apresentado por Fábio Pereira. E, bem, isso não é muito ágil.
A falta de comunicação entre equipes diferentes pode causar duplicação de casos de teste e, pior ainda, alguns cenários acabam nem sendo testados. Este anti-padrão também é conhecido como Dual Test Pyramid (Pirâmide de Testes Dupla).
Como podemos saber se estamos testando tudo o que devemos testar e quanto do nosso código está sendo testado? É aí que a Cobertura de Teste entra em jogo. A ideia é nos ajudar a monitorar a quantidade de testes existentes e nos apoiar na criação de testes que cubram áreas que foram perdidas ou não foram validadas anteriormente.
Diversas ferramentas nos permitem monitorar essas métricas. No entanto, é importante ter em mente que ter 100% de cobertura de teste não diz muito sobre a qualidade de nossos testes.
Muitas equipes trabalham duro para alcançar uma cobertura específica. Sim, ter um número baixo provavelmente é um sinal de que algo está errado, porém, ter um número alto não é garantia de que tenhamos alta qualidade em nossos testes. Podemos aprender facilmente a fazer testes que satisfaçam as métricas, mas isso não ajuda a validar nosso código.
Lembre-se de que a parte importante é o que você está testando e como você está testando, e não apenas a porcentagem de cobertura de testes ser alta.
Entrega Contínua
Como podemos obter feedback rápido sobre como as mudanças que fazemos afetam o software? Precisamos usar uma abordagem que permita que a qualidade seja incorporada ao software.
A entrega contínua é uma parte essencial desse processo. Quando temos testes automatizados e estamos trabalhando com Integração Contínua, podemos criar uma pipeline de implantação. Nessa pipeline, cada mudança que fazemos cria um pacote que pode ser implantado em qualquer ambiente, executa testes de unidade e fornece feedback instantâneo para desenvolvedores. Se os testes de unidade forem aprovados, a pipeline vai para a segunda fase, onde os testes de aceitação automatizados são executados e assim por diante. Após todos os testes serem executados em diferentes etapas, e todos passarem, temos um pacote disponível para implantação em outros ambientes, nos quais podem ser feitos testes exploratórios e de usabilidade, por exemplo.
A pipeline deve se adequar a cada projeto, de acordo com suas necessidades. O ponto principal é que podemos usar uma pipeline de implantação para ter um feedback rápido. Mais tarde, se encontrarmos defeitos, isso significa que devemos melhorar nossa pipeline, adicionando ou alterando nossos testes automatizados.
Como equilibrar a pirâmide de teste?
Agora que conhecemos os principais tipos de testes, a pirâmide ideal e todos os padrões que não devemos seguir, sabemos exatamente como implementar nossa estratégia de teste?
Não! Não há fórmula mágica. A pirâmide de teste é uma representação simplista de uma estratégia de teste que deixa de fora muitos tipos de testes e que podem ou não se aplicar ao nosso projeto.
O objetivo é estimular a reflexão nas equipes para que possamos entender o que pode ser usado e como podemos melhorar nossa estratégia, focando em alguns princípios de teste ágil como testar cedo para obter feedback rápido, prevenir bugs em vez de encontrar bugs e lembrar que o toda a equipe é responsável pela qualidade.
Artigo originalmente publicado em LeadDev