Testes automatizados para CI/CD

O objetivo da integração contínua e implantação contínua (CI/CD) é permitir que equipes de desenvolvimento entreguem com frequência software funcional aos usuários, agregando valor e obtendo feedback útil sobre como seu produto é usado no mundo real. Muitas organizações adotaram a prática de DevOps como uma forma de acompanhar a concorrência.

No entanto, a pressão do mercado para entregar mais rápido não deve diminuir a qualidade do que está sendo produzido. Afinal, seus usuários esperam um software estável e funcional, mesmo quando estão clamando pela próxima novidade. É por isso que um processo automatizado de testes confiável e abrangente que lhe dê confiança no seu build mais recente é fundamental para suas práticas de integração contínua e entrega contínua.

Por que os testes de CI/CD devem ser automatizados?

Testes são essenciais para garantir a qualidade do software e há muito faz parte das práticas de desenvolvimento de software.

No contexto de um processo cascata, o teste manual ou estágio de QA ocorre depois que o código foi desenvolvido e integrado, tendo como objetivo verificar se a aplicação se comporta de acordo com a especificação.

Essa abordagem linear retarda o processo de lançamento e significa que seus desenvolvedores não terão como saber se o que eles criaram funciona como esperado até muito mais adiante, depois que muito mais tempo foi gasto construindo código baseado nele. Por outro lado, um processo de automação de CI/CD permite uma abordagem ágil de ciclos iterativos curtos que fornecem feedback rápido e permitem que as atualizações sejam lançadas com frequência e tamanho reduzido. Uma parte importante desses ciclos curtos e iterativos é o processo de testes para validar automaticamente se o novo código funciona e se não causou problemas em outras partes do código.

A integração contínua envolve a submissão de alterações de código no mestre ou tronco regularmente, acionando uma compilação, se aplicável, e garantindo a qualidade do software todas as vezes. Para realmente colher os benefícios da CI, os membros de sua equipe devem ter como objetivo fazer commit das alterações pelo menos diariamente. No entanto, mesmo em equipes pequenas, testes manuais de automação com essa frequência demandariam um esforço considerável dos engenheiros de QA e seria um trabalho bastante repetitivo. É aqui que entram os testes automatizados.

Testes de automação são ideais para tarefas repetitivas e produz resultados mais consistentes do que o processo manual, pois as pessoas inevitavelmente correm o risco de perder detalhes ou de realizar verificações de forma inconsistente quando solicitadas a executar as mesmas etapas repetidamente.

Além de serem mais rápidos do que executar os testes equivalentes manualmente, os testes automatizados podem ser executados em paralelo, para que você possa reforçar sua garantia de qualidade (na medida em que permitido pela sua infraestrutura) quando o tempo for essencial. Embora escrever testes automatizados envolva um investimento inicial de tempo, é algo que compensa quando os membros da sua equipe estão fazendo alterações com regularidade e lançando em produção com muito mais frequência.

Embora os testes automatizados eliminem muitas tarefas chatas e repetitivas, eles não fazem com que sua equipe de engenharia de controle de qualidade se torne redundante. Além de definir e priorizar casos relacionados, a equipe de controle de qualidade está envolvida na redação de testes automatizados, muitas vezes em colaboração com desenvolvedores. Os engenheiros também são necessários para as partes que não podem ser automatizadas, conforme discutiremos mais adiante.

Onde entram os testes no processo de CI/CD?

A resposta é que testes ocorrem em múltiplos estágios ao longo de todo o pipeline.

Se você é novo em integração e implantação contínuas, isso pode parecer um exagero, mas a automação de CI/CD e o controle de qualidade têm tudo a ver com ciclos de feedback rígidos que permitem que sua equipe descubra problemas o mais cedo possível.

É muito mais fácil corrigir um problema logo depois dele ter sido introduzido, pois isso evita que mais código seja escrito em cima de um fundamento ruim. Também é mais eficiente para sua equipe fazer alterações antes de passar para a próxima tarefa, perdendo o contexto.

Muitas ferramentas automatizadas de teste de compilação oferecem suporte à integração com ferramentas de CI/CD, então é possível alimentar os dados de teste no pipeline e realizar testes em etapas, com resultados fornecidos após cada etapa. Dependendo de sua ferramenta de CI, você também pode escolher se deseja mover um build para a próxima etapa com base no resultado dos testes no passo anterior.

Para obter o máximo de seu pipeline por meio de testes automatizados, geralmente faz sentido ordenar seus testes de compilação de forma que os mais rápidos sejam executados primeiro. Assim você recebe feedback mais cedo e aproveita de forma mais eficiente os ambientes de teste, já que pode garantir que os testes iniciais passaram antes de executar testes mais longos e complexos.

Ao considerar como priorizar a criação e a execução de testes automatizados, é útil pensar em termos da pirâmide de testes.

Construindo uma pirâmide de testes

A pirâmide de testes é uma maneira prática de conceituar como priorizar testes de integração contínua em um pipeline de CI/CD, tanto em termos de número relativo quanto na ordem em que são executados.

Definida originalmente por Mike Cohn, a pirâmide de testes coloca os testes de unidade na base da pirâmide, testes de serviço no meio e testes de interface do usuário na ponta.

Embora a nomenclatura possa não ser precisa, a premissa é clara: comece com uma base sólida de testes de unidade automatizados que sejam rápidos e fáceis de executar, antes de avançar para testes mais difíceis de escrever e que demoram mais para executar. Por fim, termine com uma pequena quantidade dos testes mais complexos. Que tipos de testes de CI/CD você deve considerar? Vamos explorar as alternativas.

Testes de unidade

Os testes de unidade constituem corretamente a base da pirâmide de teste. Esses testes são projetados para garantir que seu código funcione conforme o esperado, abordando a menor unidade de comportamento possível. Para equipes que decidiram investir na criação de testes de unidade, são os desenvolvedores que geralmente assumem a responsabilidade em escrevê-los à medida que escrevem código. Isto ocorre naturalmente ao praticar o desenvolvimento orientado a testes (TDD - Test Driven Development), mas o TDD não é um requisito para escrever testes de unidade.

Se você está trabalhando num sistema existente mas não investiu anteriormente em testes de unidade, escrevê-los do zero, para toda a sua base de código, costuma ser uma barreira intransponível. Embora uma ampla cobertura com testes de unidade seja recomendada, você pode começar com o que tiver e ir adicionando mais testes ao longo do tempo. Uma estratégia realista pode ser adicionar testes de unidade a qualquer parte do código que você precisar mexer, garantindo assim que todo o novo código seja coberto e dando prioridade ao código existente com base no código com o qual você interage durante o desenvolvimento.

Testes de integração

Com os testes de integração, você garante que várias partes de seu software interajam umas com as outras conforme o esperado, como por exemplo a interação entre algum código da aplicação e um banco de dados. Pode ser útil subdividir os testes de integração em amplos e restritos. Com testes de integração restritos, a interação com outro módulo é testada usando um substituto (dublê de testes) em vez do módulo real, enquanto os testes de integração amplos usam o próprio componente ou serviço real.

Dependendo da complexidade do seu projeto e do número de serviços internos e externos envolvidos, você talvez queira escrever uma camada de testes de integração restritos que serão executados mais rapidamente do que testes de integração amplos (já que eles não requerem que outras partes do sistema estejam disponíveis) e, em seguida, realizar uma série de testes de integração amplos, tendo como alvos potenciais as áreas de prioridade mais elevada do seu sistema.

Testes ponta-a-ponta

Também conhecidos como testes full-stack, os testes ponta-a-ponta testam a aplicação inteira. Embora esses testes possam ser executados através de uma GUI, isto não é necessário; uma chamada de API também pode testar várias partes do sistema (embora APIs também possam ser testadas com testes de integração). A pirâmide de teste recomenda ter uma quantidade menor desses testes, não apenas porque eles demoram mais para serem executados, mas também porque tendem a ser frágeis.

Qualquer alteração na interface do usuário pode interromper esses testes, resultando em ruído inútil nos resultados de testes de compilação e no tempo necessário para atualizá-los. Vale a pena projetar testes de ponta a ponta com cuidado e com a compreensão do que já foi coberto por testes de compilação de nível inferior. Dessa forma, os testes poderão proporcionar o máximo de valor.

Testes de desempenho

Embora a pirâmide de teste não faça referência aos testes de desempenho, vale a pena considerar sua inclusão entre seus testes automatizados, especialmente para produtos onde estabilidade e velocidade são requisitos essenciais.

Sob o título geral de testes de desempenho, estão incluídas uma série de estratégias de teste projetadas para verificar como seu software se comportará num ambiente durante a execução. Testes de carga verificam como o sistema se comporta quando a demanda aumenta, enquanto testes de estresse excedem de forma deliberada o uso esperado. Por fim, testes de absorção (ou resistência) medem o desempenho sob uma carga elevada constante.

Com esses tipos de teste, o objetivo não é apenas confirmar que o software irá lidar bem com os parâmetros definidos, mas também testar como ele se comporta quando esses parâmetros são ultrapassados, devendo idealmente falhar de forma graciosa em vez de explodir em chamas.

Ambientes de teste

Tanto os testes de desempenho quanto os de ponta a ponta exigem ambientes muito semelhantes aos de produção e podem exigir dados de teste de compilação. Para que um regime de teste automatizado forneça confiança no software submetido a testes, é importante que os testes de compilação sejam executados da mesma maneira todas as vezes, e isso inclui garantir que os ambientes de teste permaneçam consistentes entre as execuções (embora devam ser atualizados para corresponderem ao ambiente de produção quando as alterações são aplicadas lá).

Gerenciar ambientes manualmente pode ser um exercício trabalhoso, então vale a pena considerar a automação das etapas para criar e destruir ambientes de pré-produção a cada novo build.

Trabalhando com feedback

O objetivo de executar testes automatizados como parte de sua prática de CI/CD é obter um feedback rápido sobre as alterações que você acabou de fazer, portanto, ouvir e responder a esse feedback é fundamental.

Servidores de CI normalmente são integrados com ferramentas de teste automatizadas para que você possa exibir todos os resultados em um só lugar. Equipes de desenvolvimento geralmente combinam um painel ou radiador de informações contendo os resultados mais recentes com envio de notificações automatizadas a plataformas de comunicação, como o Slack, para mantê-los informados sobre o desempenho do último build.

Quando um teste falha, entender a qual parte da base de código ele está associado e ser capaz de visualizar qualquer informação produzida pelo teste, como um stacktrace, valor de saída ou captura de tela, pode acelerar o processo de descoberta da causa raiz. Vale a pena dedicar tempo para projetar testes cuidadosamente, de modo que cada um verifique uma única coisa, e rotulá-los especificamente para que você possa entender o que falhou. Testes de integração contínua e as ferramentas de CI que fornecem informações adicionais sobre falhas de teste também podem ajudar você a recuperar suas compilações com mais rapidez.

Como sempre, ferramentas e práticas são apenas uma parte da equação. Uma prática de automação de CI/CD realmente boa requer uma cultura de equipe que reconheça não apenas o valor dos testes automatizados de CI/CD, como também a importância de responder rapidamente a testes com falha para manter o software em um estado implementável.

O CI/CD e os testes de automação representam o fim dos testes manuais?

Um equívoco comum entre aqueles que são novos em CI/CD é que os testes automatizados evitam a necessidade de testes manuais e de engenheiros profissionais de controle de qualidade.

Embora a automação de CI/CD libere algum tempo para os membros da equipe de controle de qualidade, ela não os torna redundantes. Em vez de perderem tempo em tarefas repetitivas, os engenheiros podem se concentrar em definir casos relativos, escrever testes automatizados e aplicar sua criatividade e engenhosidade a testes exploratórios.

Ao contrário dos testes de compilação automatizados, que são cuidadosamente programados para execução por um computador, os testes exploratórios requerem apenas uma missão solta. O valor de um teste exploratório está em encontrar coisas que testes planejado e bem estruturados não conseguem achar. Basicamente, você está procurando problemas que ainda não foram considerados e para os quais ainda não foram escritos casos de teste. Ao decidir quais áreas a serem exploradas, considere tanto novas funcionalidades e áreas de seu sistema que causariam mais danos se algo desse errado durante a produção.

Testes exploratórios não devem se transformar em testes manuais e repetitivos; a intenção não é executar o mesmo conjunto de testes todas as vezes. Quando problemas são descobertos durante um teste exploratório, além de corrigi-los, reserve um tempo para escrever um teste automatizado para que, se o problema ocorrer novamente, ele seja detectado numa fase inicial do processo. Para aproveitar o tempo dos testadores de forma eficiente, um teste manual só deve ocorrer depois que todos os testes automatizados tiverem sido executados com sucesso.

Melhoramento contínuo para automação de testes

Testes automatizados exercem um papel fundamental em qualquer pipeline de CI/CD.

Embora a criação de testes automatizados necessite um investimento de tempo e esforço, os benefícios do feedback rápido e da possibilidade de saber quanto o código está pronto para ser colocado em operação significa que os testes automatizados se pagam rapidamente. Mas construir uma suite de testes não é algo que você faz uma vez e esquece.

Seus testes de compilação automatizados devem fazer parte do seu aplicativo tanto quanto o restante do seu código e, portanto, precisam ser mantidos para garantir que permaneçam relevantes e úteis. Como tal, a melhoria contínua que você está sempre aplicando ao seu código também deve ser aplicada aos seus testes.

Continuar a construir uma cobertura de testes automatizados para novos recursos e alimentá-los com as descobertas dos testes exploratórios irá manter sua suite de testes eficaz e eficiente. Também vale a pena reservar um tempo para ver como está o desempenho e se vale a pena reordenar ou dividir as etapas do seu trabalho para obter feedback mais cedo.

Através das ferramentas de CI você pode obter diversas métricas que irão ajudá-lo a otimizar seu pipeline, enquanto indicadores de testes instáveis podem indicar testes não confiáveis que poderão passar uma falsa confiança ou preocupação. Mas embora métricas possam lhe ajudar a melhorar seu processo de testes automatizados, evite a armadilha de pensar que a cobertura do teste é um objetivo em si. O verdadeiro objetivo é entregar, com regularidade, um software que funciona para seus usuários. A automação atende a esse objetivo, fornecendo feedback rápido e confiável para que você possa ter confiança ao implantar seu software no ambiente de produção.