Testes automatizados para CI/CD

Testes automatizados são um dos pilares da integração e implantação contínuas. Ao incorporar testes contínuos aos seus pipelines de DevOps, você pode melhorar dramaticamente a qualidade do seu software.

Integração e implantação contínuas (CI/CD) consistem em entregar alterações frequentes e incrementais, para você obter regularmente feedback sobre o seu produto ou serviço. Porém, entregas mais rápidas e frequentes não devem diminuir a qualidade do produto. Afinal, os seus usuários esperam ter software estável e que funcione, mesmo quando estão clamando pela próxima novidade reluzente.

É por isso que um processo automatizado de testes que seja confiável e minucioso, e que lhe dê confiança na sua última build, é essencial para as suas práticas de CI/CD.

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

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

Em um contexto de cascata, os testes manuais ou a etapa de controle de qualidade ocorrem depois que o código foi desenvolvido e integrado.

💡Observação: A estratégia de cascata, que costuma ser chamada de modelo de cascata, é uma abordagem linear e sequencial ao desenvolvimento de software e gerenciamento de projetos. Foi um dos primeiros modelos usados no desenvolvimento de software e se caracteriza por um fluxo estruturado e sistemático através de fases distintas.

Uma desvantagem disso é que você não tem como verificar se o seu código funciona como esperado até bem depois de tê-lo escrito. A essa altura, muito mais código pode ter sido adicionado ao seu código original, tornando muito mais difícil corrigir quaisquer problemas. Por sua vez, isso retarda a entrega de novos recursos e correções de bugs.

Em contraste, uma abordagem de desenvolvimento ágil favorece ciclos de desenvolvimento curtos e iterativos, de modo que você pode liberar versões mais frequentes, obter feedback dos seus usuários e tomar decisões bem embasadas sobre o que desenvolver em seguida. CI/CD apoia essa forma de trabalhar ao automatizar as etapas entre escrever o seu código e liberá-lo para os usuários, tornando todo o processo mais confiável e eficiente.

Para essa metodologia de DevOps ter sucesso, você precisa fazer commit, fazer build e testar as alterações no seu código regularmente — o ideal é várias vezes ao dia. Porém, mesmo em uma equipe pequena, não é realista executar um conjunto completo de testes manuais uma ou mais vezes ao dia. É por isso que os testes automatizados são uma parte essencial de qualquer pipeline de CI/CD.

Embora escrever testes automatizados requeira um investimento inicial de tempo, esse esforço logo se paga, especialmente quando você começa a fazer commit das suas alterações e a implantá-las mais frequentemente. Investir em testes automatizados traz vários benefícios importantes, incluindo estes:

  • Cada alteração do código é verificada para garantir que funciona como esperado e não introduziu novos bugs.
  • O feedback é dado mais rapidamente que ao executar testes equivalentes manualmente.
  • Corrigir bugs logo depois de eles terem sido introduzidos tende a ser mais eficiente, pois as alterações ainda estão frescas na sua memória e nada foi acrescentado a elas ainda.
  • Os resultados dos testes são mais confiáveis que os dos testes manuais, pois pedir a alguém que execute a mesma tarefa repetidas vezes resulta inevitavelmente em erros e inconsistências.
  • Os testes automatizados podem ser executados em paralelo, de modo que você pode escalonar o seu controle de qualidade (até onde a sua infraestutura permitir) quando o tempo for importante.

Embora a automação de testes elimine muitas tarefas monótonas e repetitivas, ela não torna desnecessária a sua equipe de engenharia de qualidade. Além de definir e priorizar casos relacionados, a equipe de controle de qualidade deve se envolver na criação de testes automatizados, muitas vezes em cooperação com os desenvolvedores. Os engenheiros de qualidade também são necessários para as partes dos testes que não podem ser automatizadas, que discutiremos mais tarde.

Onde entram os testes no processo de CI/CD?

Os testes automatizados acontecem em diversas etapas ao longo do pipeline.

CI/CD e a automação do controle de qualidade têm a ver com loops estreitos de feedback, que permitem à sua equipe descobrir problemas o mais cedo possível.

É muito mais fácil corrigir um problema logo após ele ser introduzido, pois isso ajuda a evitar que mais código seja adicionado a uma base ruim. Também é mais eficiente para a sua equipe fazer alterações antes de seguir adiante para a próxima coisa e esquecer 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 ferramenta para priorizar testes automáticos em um pipeline de CI/CD, em termos tanto do número relativo de testes quanto da ordem em que eles são executados.

A pirâmide de testes foi definida originalmente por Mike Cohn e mostra os testes de unidade na base, os testes de serviço no meio e os testes da interface de usuário no alto.

A pirâmide de testes consiste das seguintes etapas:

  • Começar com uma base robusta de testes de unidade automatizados, rápidos e simples de serem executados.
  • Depois, prosseguir para testes mais complexos de se escrever e mais demorados de se executar.
  • Terminar com um pequeno número de verificações mais complexas.

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. Por exemplo, se você estiver criando um aplicativo meteorológico, talvez a conversão de valores de graus Celsius para Fahrenheit faça parte de uma função maior. Você pode usar testes de unidade para verificar se a função de conversão de temperaturas retorna os resultados esperados para uma faixa de valores. Cada vez que você alterar um trecho relacionado de código, poderá usar esses testes de unidade para confirmar que esse aspecto específico funciona como esperado, sem precisar fazer a build do aplicativo, nem executá-lo todas as vezes.

Tipicamente, em equipes que decidiram investir em escrever testes de unidade, os desenvolvedores assumem a responsabilidade de adicioná-los à medida que escrevem o código relacionado. O desenvolvimento orientado por testes (TDD - Test-driven Development) consagra esse processo (como discutiremos abaixo), mas o TDD não é obrigatório para se escrever testes de unidade.

Outra abordagem é determinar que testes de unidade façam parte da definição de "concluído" para cada tarefa de desenvolvimento e verificar se esses testes estão lá ao efetuar revisões de código ou usar relatórios de cobertura do código.

Se você estiver trabalhando em um sistema já existente e não tiver investido antes em testes de unidade, escrever esses testes do zero para toda a sua base de código pode parecer uma barreira intransponível. Embora seja recomendada uma ampla cobertura com testes de unidade, você pode começar com o que tiver e ir adicionando os testes ao longo do tempo.

Se o seu código atual não tiver uma boa cobertura de testes de unidade, pense em ampliá-la combinando com a sua equipe para adicionar testes a qualquer trecho de código que vocês tocarem. Essa estratégia garante que qualquer novo código esteja coberto e prioriza o código existente com base naquilo com que você interage mais.

Testes de integração

Com testes de integração, você garante que as interações entre várias partes do seu software, tais como entre o código do aplicativo e um banco de dados ou chamadas a uma API, funcionem como esperado.

Pode ser útil subdividir os testes de integração em amplos e restritos. Na abordagem estreita, a interação com outro módulo é testada usando uma duplicata de teste, em vez do módulo real. Testes de integração amplos usam o componente ou serviço real. Voltando ao exemplo do aplicativo meteorológico, um teste de integração amplo pode buscar dados de previsões em uma API, enquanto um teste estreito usaria dados fictícios.

Dependendo da complexidade do seu projeto e do número de serviços internos e externos envolvidos, talvez você queira começar com uma camada de testes de integração estreitos. Esses testes são executados mais rapidamente (e dão feedback mais rápido) que testes de integração ampla, pois eles não precisam que outras partes do sistema estejam disponíveis.

Se os testes estreitos forem concluídos com sucesso, você poderá então executar um conjunto de testes de integração ampla. Como esses testes demoram mais para serem executados e envolvem mais esforço de manutenção, talvez você queira limitá-los às áreas de maior prioridade do seu produto ou serviço.

Testes ponta-a-ponta

Também conhecidos como testes full-stack, os testes ponta-a-ponta testam a aplicação inteira. Tipicamente, esses testes são usados para validar casos de uso de negócios, como, por exemplo, se um usuário consegue criar uma conta ou concluir uma transação.

Embora testes de ponta a ponta automatizados possam ser executados através de uma interface gráfica, isso não é obrigatório; uma chamada a uma API também pode representar diversas partes do sistema (embora APIs também possam ser verificadas 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 de usuário pode fazer com que falhem, resultando em ruído inútil nos seus resultados de testes de builds e em mais tempo necessário para atualizá-los. Portanto, vale a pena projetar testes de ponta a ponta cuidadosamente, com um entendimento do que já foi coberto por testes de mais baixo nível, para que os novos testes tenham mais valor.

Desenvolvimento orientado pelo comportamento

O desenvolvimento orientado pelo comportamento (BDD - Behavior-driven development) é uma abordagem colaborativa do desenvolvimento de software, que elimina as lacunas de comunicação entre os desenvolvedores, os testadores e as partes interessadas no negócio. Ele amplia o alcance do TDD, ao enfatizar o comportamento do software, em vez da sua implementação.

O BDD pode ser uma estratégia útil para desenvolver tanto testes de integração quanto de ponta a ponta. Estes são alguns dos seus principais aspectos:

  • Foco no comportamento: o BDD concentra-se em especificar o comportamento de um sistema através de exemplos em linguagem natural. Esses exemplos costumam ser escritos usando um formato Given-When-Then ("Dado-Quando-Então") para descrever o estado inicial, a ação e o resultado esperado.
  • Colaboração: o BDD encoraja a colaboração entre desenvolvedores, testadores e partes interessadas no negócio, para garantir uma compreensão comum dos requisitos. Esse processo colaborativo ajuda a esclarecer as expectativas e reduzir os mal-entendidos.
  • Especificações executáveis: os cenários de BDD são escritos de forma a poderem ser executados automaticamente como testes. Costumam ser usadas ferramentas como Cucumber, SpecFlow e JBehave para escrever esses cenários em um formato passível de leitura humana que pode ser associado a testes automatizados.
  • Documentação: os cenários servem tanto como documentação quanto como testes, fornecendo uma especificação clara e viva do comportamento do sistema. Isso facilita a compreensão do sistema e de seus requisitos.
  • Desenvolvimento iterativo: o BDD apoia o desenvolvimento iterativo e incremental, ao encorajar melhorias e feedback contínuos. Cada iteração envolve escrever e implementar novos cenários, garantindo que o software evolua alinhado com as necessidades do negócio.

Testes de desempenho

Embora a pirâmide de testes não faça referência aos testes de desempenho, vale a pena considerá-los, especialmente no caso de produtos para os quais a estabilidade e a velocidade sejam requisitos essenciais.

Sob o título geral de "testes de desempenho", cabem diversas estratégias de teste, projetadas para verificar como o seu software se comportará em um ambiente real:

  • Testes de carga verificam como o sistema se comporta quando aumenta a demanda.
  • Testes de stress ultrapassam deliberadamente o nível esperado de uso.
  • Testes de imersão (ou de resistência) medem o desempenho sob uma carga de trabalho continuamente alta.

Estes tipos de testes não visam apenas confirmar que o software poderá lidar com os parâmetros definidos, mas também testar como ele se comportará quando esses parâmetros são superados. O ideal é que o software falhe de maneira ordenada, em vez de catastroficamente.

Ambientes de teste

Tanto os testes de desempenho quanto os de ponta a ponta requerem ambientes muito semelhantes aos de produção e podem precisar de dados de testes de builds. Para que um regime de testes automatizados inspire confiança, é importante que os testes sejam executados todas as vezes da mesma maneira. Isso significa garantir que os seus ambientes de teste permaneçam consistentes entre as execuções e atualizá-los para corresponderem a mudanças implementadas na produção.

Pode ser demorado gerenciar esses ambientes manualmente. Automatizar o processo de criar e destruir ambientes de pré-produção a cada nova build poupa tempo e garante que o regime de testes automatizados seja consistente e confiável.

Como o desenvolvimento orientado por testes apoia os testes automatizados?

O desenvolvimento orientado por testes (TDD) é uma abordagem de desenvolvimento que se originou da programação extrema (XP). No TDD, a primeira etapa é escrever uma lista de casos de teste para a funcionalidade que você deseja adicionar. Em seguida, você considera um caso de teste de cada vez, escreve um teste (que falhe) para ele e depois escreve o código para fazer o teste rodar com sucesso. Por fim, você refatora o código, conforme necessário, antes de prosseguir para o próximo caso de teste. Esse processo pode ser resumido como “Red, green, refactor” (vermelho, verde, refatore) ou “Make it work; make it right” (primeiro faça funcionar; depois escreva o código certo)".

Uma das principais vantagens do TDD é que ele obriga você a adicionar testes automatizados para cada código novo que você escrever. Isso significa que a cobertura dos seus testes está sempre aumentando, permitindo feedback rápido e regular toda vez que você alterar o seu código. Estes são outros benefícios do desenvolvimento orientado por testes:

  • Promoção de uma abordagem iterativa. Depois de identificar uma lista de testes, você trabalha com um caso de teste de cada vez, refinando o restante da lista à medida que avança.
  • Incentivo à separação de interface e implementação. Seguir o fluxo de trabalho "vermelho, verde, refatore" é essencial nessa questão.
  • Obtenção de feedback imediato sobre o trabalho. Você não só valida que a sua última alteração passou no teste, mas também garante que nenhum outro teste está falhando agora.
  • Manter o código bem documentado, pois o teste deixa a intenção clara. Por sua vez, isso torna o seu código mais fácil de manter e acelera a integração de novos membros da equipe.

O TDD é uma maneira eficaz de aumentar a cobertura dos seus testes automatizados para apoiar o seu processo de CI/CD. Apesar disso, o TDD não é obrigatório para se ter uma estratégia eficaz de DevOps e é possível manter altos níveis de cobertura pelos testes automatizados sem TDD.

Trabalhando com feedback

A finalidade de se executar testes automatizados como parte das suas práticas de CI/CD é obter feedback rápido sobre as alterações que você acabou de fazer. Escutar e responder a esse feedback são partes essenciais desse processo. As melhores práticas a seguir ajudarão você a extrair o máximo do seu regime de testes automatizados:

  • Torne os resultados dos seus testes fáceis de acessar. Tipicamente, servidores de CI integram-se com ferramentas de testes, para que você possa revisar todos os resultados em uma visualização em painel ou radiador.
  • Integre os relatórios dos seus testes à sua plataforma de mensagens, como o Slack, para obter notificações automáticas dos resultados dos testes.
  • Quando um teste falhar, encontre a causa o mais rapidamente possível, para que você possa começar a trabalhar em uma correção antes que mais código seja adicionado. Esse processo pode ser acelerado por ferramentas de CI que identificam a área relacionada ao teste na base de código e dão acesso a qualquer dado produzido por ela, como stack traces, valores de saída e capturas de tela.
  • Projete cada teste para verificar uma coisa só e nomeie os seus testes de forma precisa, para que você possa entender facilmente o que falhou.

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.

Testes contínuos x automatizados

No caso de muitas equipes, o ponto de partida dos testes automatizados é uma suíte de testes de unidade que podem ser acionados manualmente ou como parte de um pipeline simples de integração contínua. Quando a sua cultura de DevOps amadurecer mais, você poderá começar o caminho até a pirâmide de testes, adicionando testes de integração, de ponta a ponta, de segurança, de desempenho e outros.

"Testes contínuos" referem-se à prática de executar uma grande variedade de testes automatizados como parte de um pipeline de CI/CD. Nos testes contínuos, cada conjunto de alterações no código é submetido automaticamente a uma série de testes automatizados, para que os bugs sejam descobertos o mais rapidamente possível.

As etapas iniciais de um processo de testes contínuos podem envolver testes executados no IDE, antes de as alterações sequer sofrerem um commit. Em etapas posteriores, tipicamente, os testes contínuos requerem ambientes de teste atualizados automaticamente como parte do pipeline.

Um processo totalmente automatizado de testes contínuos oferece a máxima confiança nas alterações do seu código, ao mesmo tempo que acelera a implantação. Ao submeter o seu software a um regime rigoroso de testes, você reduz significativamente o risco de bugs. Executar esse processo de forma automática e contínua não só ajuda você a trabalhar de forma mais eficiente, como também permite que você implemente correções urgentes rapidamente e com confiança.

Embora os testes contínuos demorem algum tempo para serem implementados, esse é um objetivo para o qual você pode trabalhar de forma incremental, enquanto automatiza outros aspectos dos seus pipelines de CI/CD e aumenta a cobertura dos seus testes.

CI/CD e testes automáticos significam o fim dos testes manuais?

Um equívoco comum entre os novatos em CI/CD é pensar que os testes automatizados eliminam a necessidade dos testes manuais e de profissionais de engenharia 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 gastarem tempo em tarefas repetitivas, os engenheiros de qualidade podem se concentrar em definir casos de teste, escrever testes automatizados e aplicar sua criatividade e engenhosidade em testes exploratórios.

Ao contrário dos testes de build automatizados, que seguem um script cuidadosamente planejado para execução por um computador, os testes exploratórios requerem uma base menos estruturada. O valor dos testes exploratórios está em encontrar coisas que talvez não sejam descobertas por uma abordagem planejada e estruturada do processo de testes. 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 explorar, considere tanto as novas funções quanto as áreas do seu sistema que causariam os maiores danos se algo desse errado na produção. 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.

Testes exploratórios não devem resvalar para testes manuais e repetitivos. A intenção não é efetuar os mesmos conjuntos de verificações todas as vezes. Quando testes exploratórios descobrem problemas, você precisa não só corrigir o problema, mas também escrever um ou mais testes automatizados. Assim, se o problema ocorrer de novo, ele será detectado muito antes no processo.

Melhoramento contínuo para automação de testes

Elaborar uma suíte de testes não é algo que você faça só uma vez e depois esqueça. Testes automatizados precisam de manutenção, para garantir que continuem relevantes e úteis. Da mesma forma que você está sempre aperfeiçoando o seu código, você também precisa aperfeiçoar os seus testes continuamente.

Continuar adicionando testes automatizados para novas funções e aproveitar os achados dos testes exploratórios manterão a sua suíte de testes eficaz e eficiente. Também vale a pena tomar algum tempo para verificar o desempenho dos seus testes e se você pode reordenar ou dividir partes do seu processo de testes para fornecer algum feedback mais cedo.

Ferramentas de CI podem fornecer várias métricas para ajudar você a otimizar o seu pipeline, ao passo que indicadores de testes instáveis podem marcar testes não confiáveis que podem lhe dar uma falsa confiança ou preocupações desnecessárias.

Mas embora as métricas possam ajudar você a aperfeiçoar o seu processo de testes, você deve evitar a armadilha de pensar que a cobertura dos testes é, por si só, um objetivo. O verdadeiro objetivo é entregar, com regularidade, um software que funciona para seus usuários. A automação colabora com esse objetivo ao fornecer feedback rápido e confiável e dar a você confiança antes de colocar o seu software em produção.

Considerações finais sobre testes automatizados em CI/CD

A automatização de testes desempenha um papel central em qualquer pipeline de CI/CD. A execução automática de testes fornece feedback rápido e confiável sobre as alterações no seu código. Por sua vez, isso torna o desenvolvimento mais eficiente, pois bugs são mais fáceis de corrigir se identificados mais cedo.

É uma boa prática ordenar os seus testes automatizados pelo seu tempo de execução. Os testes de unidade devem ser executados primeiro, pois eles darão o feedback mais rápido. Em seguida, os testes de integração e por fim, os de ponta a ponta. Se você não tiver nenhum teste automatizado, os testes de unidade são o melhor ponto de partida. O desenvolvimento orientado por testes (TDD) é uma prática comprovada de desenvolvimento que pode ajudar você a aumentar e manter a cobertura dos testes de unidade.

Quando a sua cultura de DevOps amadurecer mais, talvez você queira prosseguir para os testes contínuos. Parte dessa transição envolverá automatizar a criação e a manutenção dos seus ambientes de teste. Ao escrever testes automatizados de mais alto nível, considere priorizar as áreas de maior risco. Isso pode requerer testes automatizados de desempenho, como testes de carga, stress ou imersão. Testes exploratórios manuais são uma boa maneira de identificar lacunas na cobertura dos seus testes, para você poder continuar aperfeiçoando o seu processo de CI/CD.

Como o TeamCity pode ajudar

O TeamCity oferece um amplo suporte a frameworks de teste e diversos recursos de automação de testes, para ajudar você a obter o máximo do seu processo de CI/CD.

Rapidez e confiabilidade são essenciais para uma automação eficaz de testes e o TeamCity é otimizado para as duas coisas. Além de fornecer relatórios detalhados de testes, para ajudar você a chegar rapidamente à raiz dos problemas, o TeamCity realça testes instáveis automaticamente, para você garantir que apenas falhas válidas sejam assinaladas. A reordenação inteligente dos testes e a paralelização entregam resultados ainda mais rápidos, enquanto o recurso de execução remota fornece feedback antes do commit.

Como o TeamCity oferece integrações com rastreadores de issues, IDEs, Slack e outras plataformas, você pode receber notificações de falhas de testes onde quer que você esteja trabalhando. Finalmente, o suporte completo a máquinas virtuais e a containers do Docker permite que você automatize o gerenciamento dos seus ambientes de teste e implemente testes contínuos como parte do seu processo de CI/CD.