Conceção para a Testabilidade: Superar o Próximo Obstáculo
4 min de leitura

Na nossa série contínua DevOps for the Win, temos documentado o nosso percurso de transformação DevOps. No Capítulo 1, apresentámos o Triângulo DevOps: Ferramentas, Arquitetura e Pessoas no Desenvolvimento de Produtos, onde discutimos os elementos fundamentais que impulsionam o sucesso do DevOps. No Capítulo 2, explorámos Os Primeiros Passos na Nossa Transformação DevOps, partilhando a forma como lançámos as bases para alterações significativas.

Agora, no Capítulo 3, abordamos o próximo grande desafio com que nos deparámos: Conceção para a testabilidade. Após melhorarmos a estrutura da nossa equipa, refinarmos a nossa Definição de Concluído e implementámos a análise de código automatizada, percebemos que o principal obstáculo no nosso pipeline tinha mudado para o design de software, especificamente a testabilidade. O trabalho estava a acumular-se na fase de teste, impedindo-nos de alcançar ciclos de lançamento mais rápidos. Este capítulo analisa a forma como superámos este desafio e melhorámos a nossa capacidade de entregar software de alta qualidade de forma eficiente.

O gargalo dos testes

O principal motivo deste atraso era o facto de os testes serem predominantemente manuais, focando-se em testes de IU e fluxos de trabalho dos utilizadores. Uma vez que a IU era habitualmente um dos últimos itens a ser concluído, existia pouca ou nenhuma automação implementada, e os testes manuais tornaram-se a única abordagem viável. Os casos de teste de IU automatizados (por exemplo, testes Selenium) eram tipicamente escritos após a conclusão dos testes manuais, principalmente para efeitos de regressão.

Esta dependência de testes manuais resultou em:

  • Atrasos significativos nos lançamentos de software.
  • A necessidade de múltiplas implementações para permitir testes paralelos por mais do que um membro da equipa de QA.

Isto evidenciou a importância de tornar o código mais testável para otimizar o fluxo de valor. No entanto, alcançar este objetivo revelou-se mais difícil do que tínhamos antecipado.

Priorizar testes de API e testes unitários

Para responder a este desafio, concentrámo-nos em duas áreas principais:

  1. Testes unitários - Garantir que os componentes individuais podiam ser testados isoladamente.
  2. Testes de API - Reduzir a dependência de testes baseados em IU e de dependências de bases de dados.

Testes de API: Antecipação para um feedback mais rápido

Reconcebemos a nossa estratégia de testes de API com estas melhorias fundamentais:

  1. APIs bem definidas - As APIs foram concebidas para serem simples, bem documentadas e disponibilizadas numa fase precoce do desenvolvimento.
  2. Evitar a base de dados como ponto de integração - A dependência de bases de dados para a integração criou dependências que retardavam os testes. Em vez disso, avançámos para integração baseada em API utilizando REST sobre HTTP e GraphQL. Isto minimizou o tempo de configuração da base de dados e melhorou a automatização de testes.

Esta transição reduziu significativamente os atrasos, permitindo uma automatização mais rápida e testes numa fase inicial, demonstrando que apostar em designs centrados em API melhorou tanto a testabilidade como a eficiência.

Testes unitários: uma mudança de mentalidade

Inicialmente, os nossos esforços de testes unitários resultaram em aumentos rápidos da cobertura de testes, mas identificámos rapidamente alguns problemas:

  • Alguns testes eram superficiais, criados apenas para cumprir os objetivos de cobertura de código.
  • Careciam de uma validação significativa da funcionalidade, dos casos limite e dos cenários de falhas.

Para contrariar esta situação, enfatizámos:

  • A formação dos programadores na criação de testes úteis.
  • Refatorámos o código para melhorar a testabilidade.

Desafios na escrita de código testável

O maior obstáculo para testes unitários eficazes foi a falta de testabilidade no próprio código base. Os principais problemas incluíam:

  • Funções grandes e monolíticas.
  • Componentes fortemente acoplados.
  • Fraca separação de conceitos.
  • Abstração insuficiente.

Estes problemas dificultaram o isolamento e o teste eficaz de unidades individuais. Para resolver esta questão, nós:

  1. Diretrizes fornecidas: Partilhámos as melhores práticas sobre:
    • Selecionámos funções para testes unitários.
    • Refatorámos o código para melhorar a testabilidade.
    • Concebemos e utilizámos simulações para facilitar os testes.
  2. Foco em melhorias incrementais: Os programadores foram incentivados a fazer pequenas alterações significativas para melhorar a testabilidade ao longo do tempo.

Progresso lento, mas constante

Apesar destes esforços, o progresso foi lento, especialmente no caso de produtos legados. A arquitetura existente limitava o número de testes unitários que podíamos escrever. No entanto, estas ações não visavam apenas melhorar a base de código atual — foram um investimento no futuro:

  • Melhorar as competências dos programadores na conceção de código testável garantiu que os projetos futuros não sofreriam dos mesmos problemas.
  • As melhorias incrementais evitaram perturbações, enquanto aumentavam de forma constante a cobertura da automatização de testes.
  • Uma mudança de mentalidade ajudou as equipas a encarar a testabilidade não como um encargo, mas como uma necessidade para o sucesso sustentável de DevOps.

Para as equipas e organizações que iniciam uma transformação DevOps, a testabilidade é uma área de foco fundamental. Ajuda a aliviar os constrangimentos na fase de desenvolvimento. No entanto, é importante gerir as expectativas — os resultados imediatos podem não ser possíveis, especialmente ao lidar com grandes bases de código legadas. A refatorização desse código sem testes unitários e de regressão suficientes constitui um desafio importante.

Lições de The Pragmatic Programmer

Para cenários em que começar com uma nova base de código não seja viável, The Pragmatic Programmer: Your Journey to Mastery, de Andy Hunt e David Thomas, oferece orientações práticas:

  • Focar em alterações incrementais: concentre-se na refatorização de partes pequenas e fáceis de gerir da base de código.
  • Desacoplar componentes: reduza as dependências para tornar as unidades individuais mais fáceis de testar.
  • Dividir funções monolíticas: separe as funções grandes em unidades mais pequenas e focadas.
  • Adotar um design modular: torne o código mais testável e fácil de manter, melhorando a modularidade.

A mensagem aqui é clara: assim que os constrangimentos iniciais tiverem sido resolvidos, a testabilidade deve passar a ser uma área de foco principal. Isto aliviará as limitações na fase de testes e permitirá uma entrega mais rápida de software de alta qualidade.

Perspetivas futuras

O nosso percurso para melhorar a capacidade de teste trouxe-nos lições valiosas sobre o papel do design de software na facilitação de transformações DevOps bem-sucedidas. Ao darmos prioridade à capacidade de teste, resolvemos estrangulamentos, desenvolvemos as competências dos programadores e criámos as bases para o sucesso futuro. Embora os resultados imediatos possam demorar a surgir, os benefícios a longo prazo em termos de qualidade, eficiência e crescimento da equipa compensam este investimento.

À medida que continuamos o nosso percurso em DevOps, medir o sucesso torna-se o próximo desafio principal. No Capítulo 4, exploraremos como estabelecemos KPI e painéis para monitorizar o nosso progresso e identificar novas áreas de melhoria.

Fique atento enquanto explicamos de que forma a tomada de decisões baseada em dados nos ajudou a aperfeiçoar os nossos processos DevOps e a otimizar o desempenho!

Subscrever o blogue da Freyr

Política de Privacidade