Tag: tdd

Pensando TDD

Ontem, dia 8 de outubro de 2014, ministrei uma palestra sobre TDD na Faculdade de Tecnologia de Sorocaba, durante a 21ª edição da Semana de Tecnologia, evento anual promovido pela faculdade para aproximar os alunos das práticas mais modernas do mercado.

TDD or not TDD?

Test-Driven Development (Desenvolvimento Orientado a Testes), ou simplesmente TDD, é uma disciplina de desenvolvimento ágil que, resumidamente, prioriza testes automatizados ainda na fase de projeto com o objetivo de obter software de qualidade, isto é, com código limpo e que funcione.

Indo direto ao ponto, TDD não é sobre TDD, mas sobre a disciplina e prática de como torna-se um bom Engenheiro ou Artesão de Software, dependendo da metáfora que você adota.

Embora esse assunto não seja novidade, é de muita importância para a maior parte dos profissionais de TI brasileiros, sem contar as empresas, pois infelizmente estamos de forma geral muito defasados com as melhores práticas reconhecidas no restante do mundo.

Veja, a redescoberta do TDD já tem mais de uma década e a maioria dos profissionais que encontro nunca viu um projeto que realmente aplicasse a metodologia. E o problema não é somente com relação ao TDD. Parece que somente agora empresas estão começando a aceitar certas práticas geralmente adotadas por processos ágeis, tais como testes automatizados, integração contínua, reuniões diárias, programação em par, etc.

A palestra, intitulada “Pensando TDD”, também não é necessariamente sobre TDD, mas sobre a necessidade de praticarmos o desenvolvimento de software como uma disciplina diária e não com a “lei do mínimo esforço” que, com o tempo, gera um déficit técnico tão grande que não resta alternativa a não ser literalmente jogar o software no lixo.

Material

Os slides da apresentação estão disponíveis no SlideShare e no Google Drive. Muitos deles são conceituais. Para ver comentários e informações adicionais sobre cada um, use a função de notas do SlideShare ou baixe o PPT.

Lembrando que a licença dos documentos que tenho produzido é Creative Commons. Você é livre para usar e modificar o material livremente. Só peço que cite a fonte original (meu blog, por exemplo).

Se tiver dúvidas sobre algum ponto, escreve para mim.

Agradecimentos

Por fim, gostaria de agradecer à organização do evento e à diretoria da Fatec de Sorocaba, assim como à GFT, empresa onde trabalho, por incentivar a participação em eventos e comunidades de tecnologia.

Obrigado também a todos os alunos que se mostraram muito receptivos a novos conhecimentos e suportaram a hora e meia comigo falando. Sucesso para todos vocês!

O TDD está morto?

Test Driven Development (Desenvolvimento Orientado a Testes) é uma metodologia de desenvolvimento de software bastante popular, que é centrada (adivinhe) em testes.

No entanto, apesar de muito propagada, há várias críticas a essa metodologia por profissionais e pensadores de destaque.

Por esses dias rolou uma conversa bastante interessante sobre o assunto entre, nada mais nada menos, que Martin Fowler (que elaborou a ideia de Injeção de Dependência), Kent Beck (idealizador do TDD e do framework JUnit) e David Heinemeier Hansson (criador do Ruby on Rails).

Vamos analisar os pontos altos da conversa nos próximos tópicos. Mas antes, uma breve revisão sobre TDD…

Como o TDD funciona

Ao contrário do processo “tradicional” de desenvolvimento que envolve Especificação, Implementação e Testes, o TDD começa pelos testes em cima das interfaces do sistema.

A princípio todos os testes irão falhar. O objetivo do desenvolvedor é fazer com que eles sejam executados com sucesso. Em tese, isso garante o foco no que realmente deve ser feito e fornece uma medida mais real do progresso.

Os testes utilizados no TDD geralmente são Testes Unitários. Ambos os conceitos estão muito relacionados, mas não são sinônimos.

Testes Unitários

Testes Unitários são aqueles onde uma única função ou método do sistema é testada de cada vez. Em geral esses testes são automatizados.

Isso pode parecer simples, mas implica em que a rotina em questão não pode depender de outras rotinas, caso contrário o teste é sujeito a falhas de terceiros.

Dessa maneira, a fim de testar as rotinas que naturalmente possuem dependências de outras classes e métodos, de recursos como bancos de dados ou arquivos e outros, os desenvolvedores criam simulações desses elementos para que a rotina a ser testada funcione, porém não dependa diretamente desses outros elementos durante o teste.

Este é o conceito de mock, isto é, algo que imita o elemento original.

Por exemplo, um método que normalmente retorna um valor do banco de dados poderia ser substituído por um método que retorna um valor fixo num teste em particular. Cada linguagem, framework e ferramenta deve prover formas adequadas para se fazer isso, sendo inclusive uma forma de medir a qualidade da mesma.

Benefícios desta abordagem

Escrever os testes antecipadamente e então desenvolver com base neles traz várias vantagens:

  • Melhora o entendimento dos requisitos, já que para escrever o teste é necessário que o desenvolver entenda logo de início o que o sistema deve fazer.
  • Provê um objetivo claro a ser alcançado durante o desenvolvimento, isto é, fazer os testes executarem sem erros.
  • Aumenta a visibilidade do que está ou não pronto, já que os testes servem como indicadores mais confiáveis do que algo que está implementado e “só falta testar”.
  • A automação dos testes possibilita a Integração Contínua com certa garantia do funcionamento do sistema, já que todos os testes unitários podem ser reexecutados como testes de regressão sempre que necessário.

Desvantagens

Como regra geral, tudo que traz vantagens tem seus pontos negativos. Sempre temos que tomar cuidado com certos “evangelistas” que irão fazer uma tecnologia ou metodologia parecer não ter um lado negativo ou impacto zero.

Isso se aplica também ao TDD. Vejamos algumas considerações sobre ele:

Aumento do esforço

Será necessário gastar muito tempo para criar os testes e todos os mocks, além da infraestrutura de Integração Contínua. É claro que isso pode ser visto como um investimento em qualidade ao invés de uma pena a ser paga, mas na prática nem todos podem se dar a esse luxo.

Aumento da complexidade

A arquitetura acaba sempre mais complexa, usando muita Injeção de Dependências e outras técnicas de desacoplamento.

Mudanças nos requisitos são muito mais custosas, já que além do código do sistema em si, todos os testes e mocks envolvidos deverão ser reescritos.

Se o arquiteto de software não souber o que está fazendo, a arquitetura do sistema pode acabar cheia de gambiarras.

Intrusividade no código principal

Em muitas situações, será necessário programar de uma forma específica para que um certo código possa ser testado.

A arquitetura da aplicação muitas vezes acaba sendo então afetada por elementos e fatores que, de outra forma, não fariam parte de sua natureza.

Enfim, o design da aplicação acaba sendo influenciado pelos testes, o que nem sempre é desejável, já que é mais uma coisa a ser levada em conta e pode desviar o foco do arquiteto.

Testar tudo é quase impossível

Muitas tecnologias são difíceis de testar com testes unitários ou mesmo com testes de integração.

Pense na interface do usuário, por exemplo. Quantas delas possibilitam a execução da interface de forma “desacoplada”? Existem ferramentas como o Selenium para sistemas web, mas com elas também vêm diversas outras limitações e dependências.

O TDD está morto?

Vimos que todo benefício traz um custo associado. É este todo o ponto da discussão sobre o TDD estar morto. O custo de testar tudo e criar os mocks necessários para todos os testes vale a pena? Vejamos alguns comentários a seguir.

David Hansson inicia a argumentação dizendo que muitas pessoas não conseguem trabalhar com TDD porque não faz muito sentido para determinados tipos de trabalho. Ele toca no ponto da necessidade de se ter muitos mocks e que isso acaba tornando a codificação difícil.

Kent Beck aproveita o gancho e relata de casos onde a equipe não conseguia refatorar o código porque os testes estavam tão acoplados com a implementação, com dois ou três níveis de mocks, que qualquer alteração afetava inúmeros deles. Ele também afirma que há cenários onde o TDD se aplica melhor, por exemplo, quando os requisitos do software a ser desenvolvido são claros o suficiente para escrevê-los em testes diretamente. Por outro lado, há situações onde simplesmente o desenvolvedor vai descobrindo aos poucos aquilo que deve fazer. Isso é recorrente na criação de frameworks ou algoritmos que precisam analisar dados não estruturados.

Fowler argumenta ainda que a maior vantagem do TDD é um código que consegue testar a si mesmo. Só isso poderia também ser alcançado de outras formas. Ele também comenta sobre aqueles que dizem que “você não está fazendo teste unitário de verdade” porque existem algumas dependências durante o teste. Segundo ele, o importante não é a pureza de uma definição, mas que o teste funcione e seja útil, até porque há variações da definição de testes unitários.

Conclusões

É importante entender que TDD não é a “bala de prata” do desenvolvimento de software. Ele traz consigo vários desafios e problemas em potencial, principalmente se não há pessoas suficientemente experientes na equipe.

Testar é essencial, mas para cada projeto podem haver diferentes formas de testes, cujo busto-benefício deve ser avaliado para cada caso.

O teste não deve ser um objetivo em si mesmo, ainda que adotemos o TDD. Ele apenas tem o objetivo de garantir que estamos entregando o que o cliente pediu.

O tempo e o esforço que são investidos em testes também devem ser avaliado. Isso afeta diretamente a qualidade, mas a qualidade tem o seu preço. Quem irá pagar o preço da qualidade?

Quanto a testes unitários, não devemos buscar toda a pureza possível. O importante é que ele teste, ou seja, funcione para aquela situação específica.

Concluindo, o conselho em geral é: use TDD com moderação.


Para quem consegue entender Inglês, aqui vai o link do Hangout com essas grandes personalizados:

Is TDD dead?

Is TDD dead?

Test Driven Development is a very popular software development methodology focused (guest what?) on tests.

However, despite of being well known, there’s some criticism on this methodology from prominent professionals and thinkers.

A few days ago, there was a very interesting talk about this issue between Martin Martin Fowler (that elaborated the Dependency Injection definition), Kent Beck (creator of TDD and JUnit framework), and David Heinemeier Hansson (creator of Ruby on Rails).

Let’s review some highlights from that talk in the next topics. But let’s start with a brief review of TDD…

How TDD works

Different from the “traditional” development life-cycle composed by Specification, Implementation and Test, TDD begins testing the interfaces of the system.

At first, all tests will fail. The developer’s goal is to make they succeed. In theory it guarantee the focus on what really should be done and provides a concrete measure of progress.

In general, TDD adopts Unit Tests. Both are different things, but closely related.

Unit Tests

Each Unit Test tests only one function or method of the system. Often they are automated.

It seems too simple, but implies that the routine under test can’t depend on other routines, otherwise the test is subjected to third part failures.

This way, in order to test routines that naturally depend on other classes and methods, resources like databases and files, and other stuff, developers need to create simulations of these elements so the routine works without them during the test.

This is the mock idea, i.e., something that mimics the original dependencies.

For instance, a method that would normally return a value from database could be replaced by a method that returns a fixed value for a particular test. Each language, framework, and tool should provide proper ways for doing that. This is also a manner to assess the quality of these tools!

Benefits of TDD

Writing tests in advance brings various advantages:

  • Helps in the understanding of the requirements, since you have to understand them and also what the system should do in order to write down tests.
  • Provides a clear goal in the development, that is, to make all tests to pass.
  • Increases the visibility of what is or not ready, since the tests are generally more reliable indicators of progress than something “implemented, but not tested”.
  • The automation enables Continuous Integration with some level of guarantee the system won’t break, since all unit tests can be executed over and over as regression tests.

Disadvantages

As a general rule, everything has its advantages and also its negative points. We always have to take care with “evangelists” that will make some technology or methodology looks like it hasn’t problems or has zero impact.

This also applies to TDD. Let’s see some of its reservations:

Increases the effort

It’s necessary to spend much more time creating tests and mocks, not to mention the Continuous Integration infrastructure. Of course it can be considered as an investment in quality instead of a penalty, but in practice not everyone can afford the overhead.

Increases the complexity

The architecture becomes more complex since Dependency Injection and other decoupling techniques are abundantly used everywhere.

Changes in requirements are much more costly, since you need to rewrite the code of the system itself plus all the tests and mocks involved.

If the engineer don’t know well what he’s doing, the system will end with tons of kludges.

Intrusiveness in the main code

In some situations, you’ll have to code in a specific way in order the code can be tested later. The application architecture is then affected by outside unnatural factors.

In short, the design is influenced by tests, what is not desirable since is one more thing to be considered and to grab the architect’s attention.

Testing everything is almost impossible

Many technologies are hard to test independently or even in integration tests.

Think about user interfaces, for example. How many technologies allow us to run it in a decoupled way? There’s tools like Selenium for web apps, but they also brings tons of other limitations and dependencies.

Is TDD dead?

We just saw every benefit comes at a cost. This is the main point about TDD being dead. Is the cost of testing everything and creating mocks for every dependency worthy? Let’s look at some comments about the aforementioned talk.

David Hansson starts arguing that lots of people can’t do TDD because of the nature of their work. He talks about the need of numerous mocks and how it makes coding more difficult.

Kent Beck continues and tell us about situations where the team couldn’t refactor their code because the tests were so coupled to the implementation, with two or three levels of mocks, that any modification affected countless of them. He also asserted TDD fits better certain scenarios, for instance, when requirements are clear enough so you can write them in form of tests directly. On the other hand, there are situations when the developer finds out little by little what he actually should do. This is recurrent in framework development and algorithms for analyzing non-structured data.

Fowler argues the major benefit of TDD is a self-testing code. But this could be achieved in other ways. He also talks about those who say “you aren’t doing unit tests right” because of some dependencies during the test. According to him, the “purity” of the definition is not so important, but the test should work and be useful. Furthermore, there’s alternative definitions of Unit Test.

Conclusions

It’s important to comprehend TDD is not the silver bullet of software development. It brings various potential challenges and problems, mainly if there’s not enough experienced people in the team.

Tests are essential, but for each project we could have different kinds of tests, whose cost-benefit should be evaluated for each individual case.

Tests shouldn’t be the goal in itself, even in TDD. Their true goal is guarantee we’re delivering what the client really needs.

Time and effort invested in tests also should be pondered. It affects directly the quality, but the quality has its price. Who will pay for it?

About unit testing, we shouldn’t pursue only purity. The important is the test tests, that is, it works for that situation.

Finally, a general rule could be: use TDD moderately.


Here is the link of the Hangout with those great guys:

Is TDD dead?

Creative Commons O blog State of the Art de Luiz Ricardo é licenciado sob uma Licença Creative Commons. Copie, compartihe e modifique, apenas cite a fonte.