Tecnologia / Gestão /
Como implantar uma cultura de testes onde não há testes

Cléber

![birmingham-01.jpg](/files/190) *Photo by [Birmingham Museums Trust](https://unsplash.com/@birminghammuseumstrust) on [Unsplash](https://unsplash.com/s/photos/assembly-line)* --- Recentemente foi-me perguntado como eu faria para implantar uma cultura de testes automatizados em uma equipe que já desenvolve um produto tem algum tempo e, até agora, não tem **nenhum** teste automatizado. Desafio interessante, não? E já adianto que o segredo para conseguir sucesso nisso reside em duas palavras: **disciplina e flexibilidade** # Etapas O plano de implantação segue duas etapas: 1. nenhum bug é considerado como corrigido se não há um teste para provar e 2. nenhuma nova funcionalidade é aceita sem testes. ## Bugs e testes A não ser que estejamos falando de uma equipe excepcionalmente qualificada e que **apesar disso** não tem uma cultura de automatizar testes, eu **duvido** que esse projeto não tenha uma grande fila de bugs a serem corrigidos. Logo, da parte da equipe de desenvolvimento, **é necessário disciplina** para que aquilo que estava sendo feito não se repita, que a própria falta de testes automatizados, que acaba sendo a causa da maioria dos bugs, **não se perpetue durante o processo de correção de bugs**. **E da diretoria é necessário flexibilidade**, especialmente com relação aos **prazos**. Não se pode exigir que o desenvolvedor de software faça muito mais com a mesma quantidade de tempo. E é bem comum, inclusive, as equipes não escreverem testes justamente por pressa -- geralmente misturada com algum **desespero**. Algumas vezes será necessário reforçar ou pelo menos treinar melhor as equipes que lidam diretamente com os usuários do software para que o imediatismo não tome conta de tudo e seja barrado antes que cause dano ao produto final. Ademais, há casos em que a situação já está tão deteriorada que a decisão pode ser **perder um cliente** e receber o dano imediato do que tentar manter-se "ileso" e, no médio prazo, acabar queimando a equipe inteira e, consequentemente, tendo muito mais custos com o próprio produto. ### Procedimento Mas não é apenas com discurso que se resolvem as coisas. É necessário adotar um procedimento adequado. A resolução de bugs passa a ter também **dois passos integrados diretamente no processo de desenvolvimento**, que são: 1. escrever o teste que reproduz o bug e 2. fazer o teste passar. Faço questão de enfatizar a palavra "integrados" porque estas duas etadas devem, necessariamente, integrar-se no processo já estabelecido (considerando que **haja** algum processo estabelecido). O que quero dizer? Que o primeiro passo **deve sempre ser *commitado*** no repositório. Repito: **o teste que falha deve ir para o galho-mestre do repositório de código**. Ou qualquer que seja o galho que esteja sendo usado (*development*, talvez). #### Testes que falham de propósito "*Mas... mas.. **como**?*", você pode estar se perguntando. Ao que lhe respondo: a maioria dos sistemas de testes automatizados permitem que você marque o teste como *fail*, ou seja, que você indique explicitamente que aquele teste, embora estando no repositório em um galho que **não é** de funcionalidade (chamado *feature branch*), **irá, sim, falhar**, e que isso é okay. Isso tem dois propósitos: o primeiro é simples, que é dividir a tarefa de correção em duas partes que, inclusive, colaboram de maneira bem equilibrada para a resolução do problema. Obrigando-se a escrever o teste unitário, o desenvolvedor obriga-se também a entender melhor a situação em que o problema acontece e passa a ter, dessa forma, um entendimento melhor da coisa toda. E então, num segundo momento, poderá concentrar-se na resolução. **Ou não!** Pois a presença de um teste unitário facilita muito que **outro** desenvolvedor também possa fazer a correção. Metade do caminho já está andado, afinal! E isso é excelente para a equipe, porque tal **granularização controlada** das tarefas faz com que imprevistos pessoais, por exemplo, ou mesmo o surgimento de tarefas mais urgentes para quem começou a correção sejam problemas mais facilmente contornáveis. O segundo propósito é "oficializar" essa primeira metade do caminho. O trabalho de programação, afinal, só é validado quando as alterações no código são integradas no repositório. Antes disso, tudo é teoria. Teoricamente o problema pode estar corrigido, mas se o código não vai para o galho adequado, então na prática a situação é outra. #### Resolução Estando o teste-que-falha devidamente integrado, é hora de resolvê-lo. Esse trabalho pode ser feito pelo mesmo desenvolvedor que escreveu o teste, mas também pode ser feito, como comentei acima, por ainda outra pessoa, o que pode ser interessante numa série de situações, como quando teste e resolução apresentam níveis de dificuldade diferentes ou mesmo caso algum desenvolvedor tenha um talento reconhecido (e gosto) na escrita de bons testes. Passando o teste-que-agora-já-não-falha, pode-se considerar o bug, finalmente, corrigido. ### Flexibilidade Como mencionei, não adianta cobrar que os programadores façam mais dando-lhes exatamente o mesmo tempo. Implantar um novo processo demanda tempo, disciplina e paciência de todos os envolvidos. É o custo da melhoria que vem depois, que é, basicamente, **ter** o novo processo implantado e funcionando azeitado. Ao mesmo tempo, é preciso admitir que nem sempre é por pura "má vontade" da diretoria que os prazos são curtos. Lidamos com o mercado, afinal, e as coisas não são sempre como planejamos. Nesse caso, sugiro que ambos os lados estejam dispostos a fazer **sacrifícios**. A diretoria permite que algum cliente fique brabo, mesmo. E a equipe de desenvolvimento faz um "mutirão", inclusive consumindo umas horas-extras, para dar o empurrão inicial nesse novo processo. Não é o ideal, não é gostoso, não é o desejado, mas é o que é possível fazer. Se todos querem colher os benefícios, me parece o caso de todos ajudarem também no custo. ### O perigo das "métricas" **Números nunca tem significado absoluto**. Estamos falando justamente de desenvolvimento de software, afinal, e programadores trabalham justamente com o tipo de ferramente que permite que escrevam código que mascara a realidade, seja isso proposital ou não. "*No último mês tivemos 25% menos registro de bugs*" bem poderia ser uma das "frases ditas antes de a empresa morrer", porque no fim do dia não tem em si mesmo um valor absoluto. O número de *reports* pode ter sido menor porque os clientes simplesmente desistiram. Ou porque o uso do software é sazonal. Ou porque um bug **gigante** surgiu e "eclipsou" todos os outros que ainda serão descobertos. Ou qualquer outra coisa que não seja "**temos**, com absoluta certeza, uma diminuição de 25% na quantidade de bugs no sistema*. Muitos podem torcer o nariz para o que vou dizer, mas ***trust your feeling***! Confie nas suas tripas. Números são uma ferramenta útil, mas é aquele conhecimento tácito de quem está em campo, de quem está **ali** no *front*, batalhando no dia-a-dia que realmente pode dar um vislumbre mais significativo da realidade. Então use os números a seu favor, sim, mas também mantenha-se conectado às **pessoas**. É lindo dizer que se é uma "*data-driven company*", mas se isso é em detrimento de ser uma "*people-driven company*", recomendo repensar sua estratégia. (Ou não. Sois livres.) ## Novas funcionalidades Não sou acólito da Igreja do TDD e tampouco vou dizer aqui que a equipe pode replicar no desenvolvimento de novas funcionalidades o exato procedimento da correção de bugs, porque não é assim que a coisa funciona. É muito belo dizer "escreva primeiro os testes", mas na prática implementar algo novo implementando primeiro testes unitários porque muitas vezes simplesmente nem nós mesmos sabemos imediatamente qual é o conjunto completo de *inputs* e *outputs* ou mesmo qual a melhor forma de apresentar o processo numa API palatável, o que faz com que escrever testes unitários nesse momento de **experimentação** acabe consumindo tempo demais com coisas que talvez acabem nem indo para produção realmente. Também é belo imaginar que todos os detalhes podem ser debatidos e destrinchados durante as reuniões de *grooming*, mas esta é outra falácia. Não é à toa que "metodologias" mais recentes e que não estão sujeitas ao "politicamente correto do mundo do software", como [**Shape Up!**](https://basecamp.com/shapeup/shape-up.pdf) descrevam uma fase de *figuring things out* como parte inerente do trabalho em si. **Todavia**, o período de experimentação e descoberta não cobre o todo e uma regra precisa ser mantida: **nada entra no galho-mestre sem testes automatizados**. Repare que não usei o termo "unitários", porque bem pode ser que a equipe decida que é mais vantagem focar primeiro em testes de integração. Fazer o desenvolvedor sentar a bunda na cadeira e digitar testes unitários cobrindo uma porção de casos que talvez nunca cheguem a realmente acontecer em campo **pode** não ser interessante em todos os casos e a equipe, que imagina-se que ainda tenha um mínimo de autonomia, pode decidir que um bom conjunto de testes de integração é suficiente. Mas ainda é necessário **ter** testes automatizados. Se os testes de integração não podem ser rodados na esteira de CI, por qualquer motivo que seja, então eles **não contam**. Ou seja: os testes 1. tem que existir e 2. necessariamente devem ser automatizados. Enquanto essas duas condições não forem ambas satisfeitas, a equipe não deve permitir que o novo código seja integrado (a não ser que seja *boilerplate* conhecido, claro, e arquivos de tradução, conforme as [Diretrizes de Desenvolvimento](https://mynotes.space/cleber/tecnologia/artigos/114), capítulo 1). ### Mas como impedir Estabelecendo também uma política de ***code review***. Um conjunto de regras que pode ser usado é o seguinte: 1. Ninguém mescla (ou "mergeia") o próprio código; 2. O primeiro revisor deve aprovar (usando o *Approve* da ferramenta em uso ou simplesmente comentando com um "joinha"); 3. O segundo revisor, aprovando, deve mesclar. E contanto que não aconteça de dois revisores deixarem código sem testes passar, tudo estará bem. Em algumas equipes pode ser necessário estabelecer que "júnior revisa, mas não avalia". Não é o ideal mas, enfim, cada equipe é um caso particular. Novamente: sois livres. Mas o resumo desse ponto é: **disciplina**. Não somente para impedir que código não-testado entre, mas também para ajudar quem está desenvolvendo a escrever os testes, se for o caso. A equipe sempre deve, afinal, "trabalhar em equipe". É redundante, não? Então. Deveria ser óbvio. ### Testes bons e testes ruins Um sinal de que o teste é bom é que ele **evita regressão**. Regressão é um conceito simples: se um bug foi corrigido ontem e volta a aparecer hoje, então a qualidade do código regrediu. Da mesma forma, se ontem algo estava funcionando e hoje não está mais, a qualidade do código, novamente, regrediu. Ou seja: os testes devem garantir que durante o ciclo de vida do software não seja possível alguém, acidentalmente, quebrar o que já está funcionando e deixar isso passar batido. Se alguém estraga uma funcionalidade, é a suíte de testes quem garante que isso **não** prosseguirá esteira adentro. E um segundo sinal muito importante é que **o teste prevê malícia ou idiotice**. Se é um endpoint exposto ao mundo, pode ser interessante testar se o software irá se comportar adequadamente caso seja enviado um payload grotescamente maior do que normalmente esperado: se o endpoint recebe uma pequena string JSON, experimente enviar um grande livro para ver o que acontece. Ou, se o endpoint é autenticado, sempre experimente operar sobre ele estando desautenticado. Ou tente mexer nas coisas dos outros. Lembre-se: **insegurança é um bug**. E o propósito dos testes é justamente manter o código livre de bugs. ### Flexibilidade Alterar o processo e ter a disciplina de não deixar código novo entrar sem testes também demanda tempo e fará, sim, com que as entregas demorem mais, especialmente nas primeiras **semanas**. O preço é esse e a diretoria precisa decidir se irá pagá-lo ou não. A equipe pode tentar "adiantar umas horas", mas é difícil dizer exatamente como será a adaptação ao novo processo e, portanto, a comunicação entre todas as partes precisa ser fluida e transparente. # Mas e o código antigo? Boa pergunta. Outra boa é a seguinte: *vale a pena escrever testes novos para funcionalidades antigas?*. E a resposta é: depende. Alguns dizem que todo código sem testes é "código legado". Código legado é aquele código que a equipe não domina bem o suficiente para garantir que tudo continuará funcionando caso a situação no entorno mude completamente. *Migrar da GCP para AWS? Hummm... absolutamente não temos como garantir que nada vai quebrar...* Então fica a critério da empresa. Num cenário ideal, alguém sempre deveria estar escrevendo testes para funcionalidades antigas, sejam unitários, de integração ou o que quer que seja. Minha recomendação é que, a não ser que haja previsão de se reescrever a coisa toda do zero usando tecnologias completamente diferentes, testes **devem** ser escritos para funcionalidades antigas e quanto maior a cobertura (e qualidade), melhor para todos.

Curti

27 visitantes curtiram esse Item.

Próximo: Como implantar um processo de desenvolvimento onde não há processo de desenvolvimento