No segundo capítulo desta série - DevOps for the Win, exploramos os passos iniciais cruciais de uma jornada de transformação DevOps. Os primeiros passos em qualquer jornada de transformação são geralmente os mais difíceis. No DevOps, identificar os passos iniciais corretos - aqueles que têm impacto e são exequíveis - é a chave para conduzir a um progresso significativo.
Muitas organizações cometem o erro de implementar cegamente as melhores práticas sem ter em conta os seus constrangimentos específicos, o que conduz frequentemente à frustração e ao cansaço do fracasso. 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 vem de uma pesquisa publicada há 30 anos em The Goal: A Process of Ongoing Improvement de Eliyahu M. Goldratt. A ideia é simples: identificar o principal estrangulamento - o fator limitante que restringe o desempenho do sistema. No nosso caso, isto tornou-se claro quando vimos o trabalho a acumular-se na equipa de desenvolvimento. As tarefas estavam a ser iniciadas mas não concluídas e o atraso continuava a aumentar.
Para descobrir as causas principais, organizámos sessões aprofundadas com equipas de desenvolvimento, chefes técnicos e gestores de produtos. Essas discussões trouxeram à tona vários problemas importantes:
- As equipas estavam a assumir novas tarefas antes de concluírem as existentes.
- Não existiam critérios claros de conclusão de tarefas - o que os programadores consideravam "feito" diferia frequentemente das expectativas dos intervenientes.
- Não existiam medidas objectivas de qualidade antes de marcar o trabalho como concluído, o que conduzia a defeitos e a retrabalho.
- As reafectações frequentes perturbaram a estabilidade da equipa, causando ineficiências.
Criação de equipas estáveis
Uma das primeiras medidas corretivas foi reestruturar as equipas para que se tornassem consistentes e focadas. Em vez de tratarmos grupos de indivíduos como grupos de trabalho ad hoc, formámos equipas estáveis e duradouras com responsabilidades claramente definidas.
A criação de algumas equipas, como as equipas de desenvolvimento, foi relativamente simples. No entanto, a organização de equipas de apoio - como infra-estruturas, gestão de projectos, gestão de produtos e análise comercial - revelou-se mais complicada. Passámos por várias iterações para encontrar as combinações certas.
Seguindo o princípio do pensamento de equipa em primeiro lugar descrito no livro Team Topologies de Matthew Skelton e Manuel Pais, nós:
- Criou equipas pequenas e autónomas (4-7 membros) para promover um conhecimento profundo do domínio e a responsabilidade.
- Eliminou as dependências entre equipas, assegurando que os indivíduos eram totalmente afectados a uma única equipa.
- Iteração das estruturas das equipas, especialmente para funções de apoio como a infraestrutura e a gestão de produtos, para otimizar o fluxo.
Aproveitamento de ferramentas para visibilidade e gestão
Assim que as equipas foram estruturadas, mudámos para o Azure DevOps para a gestão de atrasos e do trabalho. Uma plataforma unificada permitiu:
- Melhor visibilidade do trabalho para que as equipas possam acompanhar os progressos de forma transparente.
- Definições padronizadas de "feito" para alinhar as expectativas entre as funções.
- Melhoria da gestão de atrasos, reduzindo o desfasamento do âmbito e os conflitos de prioridades.
Aplicação de métricas de qualidade objectivas
Para resolver a questão das medidas de qualidade pouco claras antes de marcar as tarefas como "prontas para teste", integrámos uma ferramenta de análise de código estático, que fornece informações objectivas sobre:
- Cobertura do código
- Vulnerabilidades de segurança
- Falsos códigos
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.
Gerir o trabalho em curso (WIP)
Uma das mudanças mais impactantes foi monitorar e limitar o Trabalho em Andamento (WIP). Um WIP elevado indicava gargalos, us proativamente as áreas onde o trabalho estava parado.
Esta abordagem sistemática, baseada nas Topologias do Objetivo e da Equipa, lançou as bases para a melhoria contínua, reduzindo as dependências e melhorando a responsabilidade.
Na nossa jornada de transformação DevOps, uma das conclusões mais significativas foi que, após as melhorias iniciais, a principal restrição ao fluxo passou a ser o design de software — especificamente, a testabilidade do nosso código. Depois de resolver os gargalos iniciais, melhorando a configuração da equipa, definindo uma «Definição de Concluído» e implementando a análise de código, percebemos que o trabalho estava a acumular-se durante a fase de testes. O código desenvolvido pelas equipas scrum frequentemente ficava na fila para ser testado pelos QA dentro das mesmas equipas scrum.
Resolver o próximo obstáculo: A lacuna nos testes
Com os estrangulamentos iniciais resolvidos, surgiu um novo constrangimento - atrasos nos testes. Enquanto as tarefas de desenvolvimento estavam a progredir de forma eficiente, os testes tornaram-se um obstáculo, impedindo lançamentos mais rápidos. Após investigação, identificámos os principais desafios que atrasavam o processo:
- Confiança nos testes manuais: A maioria dos testes era executada manualmente, o que conduzia a ciclos de feedback lentos e a uma deteção tardia dos defeitos.
- Dependência da IU nos testes: Uma vez que os componentes da IU eram normalmente concluídos em último lugar, os testes só podiam começar numa fase tardia do ciclo de desenvolvimento.
- Falta de automatização proactiva: Os testes automatizados, como os testes de IU baseados em Selenium, só foram escritos após os testes manuais, limitando a sua eficácia na validação da fase inicial.
Mudança para abordagens de teste em primeiro lugar
Para quebrar este estrangulamento, demos prioridade aos testes unitários e aos testes de API em detrimento dos testes de IU. A mudança exigiu alterações fundamentais na mentalidade de desenvolvimento e no design do software:
Melhorias nos testes de API
Para tornar os testes de API eficientes:
- APIs bem definidas: APIs ser simples, bem documentadas e disponíveis no início da fase de 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 tornaram os testes mais lentos.
- 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 vários cenários.
- Ao mudar para a integração baseada em API (principalmente REST sobre HTTP e, mais recentemente, GraphQL), reduzimos significativamente os atrasos e as complexidades causados pelas dependências da base de dados.
Essa mudança permitiu um progresso mais rápido na automatização de casos de teste para APIs, mostrando que o foco em projetos API-first melhorou tanto a testabilidade quanto a eficiência.
Reforçar as práticas de testes unitários
Os testes unitários constituíram um desafio maior:
- Progresso rápido, depois teatro da cobertura: Inicialmente, atingimos altos níveis de cobertura de código, mas as revisões de código revelaram que muitos testes unitários eram superficiais, escritos apenas para atingir as metas de cobertura. Esses testes não conseguiram verificar funcionalidades significativas, cobrir cenários de falha ou validar casos extremos.
- Mudança de mentalidade: Os programadores precisavam de compreender o valor dos testes unitários - não só para melhorar a qualidade do código, mas também para acelerar o desenvolvimento através da deteção precoce de problemas.
Desafios na escrita de código testável
O maior obstáculo a um teste unitário eficaz era a falta de testabilidade na própria base de código. Os principais problemas incluíam:
- Funções grandes e monolíticas.
- Componentes fortemente acoplados.
- Má separação das preocupações.
- Abstração insuficiente.
Estes problemas dificultaram o isolamento e o teste eficaz de unidades individuais. Para resolver este problema, nós:
- Diretrizes fornecidas: Partilhámos as melhores práticas sobre:
- Seleção de funções para testes unitários.
- Refactoring do código para melhorar a testabilidade.
- Conceber e utilizar simulações para facilitar os testes.
- Focado em melhorias incrementais: Os programadores foram encorajados a efetuar pequenas alterações significativas para melhorar a testabilidade ao longo do tempo.
Progressos, desafios e o caminho a seguir
Para os produtos antigos, a melhoria da testabilidade continuou a ser um processo lento devido a restrições arquitectónicas. No entanto, estas acções não se destinavam apenas a melhorar a base de código atual - eram um investimento no futuro:
- Os programadores criaram melhores hábitos, incorporando a testabilidade em novas bases de código.
- As equipas tornaram-se mais autossuficientes, reduzindo a dependência de QA externos QA
- A organização evitou repetir os erros do passado, garantindo que os produtos futuros fossem mais fáceis de manter e evoluir.
A principal conclusão? DevOps não se trata de implementar ferramentas ou listas de verificação. Trata-se de melhorar continuamente o fluxo de trabalho, uma restrição de cada vez.
No próximo capítulo desta série, vamos explorar "Designing for Testability". Iremos aprofundar a forma como a melhoria do design do software pode remover as restrições de teste, permitindo ciclos de feedback mais rápidos e maior qualidade do software - primeiro nas estruturas da equipa, depois na gestão do fluxo de trabalho e, finalmente, na testabilidade - estabelecemos as bases para uma transformação DevOps sustentável.