TDD: uma metodologia de desenvolvimento que mudou minha vida

Às 7:15 da manhã Nosso suporte técnico é inundado de trabalho. O Good Morning America acaba de falar sobre nós e muitos dos que visitam nosso site pela primeira vez encontraram erros.

Temos uma verdadeira corrida. Agora, antes de perder a oportunidade de transformar os visitantes do recurso em novos usuários, lançaremos o fix pack. Um dos desenvolvedores preparou algo. Ele acha que isso ajudará a lidar com o problema. Colocamos um link para a versão atualizada do programa, que ainda não entrou em produção, para o bate-papo da empresa e pedimos a todos que o testem. Isso funciona!

Nossos engenheiros heróicos executam scripts para implantar os sistemas e, após alguns minutos, a atualização entra em batalha. De repente, o número de chamadas de suporte técnico dobra. Nossa solução urgente quebrou alguma coisa, os desenvolvedores assumiram a culpa e os engenheiros reverteram o sistema para o estado anterior naquele momento.

imagem

O autor do material, cuja tradução publicamos hoje, acredita que tudo isso poderia ter sido evitado graças ao TDD.

Por que estou usando TDD?


Eu não estou em situações como essa há muito tempo. E não é que os desenvolvedores parem de cometer erros. O fato é que, há muitos anos, em todas as equipes que eu liderava e influenciava, a metodologia TDD era aplicada. É claro que os erros ainda acontecem, mas a penetração na produção de problemas que podem "derrubar" o projeto diminuiu para quase zero, embora a frequência das atualizações de software e o número de tarefas que precisam ser resolvidas durante a atualização tenham aumentado exponencialmente desde então. quando aconteceu algo que eu falei no começo.

Quando alguém me pergunta por que ele deveria entrar em contato com o TDD, conto a ele essa história e lembro uma dúzia de casos semelhantes. Uma das razões mais importantes pelas quais mudei para o TDD é que essa metodologia melhora a cobertura dos testes com código, o que leva a 40-80% menos erros na produção. É disso que eu mais gosto no TDD. Isso tira uma montanha de problemas dos ombros dos desenvolvedores.

Além disso, vale a pena notar que o TDD salva os desenvolvedores do medo de fazer alterações no código.

Nos projetos dos quais participo, conjuntos de módulos automáticos e testes funcionais quase diariamente impedem que o código entre em produção, o que pode prejudicar seriamente o trabalho desses projetos. Por exemplo, agora estou olhando para 10 atualizações automáticas da biblioteca feitas na semana passada, como antes de liberá-las sem usar o TDD, eu teria medo de que elas estragassem alguma coisa.

Todas essas atualizações foram automaticamente integradas ao código e já são usadas na produção. Não verifiquei nenhum deles manualmente e não me preocupei de que eles pudessem ter um efeito ruim no projeto. Ao mesmo tempo, para dar esse exemplo, não precisei pensar muito. Acabei de abrir o GitHub, observei fusões recentes e vi do que estava falando. A tarefa que foi resolvida anteriormente manualmente (ou, pior ainda, o problema que foi ignorado) agora é um processo automatizado em segundo plano. Você pode tentar fazer algo semelhante sem uma boa cobertura de código com testes, mas eu não recomendaria fazer isso.

O que é TDD?


TDD significa Desenvolvimento Orientado a Testes. O processo implementado aplicando esta metodologia é muito simples:


Os testes detectam erros, os testes são concluídos com sucesso, a refatoração é realizada

Aqui estão os princípios básicos para usar o TDD:

  1. Antes de escrever um código de implementação para algum recurso, eles escrevem um teste que permite verificar se esse código de implementação futuro funciona ou não. Antes de prosseguir para a próxima etapa, o teste é iniciado e convencido de que gera um erro. Graças a isso, você pode ter certeza de que o teste não produz resultados positivos falsos, é uma espécie de teste dos próprios testes.
  2. Eles criam uma implementação da oportunidade e garantem que ela passe no teste com sucesso.
  3. Execute, se necessário, refatoração de código. A refatoração, na presença de um teste que possa indicar ao desenvolvedor se o sistema está funcionando corretamente ou incorretamente, dá ao desenvolvedor confiança em suas ações.

Como o TDD pode ajudar a economizar o tempo necessário para desenvolver programas?


À primeira vista, pode parecer que escrever testes significa um aumento significativo na quantidade de código do projeto, e que tudo isso leva muito tempo aos desenvolvedores. No meu caso, no começo, tudo era exatamente isso, e tentei entender como, em princípio, é possível escrever o código testado e como adicionar testes ao código que já foi escrito.

O TDD é caracterizado por uma certa curva de aprendizado e, embora um iniciante suba nessa curva, o tempo necessário para o desenvolvimento pode aumentar de 15 a 35% . Muitas vezes, é exatamente isso que acontece. Mas em algum lugar cerca de 2 anos após o início do uso do TDD, algo incrível começa a acontecer. Nomeadamente, por exemplo, comecei, com a escrita preliminar de testes de unidade, a programação mais rápida do que antes, quando o TDD não era usado.

Há alguns anos, implementei, no sistema do cliente, a capacidade de trabalhar com fragmentos de um videoclipe. Ou seja, o ponto era que seria possível permitir ao usuário indicar o início e o fim do fragmento de gravação e receber um link para ele, o que tornaria possível a referência a um local específico no clipe, e não a todo esse clipe.

Eu não trabalhei O jogador chegou ao final do fragmento e continuou a reproduzi-lo, mas eu não fazia ideia do porquê disso.

Achei que o problema estava conectando inadequadamente ouvintes de eventos. Meu código ficou mais ou menos assim:

video.addEventListener('timeupdate', () => {  if (video.currentTime >= clip.stopTime) {    video.pause();  } }); 

O processo de encontrar o problema ficou assim: fazendo alterações, compilando, reiniciando, clicando, aguardando ... Essa sequência de ações foi repetida várias vezes.

Para verificar cada uma das alterações introduzidas no projeto, demorou quase um minuto para gastar e eu experimentei muitas opções para solucionar o problema (a maioria delas 2-3 vezes).

Talvez eu tenha cometido um erro na palavra-chave timeupdate ? Entendi os recursos de trabalhar com a API corretamente? A chamada video.pause() ? Fiz alterações no código, adicionei console.log() , voltei ao navegador, cliquei no botão , cliquei na posição localizada no final do fragmento selecionado e esperei pacientemente até que o clipe fosse totalmente reproduzido. O registro dentro da construção if não levou a nada. Parecia uma pista sobre um possível problema. Copiei a palavra timeupdate da documentação da API para ter certeza absoluta de que não cometi um erro ao inseri-la. Recarrego a página novamente, clique novamente, aguarde novamente. E, novamente, o programa se recusa a funcionar corretamente.

Finalmente coloquei console.log() fora do bloco if . "Não vai ajudar", pensei. No final, a if era tão simples que eu simplesmente não fazia ideia de como soletrar incorretamente. Mas o logon neste caso funcionou. Engasguei com café. "Que diabos é isso!?" Eu pensei.
Lei de depuração de Murphy. O local do programa que você nunca testou, uma vez que acreditava firmemente que não poderia conter erros, será exatamente o local em que você encontrará um erro após, completamente exausto, fará alterações nesse local apenas porque que eles já tentaram tudo o que podiam pensar.

Defino um ponto de interrupção no programa para entender o que está acontecendo. Eu explorei o significado de clip.stopTime . Para minha surpresa, estava undefined . Porque Eu olhei para o código novamente. Quando o usuário seleciona a hora de término do fragmento, o programa coloca o marcador para o final do fragmento no lugar certo, mas não define o valor de clip.stopTime . "Sou um idiota incrível", pensei, "não devo entrar nos computadores até o fim da minha vida."

Não esqueci disso e anos depois. E tudo - graças à sensação que experimentei, ainda encontrando um erro. Você provavelmente sabe do que estou falando. Com tudo isso aconteceu. E, talvez, todos sejam capazes de se reconhecer neste meme.


É assim que eu pareço quando programo

Se eu escrevesse esse programa hoje, começaria a trabalhar assim:

 describe('clipReducer/setClipStopTime', async assert => { const stopTime = 5; const clipState = {   startTime: 2,   stopTime: Infinity }; assert({   given: 'clip stop time',   should: 'set clip stop time in state',   actual: clipReducer(clipState, setClipStopTime(stopTime)),   expected: { ...clipState, stopTime } }); }); 

Há uma sensação de que há muito mais código do que nesta linha:

 clip.stopTime = video.currentTime 

Mas esse é o ponto. Este código atua como uma especificação. Isso é documentação e prova de que o código funciona conforme exigido por esta documentação. E, como essa documentação existe, se eu alterar a ordem de trabalho com o marcador para o horário de término de um fragmento, não preciso me preocupar se, durante a introdução dessas alterações, violei a operação correta com o horário de término do clipe.

A propósito, aqui está um material útil para escrever testes de unidade, o mesmo que acabamos de ver.

A questão não é quanto tempo leva para inserir esse código. A questão é quanto tempo leva para depurar se algo der errado. Se o código estiver incorreto, o teste fornecerá um excelente relatório de erros. Eu saberei imediatamente que o problema não é o manipulador de eventos. Eu saberei que está em setClipStopTime() ou em clipReducer() , onde uma alteração de estado é implementada. Graças ao teste, eu saberia quais funções o código executa, o que ele realmente exibe e o que é esperado dele. E, mais importante, meu colega terá o mesmo conhecimento que, seis meses depois que eu escrevi o código, introduzirá novos recursos nele.

Iniciando um novo projeto, eu, como uma das primeiras coisas, configurei um script observador que executa automaticamente testes de unidade toda vez que um determinado arquivo é alterado. Costumo programar usando dois monitores. Em um deles, o console do desenvolvedor é aberto, no qual os resultados desse script são exibidos, por outro, a interface do ambiente em que escrevo o código é exibida. Quando faço uma alteração no código, normalmente, em 3 segundos, descubro se a alteração está funcionando ou não.

Para mim, o TDD é muito mais do que apenas seguro. É a capacidade de receber constantemente e rapidamente, em tempo real, informações sobre o status do meu código. Recompensa instantânea na forma de testes aprovados ou um relatório instantâneo de erros no caso de eu ter feito algo errado.

Como a metodologia TDD me ensinou a escrever um código melhor?


Eu gostaria de fazer uma admissão, até admitir que é embaraçoso: eu não tinha ideia de como criar aplicativos antes de aprender TDD e testes de unidade. Não consigo imaginar como fui contratado, mas depois de entrevistar muitas centenas de desenvolvedores, posso dizer com confiança que muitos programadores estão em uma situação semelhante. A metodologia TDD me ensinou quase tudo o que sei sobre decomposição e composição eficiente de componentes de software (refiro-me a módulos, funções, objetos, componentes de interface do usuário etc.).

A razão para isso é que os testes de unidade forçam o programador a testar componentes isoladamente um do outro e dos subsistemas de E / S. Se o módulo for fornecido com alguns dados de entrada, ele deverá fornecer certos dados de saída conhecidos anteriormente. Caso contrário, o teste falha. Se isso acontecer, o teste será bem-sucedido. O ponto aqui é que o módulo deve funcionar independentemente do restante do aplicativo. Se você estiver testando a lógica do estado, poderá fazer isso sem exibir nada na tela ou salvar qualquer coisa no banco de dados. Se você estiver testando a formação da interface do usuário, poderá testá-la sem precisar carregar a página em um navegador ou acessar os recursos de rede.

Entre outras coisas, a metodologia TDD me ensinou que a vida se torna muito mais fácil se você se esforça pelo minimalismo ao desenvolver componentes da interface do usuário. Além disso, a lógica de negócios e os efeitos colaterais devem ser isolados da interface do usuário. Do ponto de vista prático, isso significa que, se você usar uma estrutura de interface do usuário baseada em componente como React ou Angular, poderá ser aconselhável criar componentes de apresentação responsáveis ​​por exibir algo na tela e componentes de contêiner que não estejam conectados entre si. são misturados.

Um componente de apresentação que recebe certas propriedades sempre gera o mesmo resultado. Esses componentes podem ser facilmente verificados usando testes de unidade. Isso permite descobrir se o componente funciona corretamente com as propriedades e se certa lógica condicional usada na formação da interface está correta. Por exemplo, é possível que o componente que forma a lista não exiba nada além de um convite para adicionar um novo elemento à lista, se a lista estiver vazia.

Eu conhecia o princípio da separação de responsabilidades muito antes de dominar o TDD, mas não sabia como compartilhar responsabilidades entre diferentes entidades.

O teste de unidade me permitiu estudar o uso de mokas para testar alguma coisa, e então descobri que zombar é um sinal de que algo pode estar errado com o código . Isso me surpreendeu e mudou completamente minha abordagem à composição de software.

Todo o desenvolvimento de software é uma composição: o processo de dividir grandes problemas em muitos pequenos problemas facilmente resolvidos e criar soluções para esses problemas que formam o aplicativo. Tuxing para fins de testes de unidade indica que as unidades atômicas da composição não são, de fato, atômicas. Estudar como me livrar do mok sem afetar a cobertura do código por meio de testes me permitiu aprender sobre como identificar as inúmeras razões ocultas para a forte conexão das entidades.

Isso me permitiu, como desenvolvedor, crescer profissionalmente. Isso me ensinou a escrever código muito mais simples, mais fácil de estender, manter e dimensionar. Isso se aplica à complexidade do próprio código e à organização de seu trabalho em grandes sistemas distribuídos, como infraestruturas de nuvem.

Como o TDD economiza tempo da equipe?


Eu já disse que o TDD, em primeiro lugar, leva a uma melhor cobertura do código com testes. A razão para isso é que não começamos a escrever código para implementar algum recurso até escrevermos um teste que verifica a operação correta desse código futuro. Primeiro, escrevemos um teste. Então, permitimos que termine com um erro. Em seguida, escrevemos o código para implementar a oportunidade. Estamos testando o código, recebemos uma mensagem de erro, alcançamos a aprovação correta dos testes, realizamos a refatoração e repetimos esse processo.

Este processo permite que você crie uma "cerca" através da qual apenas alguns erros podem "pular". Essa proteção contra erros tem um efeito incrível em toda a equipe de desenvolvimento. Alivia o medo da equipe de mesclagem.

O alto nível de cobertura do código com testes permite que a equipe se livre do desejo de controlar manualmente qualquer alteração, mesmo que pequena, na base de código. As alterações de código se tornam uma parte natural do fluxo de trabalho.

Livrar-se do medo de fazer alterações no código lembra o desfoque de uma determinada máquina. Se isso não for feito, a máquina irá parar - até que seja lubrificada e reiniciada.

Sem esse medo, o processo de trabalhar em programas é muito mais calmo do que antes. As solicitações de recebimento não são atrasadas até a última. O sistema de CI / CD executará os testes e, se os testes falharem, interromperá o processo de fazer alterações no código do projeto. Ao mesmo tempo, será muito difícil não notar mensagens de erro e informações sobre exatamente onde elas ocorreram.

Este é o ponto inteiro.

Caros leitores! Você usa o TDD ao trabalhar em seus projetos?

Source: https://habr.com/ru/post/pt450316/


All Articles