Escrevo Testes Automatizados, mas não pratico TDD!

Como você pode se considerar um profissional se você não sabe se todo seu código funciona? Como você pode saber se todo seu código funciona se não testa ele toda vez que faz uma mudança? Como pode testar seu código todo, toda vez que faz uma mudança se você não tem testes de unidade com um alto índice de cobertura? Como você pode ter um alto índice de cobertura por testes automatizados sem praticar TDD? (Uncle Bob)

Perguntas difíceis! Não é mesmo?

De forma geral, concordo com todos os questionamentos propostos (e suas conclusões), e, como Uncle Bob, tenho dificuldades em perceber um código com baixa cobertura de testes automatizados como tendo sido desenvolvido por um bom profissional. Entretanto, não penso que a única maneira de atingir este alto índice de cobertura seja através da prática do TDD.

Não me entenda mal. Já fui praticante, até mesmo defensor, da prática de desenvolvimento/design orientado por testes. Mas, não sou mais! Atualmente, tendo a acreditar, inclusive, que essa prática mais atrapalha do que ajuda.

Salvo quando estou corrigindo bugs, onde acho muito prático e razoável escrever um teste que demonstre a falha primeiro, para depois iniciar meus esforços de correção, abandonei a prática do TDD completamente.

Como trabalho?

Basicamente, meu processo de desenvolvimento pode ser descrito em três momentos distintos. Sendo os dois primeiros:

  • Experimentação – Quando estou criando algo, costumo não me preocupar tanto com design do código, qualidade ou organização. Se tenho algum teste, este é bem genérico, geralmente indicando algum caso de sucesso. Quase nunca este teste é de unidade. Nesse momento, o importante é “botar para fora” a idéia que está na minha cabeça.
  • Estabilização – Tendo criado uma base de código relevante que representa a visão (geralmente caótica) que eu tinha de como resolver o problema, começa a fase de estabilização. Aqui, a ideia é tentar aplicar alguma organização e, aqui, começam a ser escritos alguns testes. Os testes, quase sempre, me ajudam a polir o meu código. Como desenvolvo APIs, são os testes que me ajudam a perceber oportunidades de melhoria.

Fico alternando fases de experimentação e estabilização sem me preocupar muito com os tempos destinados a cada uma delas. Geralmente, no início de uma tarefa, costumo ser mais experimental. Então, na medida que o projeto avança, aumento meus esforços de estabilização.

A chave, penso eu, para conseguir ter boa cobertura de testes automatizados é ser exigente na fase de estabilização. De forma geral, não consigo entender um código como estável sem que ele atinja por volta de 90% de cobertura.

Por fim, com meu código estabilizado, entro em um último momento:

  • Otimização – Costumo não perder muito tempo pensando em performance quando estou nos momentos “experimentação” e “estabilização”. Ali as ideias são outras. As primeiras versões dos meus códigos costumam ter performance bem ruim. As últimas costumam ser realmente rápidas (e economicas em termos de recursos computacionais).

IMPORTANTE: Não inicio nenhuma OTIMIZAÇÃO em código que não esteja ESTABILIZADO (ou seja, com ótima cobertura por testes automatizados).

Resumindo, meu processo é ExperimentaEstabilizaOtimiza.

Gostaria de ser praticante de TDD. Mas, ao longo de todos esses anos, confesso que a ideia acabou não funcionando para mim.

Como é o seu processo?

Compartilhe este insight:

14 respostas

  1. Elemar, Obrigado por compartilhar seu processo.

    Atualmente não tenho um processo definido, porém estou procurando melhorar isso.

  2. Perfeito Elemar…, na maioria das vezes trabalho nesse fluxo 🙂

    Desde sempre achei muito estranho o uso do TDD como um diferencial em processos seletivos ou como regra de mercado em geral. TDD ajuda a criar um fluxo de codificação bacana e tem vários benefícios, principalmente quando utilizado de forma responsável. Mas não deveria ser usado como regra para rotular um “bom” profissional ou para definir que todo “bom” desenvolvedor deveria utilizar.

    Conheço alguns profissionais, principalmente na área de AI, que por conta da bagagem tanto de conhecimento de linguagem como matemático também preferem esse fluxo. Inclusive para um deles TDD é uma adestração desnecessária para seu fluxo de trabalho (Palavras do meliante).

    Acredito que o segredo está em experimentar, avaliar e melhorar (com ou sem TDD). É assim com todo e qualquer processo em qualquer área.

  3. Fala grande Elemar,

    Olha vc fazendo eu botar um comentário num blog (fazia tempo!).

    As vezes incomoda esse jeitão de nós desenvolvedores entendermos as práticas como preto e branco. 1 ou 0. Faz ou não faz. Vc pode não praticar TDD o tempo todo e ainda assim praticar TDD. Eu lembro quando tive um impasse numa das empresas que trabalhava por causa de pair programming. Adoro pair programming! Mas botaram uma “lei” lá que: não pode subir código pra produção se não for escrito com pair programming. Isso gerou um problema que era, se o time tava ímpar, um ficava sem fazer nada e tinha q ter um pool de tarefas q não mexem em código de produção só pro cara ter o q fazer. PARA TUDO!

    Será que não dá só pra não ser strict com as coisas, ter bom senso?

    Aí eu no auge do meu desespero pensei: vou escrever pro Kent Beck. Ele respondeu: (literal): “I’m not going to get in the middle of your disagreement. I always start change with myself, so in your situation I would begin keeping a diary of the personal costs and benefits of pairing, then share my observations once I had some data.”

    Eu acho que TDD é a mesma pegada. Ajuda pra caramba. Dá pra fazer TDD 100% do tempo, eu acho que não. Principalmente em código legado. Mas também não dá pra usar o TDD como desculpa pra não escrever teste com a mesma ladainha do: diminui a produtividade, teste quebra, é perda de tempo, etc. Tem estudo científico provando a eficiência… mas essa ladainha é usada mesmo pra unit test, sem TDD.

    Então respondendo a tua pergunta. O jeito que eu trabalho é:
    – Desde um projeto que eu trabalhei que o time todo comprou a idéia e entregamos 99.8% de cobertura (pq os .2% a porcaria do jacoco não pegava os try/finally) eu descobri que 100% de cobertura não só não é impossível, como não é difícil. Quando vc quebra suas dependências e tua arquitetura não é um monolito, escrever 80, 90 ou 100% de cobertura não faz muita diferença. Agora se vc tem um monolito, chegar em 10% é difícil pra caramba. Passar dos 30% muito difícil. Essa é a situação que eu vejo com legados.
    – Eu “busco” escrever as coisas com TDD. As vezes eu dou uma distraída e escrevo muito código ou muito teste up/front, mas tento voltar pro loop assim que eu percebo que perdi a mão.
    – Já peguei código cascudo q se não tivesse feito TDD bonitinho desde o começo não tinha chegado no final. Completamente não óbvio o funcionamento do código.

    Eu também gosto da idéia de “prototipar”. Eu vou escrever uma feature e não tenho a menor idéia do q eu quero. Eu abro uma branch, saio remendando tudo até entender o q eu quero. O código claro, vai ficar um lixo. Aí eu deixo essa branch como referência e uma vez que conheço o comportamento que espero do código, vou lá e escrevo tudo de novo, usando TDD. O mesmo serve pra bug. Quando vc sabe onde é o bug, é fácil ir lá e escrever um teste. Quando não sabe, as vezes tem que prototipar um pouco.

    Por último, vai a experiência com legado. Eu tenho pego uns códigos de games, bem complicados, grandes, cascudos. Não consigo digerir o código de jeito nenhum. Pra mim até nesse cenário, os testes tem ajudado. Eu faço alterações não-funcionais no código, do tipo, inversão de dependência pra tornar a classe que eu quero instanciável (parece piada, mas é por aí mesmo. Quando instanciar uma classe inicializa um driver por debaixo do pano, é nesse nível) e então saio cobrindo o comportamento que está lá com testes. Esse processo me faz entender o que a classe faz e capturar os requisitos, aí eu tenho coragem de refatorar.

    Pra resumir a conversa toda, eu acho que TDD é uma ferramenta muito legal, mas eu consigo viver num mundo em que essa ferramenta não é a única forma de escrever código. Só é difícil pra mim conviver hoje com a idéia de que dá pra manter um time saudável e produtivo mantendo código a longo prazo sem uma cobertura por unit test representativa.

    Abraço,

    Eric

    1. “Por último, vai a experiência com legado. Eu tenho pego uns códigos de games, bem complicados, grandes, cascudos. Não consigo digerir o código de jeito nenhum. Pra mim até nesse cenário, os testes tem ajudado. Eu faço alterações não-funcionais no código, do tipo, inversão de dependência pra tornar a classe que eu quero instanciável (parece piada, mas é por aí mesmo. Quando instanciar uma classe inicializa um driver por debaixo do pano, é nesse nível) e então saio cobrindo o comportamento que está lá com testes. Esse processo me faz entender o que a classe faz e capturar os requisitos, aí eu tenho coragem de refatorar.”

      Algo que pode ajudar são testes de caracterização: https://michaelfeathers.silvrback.com/characterization-testing

  4. É excelente ver pessoas que consideramos sensatas reforçando aquilo que pensamos e praticamos.

    Minha prática é bem próxima do que você descreveu, pra não dizer exatamente a mesma, e provavelmente pelos mesmos motivos.

    TDD é muito bom quando se precisa de disciplina para o design de um ou outro componente de software mais complexo, mas como regra mais atrapalha que ajuda mesmo.

    Excelente artigo. Obrigado.

  5. interessante sua abordagem.

    Por anos eu estudei sobre TDD, li e assiste tudo que podia sobre o assunto, mas nunca consegui empregar de maneira professional.

    Faz pouco mais de um ano que trabalho em um time que tem TDD como “regra”. Coloquei entre áspas pois não é imposto, fazemos pois acreditamos que os benefícios que obtemos prevalecem sobre os malefícios. Mesmo sem um dogmatismo por trás do emprego da técnica, constantemente refletimos sobre a qualidade dos testes escritos e se os passos foram os adequados.

    Na minha limitada experiência, escrever o teste depois do código nunca deu certo. O sentimento que eu tinha é parecido com uma comparação que li uma vez, acho que do Misko Hevery (criador do Angular, se não me engano), “Testes depois do código é como fazer um bolo e esquecer de adicionar o açucar. Você pode tentar compensar adicionando uma cobertura doce, mas não é a mesma coisa”. É um exemplo simplório, obviamente, mas ilustra algo que percebi não só comigo mas em outros, de que o teste passa a ser opcional. Em momentos de pressão, a primeira coisa que vem à mente é abandonar os testes. Na maioria das vezes essa decisão acaba não se pagando com o passar do tempo.

    Um outro ponto interessante é sobre a cobertura do código. Eu tenho notado um aumento no interesse por esse tópico, inclusive pessoas colocando certas metas, etc. Essa é uma abordagem da qual eu definitivamente discordo. Ao meu ver, cobertura de código só é útil em uma única situação: No primeiro contato com um fonte do qual você desconhecesse completamente. E ainda sim, não confiaria nos números, apenas tiraria uma base através deles. A razão disso é de que cobertura é um número vago. Não te diz nada além de que, um ou mais testes invocaram código x ou y. Dá pra cobrir 100% assim, inclusive sem fazer nenhum assert.

    É claro que isso é radical, mas dependendo do tamanho do time, ainda mais se ele é espalhado pelo globo, não dá para ter certeza do que outros estão fazendo, ao colocar code coverage como uma meta, abrimos esse tipo de precedente. Penso que é um exemplo perfeito do que dizia Charles Goodhart:
    “When a measure becomes a target, it ceases to be a good measure.”

    Quanto a abordagem de: “Experimenta – Estabiliza – Otimiza”. Não soa um pouco como TDD? Digo, tirando obviamente o custo (tempo) para fazer os testes antes, vejo cada etapa dessas como sendo um dos fluxos do TDD, só que talvez com uma liberdade maior em cada passo. Fora que, usando a técnica, fica dispensado a preocupação com code coverage ou se o código está estabilizado o suficiente. Quando cada linha de código foi feita para um teste passar, toda linha tem um motivo para existir.

    Só para deixar claro, fiz alguns contrapontos, mas não acho TDD algo perfeito, nem acho que necessariamente se encaixa em todas as situações. Um dos grandes problemas é de que, assim como aprender a codificar, TDD é pura experimentação. O processo de praticar pode ser desgastante e frustrante, pois não existe certo ou errado, necessariamente. Como existe muito dogmatismo em torno disso, é natural que o individuo se questione todo tempo se fez os passos corretamente ou se seguiu todas as normas. Até mesmo se sentir mal quando esquece alguma etapa. O importante é tentar vencer essa barreira inicial e tirar as conclusões á partir daí.

    No mais, ótima leitura. Obrigado!

    1. Então… vou colocar uma pimenta nessa discussão. Eu tenho trabalhado muito com métrica de código e tendo uns resultados muito legais.

      Essa frase q vc colocou sobre começar a medir a cobertura e logo as pessoas escreverem testes simplesmente pra bater a meta de cobertura tem muito a ver com a resposta do Kent Beck. Vc já viu algum estudo, dado sobre esse assunto? Eu nunca vi nenhum exemplo. Agora gente usando isso como desculpa pra atingir uma cobertura irrelevante (< 10% ou 0%, ou não fazer teste nenhum) eu vejo quase que diariamente.

      Minha provocação é no sentido que tudo que conhecemos sobre gestão de equipes de desenvolvimento, gestão no sentido business da coisa é que usa-se 2 variáveis: custo e prazo. Pq essas vc consegue medir de forma objetiva. Por essas e outras qualidade é sempre baseado na opinião do desenvolvedor (subjetivo). E como desenvolvedor tá sempre reclamando de qualidade de código, acaba-se jogando isso pra escanteio.

      Meu ponto é. Medir cobertura não é perfeito. Mas é uma das poucas métricas objetivas de qualidade que se tem em software. Se vc mede cobertura, vc tem um build automatizado, vc roda seus testes no build, vc faz algum tipo de teste automatizado e ainda sabe quantas linhas bateu. Eu tô tentando inventar outras métricas mais objetivas e entendendo como elas impactam o número de defeitos de uma aplicação, justamente pra cruzar com cobertura.

      Pra mim mesmo que se tenha uma baixa qualidade de teste (pouco assert, teste de integração misturado com unit test, etc), se vc tiver uma cobertura alta ao menos código com pouca dependência vc terá, já que quando vc começa a passar dos 30, 40% de cobertura, não dá pra ficar testando só as "folhas" da árvore de dependência, logo vc vai aprender a fazer inversão de dependência e vai testar o resto. Isso pra mim já é uma evidência de qualidade em si. Já que pra mim eu me convenci dos testes unitários e do TDD (graças ao Maurício Aniche) quando percebi os benefícios que o teste trás para o design.

      Mas não precisa concordar comigo. Meu ponto é só que: a falta de métricas objetivas de qualidade é o que faz só custo e prazo ser levado em consideração. O que faz vender refactoring tão difícil.

      Ou ainda como o Abílio Diniz gosta de dizer: "gerenciar é medir". O que significa também que não se gerencia aquilo que não se mede.

      1. Cobertura do código garante que ele foi testado, mas não garante a qualidade do teste. Para garantir a qualidade do teste só consigo pensar em teste de mutação, mas não acho que seja algo aplicável na prática.

      2. Concordo, eu mesmo quando a qualidade do código não está boa, eu coloco um teste no metodo principal, ficando com o code coverage de 100% e vou pra casa tranquilo, com a sensação de dever cumprido.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

Minha opção para “vender” os meus serviços, bem como os da EximiaCo, sempre foi buscar o reconhecimento, no lugar do...
Write code is not a simple task. It is easy to make mistakes that result in bad performance. The last...
Este post é uma releitura de um que havia escrito, em 2016, e que se perdeu no “reboot” do blog....
Parsing large files is a recurring and challenging task. Right? It is too easy to write slow code that consumes...
Neste post, compartilho um exemplo de implementação para um algoritmo extremamente famoso e importante: O algoritmo de Dijkstra. Trata-se de...
When designing systems that need to scale you always need to remember that [tweet]more computing power does not necessarily mean...
× Precisa de ajuda?