No segundo capítulo desta série – DevOps para o Sucesso, exploramos os passos iniciais cruciais de um percurso de transformação DevOps. Os primeiros passos em qualquer percurso de transformação são frequentemente os mais difíceis. Em DevOps, identificar os passos iniciais corretos, que sejam simultaneamente impactantes e exequíveis, é fundamental para impulsionar um progresso significativo.
Muitas organizações cometem o erro de implementar cegamente as melhores práticas sem considerar as suas limitações específicas, o que conduz frequentemente à frustração e ao esgotamento por falhas. Em vez disso, é necessária uma abordagem mais estratégica — uma que identifique os estrangulamentos e os elimine sistematicamente.
Como identificar os primeiros passos?
Um princípio poderoso para identificar por onde começar provém de uma investigação publicada há 30 anos em The Goal: A Process of Ongoing Improvement, da autoria de Eliyahu M. Goldratt. A ideia é simples: identificar o principal estrangulamento — o fator limitativo que restringe o desempenho do sistema. No nosso caso, isto tornou-se evidente quando observámos a acumulação de trabalho na equipa de desenvolvimento. As tarefas eram iniciadas mas não concluídas, e o volume de trabalho pendente continuava a aumentar.
Para descobrir as causas principais, organizámos sessões de análise aprofundada com equipas de desenvolvimento, líderes técnicos e gestores de produto. Destas discussões emergiram vários problemas fundamentais:
- As equipas assumiam novas tarefas antes de concluírem as existentes.
- Não existiam critérios claros de conclusão de tarefas — o que os programadores consideravam "concluído" diferia frequentemente das expectativas das partes interessadas.
- Não havia medidas de qualidade objetivas antes de marcar o trabalho como concluído, o que gerava defeitos e trabalho adicional.
- As reafetações frequentes perturbaram a estabilidade das equipas, causando ineficiências.
Estabelecer equipas estáveis
Uma das primeiras medidas corretivas foi reestruturar as equipas para garantir consistência e foco. Em vez de tratar grupos de indivíduos como forças-tarefa ad hoc, formámos equipas estáveis e duradouras, com responsabilidades claramente definidas.
A criação de algumas equipas, como as de desenvolvimento, foi relativamente simples. Contudo, organizar as equipas de suporte — tais como infraestruturas, gestão de projetos, gestão de produto e análise de negócios — revelou-se mais complexo. Passámos por várias iterações até encontrar as combinações adequadas.
Seguindo o princípio de priorizar a equipa delineado em Team Topologies, de Matthew Skelton e Manuel Pais, nós:
- Criámos equipas pequenas e autónomas (4 a 7 membros) para promover uma especialização profunda no domínio e responsabilização.
- Eliminámos dependências entre equipas, assegurando que os colaboradores estivessem inteiramente afetados a uma única equipa.
- Iterámos sobre as estruturas das equipas, em particular para as funções de suporte como infraestruturas e gestão de produto, de modo a otimizar o fluxo.
Aproveitar ferramentas para visibilidade e gestão
Uma vez estruturadas as equipas, passámos a utilizar o Azure DevOps para a gestão do trabalho pendente e das tarefas. Esta plataforma unificada permitiu:
- Maior visibilidade do trabalho, para que as equipas pudessem acompanhar o progresso de forma transparente.
- Definições normalizadas de "concluído", para alinhar as expectativas entre as diferentes funções.
- Melhor gestão do trabalho pendente, reduzindo a expansão excessiva do escopo e os conflitos de priorização.
Aplicar métricas de qualidade objetivas
Para resolver o problema de critérios de qualidade pouco claros antes de marcar as tarefas como “prontas para teste”, integrámos uma ferramenta de análise estática de código, fornecendo informações objetivas sobre:
- Cobertura de código
- Vulnerabilidades de segurança
- Anomalias de código
Também melhorámos a nossa definição de “Concluído” para tornar as verificações de qualidade um passo obrigatório antes de marcar os itens de trabalho como concluídos.
Gestão de trabalho em curso (WIP)
Uma das alterações com maior impacto foi a monitorização e limitação do Trabalho em Curso (WIP). Um WIP elevado indicava pontos de estrangulamento, permitindo-nos intervir proativamente nas áreas onde o trabalho estava a atrasar.
Esta abordagem sistemática, enraizada em The Goal e Team Topologies, lançou as bases para a melhoria contínua, reduzindo as dependências e melhorando a responsabilização.
No nosso percurso de transformação DevOps, uma das conclusões mais significativas foi que, após as melhorias iniciais, o principal obstáculo ao fluxo passou a ser o design do software, especificamente a testabilidade do nosso código. Depois de resolvermos os estrangulamentos iniciais através da melhoria da configuração das equipas, da definição de uma "Definição de Concluído" e da implementação de análise de código, reparámos que o trabalho se estava a acumular durante a fase de testes. O código desenvolvido pelas equipas de scrum ficava frequentemente em fila de espera para ser testado pelos membros de QA integrados nas mesmas equipas de scrum.
Resolver o estrangulamento seguinte: A lacuna nos testes
Com os estrangulamentos iniciais resolvidos, surgiu um novo obstáculo: os atrasos nos testes. Embora as tarefas de desenvolvimento estivessem a progredir eficientemente, os testes tornaram-se um entrave, impedindo lançamentos mais rápidos. Após investigação, identificámos os principais desafios que abrandavam o processo:
- Dependência de testes manuais: A maioria dos testes era executada manualmente, o que resultava em ciclos de feedback lentos e deteção tardia de defeitos.
- Dependência da interface de utilizador (UI) nos testes: Uma vez que os componentes de UI eram habitualmente concluídos em último lugar, os testes não podiam iniciar-se senão numa fase avançada do ciclo de desenvolvimento.
- Falta de automatização proativa: Os testes automatizados, como os testes de UI baseados em Selenium, só eram escritos após os testes manuais, limitando a sua eficácia na validação em fase inicial.
Transição para abordagens focadas primeiro nos testes
Para ultrapassar este estrangulamento, priorizámos testes unitários e testes de API em detrimento dos testes de UI. Esta mudança exigiu alterações fundamentais na mentalidade de desenvolvimento e no design de software:
Melhorias nos testes de API
Para tornar os testes de API eficientes:
- APIs bem definidas: as APIs tinham de ser simples, bem documentadas e disponibilizadas numa fase precoce do desenvolvimento para que os testadores pudessem criar casos de teste de forma proativa.
- Evitar a base de dados como ponto de integração:
- A utilização de bases de dados como ponto de integração entre equipas criou dependências que abrandaram os testes.
- A criação de casos de teste exigia a configuração de bases de dados com dados complexos, o que aumentava o tempo de configuração e limitava a capacidade de testar múltiplos cenários.
- Ao transitar para a integração baseada em API (principalmente REST sobre HTTP e, mais recentemente, GraphQL), reduzimos significativamente os atrasos e as complexidades causadas pelas dependências de bases de dados.
Esta mudança permitiu acelerar a automatização de casos de teste para APIs, demonstrando que apostar em designs focados em API melhorou tanto a testabilidade como a eficiência.
Reforçar as práticas de testes unitários
Os testes unitários representaram um desafio maior:
- Progresso rápido, seguido de teatro de cobertura: Inicialmente, alcançámos níveis elevados de cobertura de código, mas as revisões de código revelaram que muitos testes unitários eram superficiais, escritos apenas para cumprir metas de cobertura. Estes testes não conseguiram verificar funcionalidades significativas, abranger cenários de falha ou validar casos limite.
- Mudar mentalidades: Os programadores precisavam de compreender o valor dos testes unitários, não apenas para melhorar a qualidade do código, mas também para acelerar o desenvolvimento, detetando problemas precocemente.
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:
- 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.
- Foco em melhorias incrementais: Os programadores foram incentivados a fazer pequenas alterações significativas para melhorar a testabilidade ao longo do tempo.
Progresso, Desafios e o Caminho a Seguir
No caso dos produtos legados, a melhoria da testabilidade continuou a ser um processo lento devido a restrições arquitetónicas. Contudo, estas ações não visavam apenas melhorar a base de código atual – representavam um investimento no futuro:
- Os programadores desenvolveram melhores hábitos, integrando a testabilidade nas novas bases de código.
- As equipas tornaram-se mais autónomas, reduzindo a dependência de esforços externos de QA.
- A organização evitou repetir erros do passado, garantindo que os futuros produtos fossem mais fáceis de manter e evoluir.
A principal conclusão? O DevOps não se resume à implementação de ferramentas ou listas de verificação. Trata-se de melhorar continuamente o fluxo de trabalho, ultrapassando um obstáculo de cada vez.
No próximo capítulo desta série, exploraremos o tema "Conceção para a Testabilidade". Analisaremos mais detalhadamente a forma como a melhoria do design de software pode remover restrições de teste, permitindo ciclos de feedback mais rápidos e uma maior qualidade do software — primeiro nas estruturas das equipas, depois na gestão de fluxos de trabalho e, por fim, na testabilidade —, estabelecendo assim a base para uma transformação DevOps sustentável.
