TDD Anti-Patterns
Este post foi reproduzido inteiramente do Site do A Maldita Cómedia.
Créditos ao seu Autor Victor Hugo Germano.
Aqui está a tradução (e considerações) para o texto de James Carr entitulado TDD Anti-Patterns
Acredito que essa contribuição é válida, pois ainda vejo muitas pessoas buscando materiais em português sobre testes, e encontrando pouca coisa de qualidade. Segue o texto abaixo:
Catálogo de Anti-Padrões em TDD
The Liar
Todos os metodos de um teste unitário estão passando perfeitamente, aparentando serem validos, entretanto sob uma inspeção mais próxima é descoberto que o teste unitário não testa o real intuíto para que foi criado.
Excessive Setup
Um teste que necessita muito trabalho para ser configurado antes mesmo de ser executado. Algumas vezes centenas de linhas de código tornam-se necessárias para adaptar o ambiente a um único método de testes, com dezenas de objetos envolvidos. Aqui a maior dificuldade é compreender “o quê” realmente está sendo testado dentro de toda a “sujeira” que um setup pode causar. (tradutor: Lembrem-se sempre do princípio KISS)
The Giant
Um teste unitário que, mesmo sendo verdadeiro na intenção de validar um objeto, pode possuir centenas de linhas contendo inúmeros casos de teste (inúmeros mesmos). Esta pode ser uma indicação do que chamamos de God Object, objeto que possui responsabilidades demais dentro do sistema. Indício claro de alto acoplamento em seu sistema.
The Mockery
Muitas vezes um mock pode ser útil e bastante indicado. Mas desenvolvedores podem perder tempo desnecessariamente esforçando-se em mockear o que não está sendo testado. Percebe-se neste caso que a classe possuí tantos mocks, stubs ou fakes que no final das coisas o sistema não está sendo testado, mas o que é retornado da interação entre os mocks. (tradudor: use apenas o que for estritamente necessário!)
The Inspector
Um teste unitáro que viola o encapsulamente em um esforço de atingir 100% de cobertura de testes, mas está situação nem sempre é favoravél, pois qualquer tentativa de refactor pode quebrar testes desnecessariamente, necessitanto adequações nas classes de teste unitário.
Generous Leftovers
Uma instância de um teste unitário cria um dado que é persistido em algum lugar, e outro teste utiliza tal dado para seus próprios asserts. Caso algo saia errado, o teste que utiliza o dado cadastrado também falhará. (tradutor: Testes devem ser independentes! )
The local Hero
Um teste que é dependente de algo específico do ambiente de desenvolvimento em que ele foi escrito. O resultado: o teste passa perfeitamente na células de desenvolvimento, mas falha quando alguém tenta executá-lo fora desse ambiente.
The Nitpicker
Um teste unitáro que compara toda a saída quando o que lhe deveria interessar é uma pequena parte apenas, assim o teste deve se manter sempre alinhado com detalhes que o teste não deveria tratar. Esta situação é endemica em testes de aplicações web.
The Dodger
Um teste unitário que possui muitos testes para efeitos pequenos (e presumidamente simples de testar), mas nunca testando o comportamente real desejado. Encontrado em testes relacionados para testes de banco de dados, onde um método é chamado, e então o teste seleciona itens do banco e procede asserts contra os resultados.
The Loudmout
Um teste unitário (ou suite de testes) que enxe o console com mensagens de diagnóstico, logs e qualquer outro tipo de saídas, mesmo quando os testes estão passando. Algumas vezes durante a criação dos testes existe o desejo de manualmente ver a saída dos metodos, mas mesmo eles deixando de serem necessários, são deixados para trás.
The Greedy Catcher
Um teste unitário que trata exceções e sobrepões pilhas de execução (stack trace) algumas vezes com mensagens menos informativas, mas algumas vezes ainda apenas logando (Loudmouth) e deixando o teste passar.
The Sequencer
Um teste unitário dependente de uma lista que sempre é retornada em forma desordenada.
Hidden Dependecy
Primo de primeiro grau do “The Local Hero”, um teste unitário dependente de um dado que deve ser populado em algum lugar par ao teste rodar. Se o dado não estive presente, o teste falhará deixando pouca informação para o desenvolvedor o que é necessário, ou porque o teste falhou… forçando-o a buscar através de uma floresta de código para descobrir de onde vem o dado que o teste deveria utilizar.
The Enumerator
Um teste unitário em que os nomes de métodos são apenas uma enumeração: teste1, teste2, teste3. Como resultado, a intenção dos testes torna-se pouco clara, e a única maneira de ter certeza é ler o código fonte e rezar para que esteja bem escrito.
The Stranger
Um método de teste que nem ao menos pertence ao Teste Unitário que ele está inserido. O método está realmente testando um objeto separado e independente, normalmente um objeto utilizado pelo objeto que sofre o teste.
The Operating System Evangelist
Um teste unitário que está ajustado apenas para um determinado sistema operacional para que possa funcionar. Um bom exemplo seria um caso de teste que utilize o separador de linhas do Windows para um assert, que falha apenas rodando em Linux.
Success Against All Odds
Um teste escrito para passar antes mesmo de falhar. Como um infeliz efeito colateral, o caso de teste acaba sempre passando mesmo que tenha sido feito para falhar.
The Free Ride
Ao invés de escrever um novo teste para uma nova funcionalidade ou característica, apenas um novo assert é criado ao final de um teste já existente.
The One
Uma combinação de alguns outros padrões, particularmente o TheFreeRide e TheGiant. Um teste unitário que contém apenas um único metodo que teste todo tipo de funcionalidade que um objeto pode conter. Um indicador comum é que o teste possui o mesmo nome da classe, e ainda com múltiplas linhas, setups e asserts
The Peeping Tom
Um teste que, compartilhando recursos, pode ver o resultado de outro test, e pode falhar mesmo que o sistema testado esteja em perfeito funcionamento. Verificado na ferramenta Fitnesse, onde a utilização de variáveis estáticas para abrigar coleções não eram corretamente limpas após a execução do teste, podendo surgir erros durante a execução de qualquer teste. Também conhecido com TheUninvitedGuests.
The Slow Poke
Um teste unitário que é incrivelmente lento para ser executado. Quando o teste é iniciado, os programadores podem ir ao banheiro, fumar um cigarro ou pior ainda, inicar o teste no final do dia e ir para casa, esperando que o resultado saia no dia seguinte.
Mocks vs Stubs: qual a diferença afinal?
Mock Objects representam uma abordagem para simular objetos dentro dos testes. Isso nos ajuda quando o objeto real é muito complexo, lento ou ainda nem existe. Até este ponto mocks são bem parecidos com stubs.
A diferença entre eles está nas abordagens que eles propõem:
Com stubs, nos preocupamos em testar o estado dos objetos após a execução do método. Neste caso incluimos os asserts para ver se o método nos levou ao estado que esperamos.
Com mocks, a preocupação é testar a interação entre objetos durante a execução do método. Neste caso, os asserts servem para ver se os métodos se relacionaram como o esperado.
Desta forma conseguimos testar com a mesma eficiência dos stubs, mas com a vantagem de não precisarmos implementar nenhuma classe extra. Este também é o motivo que normalmente utilizamos stubs apenas quando estes são necessários, enquanto os mocks podem ser utilizados largamente nos testes quando queremos aumentar o isolamento entre as classes que estão sendo testadas.
Fake it (til you make it)
A técnica Fake it consiste em:
Escreva um teste que falhe. Para fazê-lo passar, utilize constantes. Vá escrevendo novos testes e gradualmente substituindo as constantes por variáveis.
Um exemplo simples: gostaria que um método me informasse se um número é par ou ímpar. Neste caso, eu posso começar com o seguinte teste:
public void testNumeroPar()
{
assertTrue(verificador.ehPar(4));
}
Eu poderia tentar escrever a lógica deste teste, mas vou fazê-lo passar inicialmente com uma constante:
public boolean ehPar(int valor)
{
return true;
}
O teste passa, mas é fácil ver que o método não faz tudo que queremos, o que me obriga a escrever um novo teste:
public void testNumeroImpar()
{
assertFalse(verificador.ehPar(3));
}
Mais uma vez, posso usar uma implementação utilizando constantes. Então, modificamos o método ehPar:
public boolean ehPar(int valor)
{
if(valor==3){
return false;
}
return true;
}
Mais uma vez os testes passam, mas sinto que não cheguei onde eu queria. Vou escrever outro teste:
public void testNumeroImparSete()
{
assertFalse(verificador.ehPar(7));
}
Agora meu teste falha, mas eu posso fazê-lo passar aproveitando para remover a duplicação da constante “3″ (que está tanto no teste quanto no método ehPar):
public boolean ehPar(int valor)
{
return (valor % 2 == 0) ? true : false;
}
Todos os testes passam e eu posso assumir que meu método está completo.
Esta técnica, embora seja simples, pode causar aversão à primeira vista. No exemplo acima é fácil imaginar a solução após escrever o primeiro testes, então o programador desavisado pode pensar: “por que diabos vou retornar uma constante?”. Simples: isto te obrigará a escrever novos testes e a pensar um pouco mais sobre o problema que está tentando se solucionar. Além disso, no final você terá vários testes para provar que a sua implementação funciona. Portanto, esta técnica trás dois benefícios:
Aumenta a confiança: uma vez que você terá vários testes para provar que a implementação continua funcionando mesmo depois de um refactoring.
Ajuda a controlar o escopo: já que você pode tratar um problema isolando um pedaço dele e gradativamente adicionando complexidade à solução.
Apesar de às vezes ir contra a natureza criativa dos programadores, esta técnica nos ajuda a trabalhar com um foco bem definido, o tempo todo.
Mais detalhes sobre esta técnicas podem ser encontrados no capítulo 28 (Testing Patterns) do livro Test-driven Development By Example, do Kent Beck.
5 razões para ter um coding dojo na sua empresa
1. É a maneira mais fácil e barata de investir em treinamento
As reuniões do dojo não precisam muito mais que um computadores e um projetor emprestados. Pagar pizza e umas cervejas para o final das reuniões também não sai caro, e com certeza é muito bem-vindo. Agora, se a empresa for bacana mesmo, pode-se tentar algumas reuniões no horário de trabalho para incentivar que mais pessoas participem. Nada disso custa rios de dinheiro, nem é muito complicado. Só é preciso um pouco de iniciativa por parte dos desenvolvedores e bom senso em investir nas pessoas por parte da empresa.
2. Estimula habilidades sociais e de auto-organização
Muitos desenvolvedores têm alguma dificuldade para falar em público, expor suas idéias e colaborar com outras pessoas. Outros têm dificuldade de se organizar, trabalhar em equipe ou até mesmo liderar. Quando é que uma empresa investe neste tipo de habilidade de um desenvolvedor? Raramente. O dojo é um ótimo começo para estas pessoas, e mesmo quem não possui nenhuma dessas dificuldades acaba tendo a chance de aprender coisas novas e explorar pontos que precisa melhorar.
3. É publicidade para a empresa
Se a empresa já deu o primeiro passo e os desenvolvedores estão confortáveis com a rotina do dojo, por que não abrir as portas para outras pessoas? O nome da empresa vai ficar associado aos princípios ágeis por trás do dojo e ninguém terá que sair do escritório para fazer networking. E de quebra, pode ser que nem precisem mais postar anúncios de emprego por aí, já que candidatos em potencial já farão parte da rotina da empresa.
4. Incentiva os desenvolvedores a participarem da comunidade
Divulgando o dojo da sua empresa em listas de discussão e fóruns das tecnologias que são aplicadas nas reuniões podem dar um incentivo a mais para os desenvolvedores trocarem idéias na comunidade de desenvolvimento de software em geral. Além disso pode ser o primeiro passo para participação em projetos open source, publicação de artigos e participação de conferências.
5. Quebra a rotina
Programar algo diferente dos projetos do dia-a-dia, em outras linguagens, usando outras técnicas, com outras pessoas e numa dinâmica bem mais informal do que os projetos das empresa exigem pode ser muito estimulante no final das contas.
Então, será que precisa de mais motivos? Infelizmente não estou mais no Brasil para poder participar das reuniões que estão acontecendo por aí, mas ficarei muito contente em ajudar qualquer um que queira botar um dojo em prática no seu ambiente de trabalho. As listas do dojo de SP e Floripa também estão de portas abertas para tirar dúvidas e incentivar essa idéia.
Top 5 desculpas de quem não quer programar orientado a testes
Este post foi reproduzido inteiramente do Site do Dojo Floripa.
Créditos ao seu Autor Ivan Sanches.
Quando comecei a usar TDD regularmente, várias vezes tentei me enganar para de alguma forma poder ignorar os testes e partir direto para codificação. E inevitavelmente depois eu via que com os testes eu poderia ter facilitado a minha vida.
Depois de um tempo eu vi que outras pessoas também usavam as mesmas desculpas que as minhas. Então resolvi escrever comentando um pouco sobre as principais desculpas para não fazer TDD que eu conheço. São elas:
1. Vai demorar muito mais: esta é uma das mais populares, utilizada inclusive pelos gerentes que se enganam pensando que ter qualquer coisa pronta é sinal de produtividade. Só que a maioria das pessoas adeptas desta desculpa pensam que o tempo de desenvolvimento de uma funcionalidade é só aquele utilizado para entregá-la. Supondo que uma pessoa produziu todo o código utilizando basicamente seus instintos e disse “terminei” sem nenhum tipo de validação, os riscos de ter alguma coisa incorreta aumentam bastante. E é neste ponto que TDD apresenta uma vantagem. O programador é obrigado a explorar melhor o problema, e ao terminar de codificar já terá uma série de testes automatizados para mostrar que aquilo que foi feito funciona. Ou seja, para entregar pode até ser que demore mais, mas para ter uma funcionalidade de acordo com os requisitos e sem defeitos o tempo provavelmente será menor.
2. A funcionalidade é muito fácil: se depois de uma breve avaliação o sujeito achar que a solução a ser programada é muito fácil, ele está correndo três riscos: 1) Não entendeu todas as variações do problema; ou 2) Vai programar com displiscência; ou 3) Planeja usar e abusar de um dos piores inimigos da programação, o Ctrl+C, Ctrl+V. Em todos os casos a possibilidade de fazer besteira é iminente e poderia ser diminuída com testes, onde o problema seria explorado melhor antes do código e haveria a chance de remover a duplicação de código com auxílio de testes. Estes testes também confirmariam que a mudança não quebrou outra parte do sistema, o que indica que deveriam haver testes para todo o sistema.
3. Não sei como testar: quem está começando a usar TDD tem essa desculpa na ponta da língua. É como se não conhecesse uma biblioteca e isto o impedisse de programar. Mas programar orientado a testes não é tão difícil assim, afinal se trata apenas de programação, coisa que já estamos acostumados. Basta pensar no problema que as perguntas surgem e aí estão os testes. Escreva a pergunta, pense na resposta e a escreva também. Com o tempo as perguntas vão ficando melhores, mas não saber fazê-las não pode ser a razão para não começar.
4. Isso não dá pra testar: este é um mito que não tem fundamento. Tudo é testável, embora alguns casos sejam mais difíceis ou exijam um pouco mais de pesquisa. Dificilmente você terá um cenário que outros não tenham conseguidos testar, e neste caso é só aprender como se faz. Se ainda assim o cenário for inédito na literatura, existe uma comunidade grande sempre disposta a ajudar, então só basta querer. Ah! E na maioria dos casos usar mocks e stubs resolvem os problemas ditos como “não testáveis” pelos iniciantes.
5. Melhor deixar os testes com testadores: aqui é um exemplo de que não se entendeu pra que serve TDD afinal. Escrever testes de unidade por si só não significa programar orientado a testes, uma vez que os testes são escritos antes do código existir e de maneira incremental. Então deixar os testes com os testadores significa abrir mão de uma ferramenta de modelagem simples, pensando que esta se trata apenas de testes.
Abrace as mudanças no seu software
Este post foi reproduzido inteiramente do Site do Dojo Floripa.
Créditos ao seu Autor Ivan Sanches.
Um dos maiores fatores de estress em projetos de software é a mudança desordenada no escopo. As funcionalidades mudam durante o projeto com mais frequencia do que a equipe gostaria, e isso se reflete negativamente por todos os lados.
A questão é que a maioria das pessoas que já se envolveram com projetos de software sabem que as mudanças fazem parte (e a minoria se engana achando que não), porém dificilmente as pessoas se preparam para quando isso acontece. E os resultados mais comuns disso são: cliente insatisfeito, desenvolvolvedores frustrados e prazos/custos estourados. Isso sem mencionar a qualidade do produto final.
Por isso proponho 2 tarefas para melhorar este cenário:
• Defina datas para as mudanças acontecerem
Duas coisas que incomodam os desenvolvedores são: interrupções e trabalho jogado fora. Uma maneira fácil de evitar isso é educar o cliente* para que faça seus pedidos apenas em datas pré-definidas. Obviamente o cliente vai continuar pedindo alterações a qualquer momento, mas é preciso deixar claro quando que estas alterações serão trabalhadas e o qual será o impacto delas.
Se o cliente começar a ficar insatisfeito com o quanto ele precisa esperar para ter suas mudanças, é sinal que estas datas precisam estar mais próximas uma das outras.
• Automatize seus testes
Agora é a vez dos desenvolvedores fazerem a sua parte. Uma vez que as mudanças do cliente já são esperadas, é preciso fazer com que estas mudanças no software não signifiquem queda na qualidade. E é aí que entram os testes automatizados. Poder verificar facilmente todas as funcionalidades do sistema a qualquer momento é o que dá confiança para um desenvolvedor mudar qualquer coisa a qualquer momento.
O impacto de uma mudança de requisito pode ser maior ou menor, e os testes permitem verificar com mais clareza como a mudança se reflete no resto do sistema. Isto acontece porque uma vez que uma parte do software é modificada e todos os testes são executados, todos os pontos onde esta mudança causou problema são descobertos na hora. E com este feedback rápido os desenvolvedores poderão verificar se suas estimativas estavam corretas e se a implementação está correndo como o planejado.
Em resumo
Permitir que mudanças de escopo sejam bem-vindas num projeto de sofware precisa ser um esforço tanto de cliente quanto desenvolvedores e a palavra-chave para diminuir o estress é disciplina. Quanto mais previsíveis forem os clientes e mais testes automatizados forem escritos, melhor serão aceitas as mudanças.
O que é TDD, e como utilizá-lo no dia a dia.
Test-Driven Development (TDD) ou Desenvolvimento Orientado a Testes é uma maneira diferente de escrever software. A diferença entre esta abordagem e a que você provavelmente está acostumado é que com TDD você vai evoluindo seu código aos poucos, conforme vai explorando o problema com o uso de testes automatizados escritos antes da solução sequer existir.
Algumas vantagens desta abordagem são:
• Incentiva a simplicidade: como a solução vai surgindo pouco a pouco, a tendência é que não se perca tempo com aquilo que não tem certeza que será usado em seguida. Expressões como “You Ain’t Gonna Need It (YAGNI)” e “Keep It Simple, Stupid (KISS)” são recorrentes quando se está programando orientado a testes.
• Aumenta a confiança no código: o sistema funciona de uma determinada maneira porque existem testes que foram utilizados durante sua criação e validam o que foi criado. E se ainda assim algum erro surgir, um novo teste é criado para reproduzí-lo e garantir que depois de solucionado ele não irá se repetir.
• Ajuda como documentação: os testes quando bem definidos são mais simples de ler que o código e, embora nem sempre sirvam como uma especificação para o usuário final, eles são uma fonte eficiente para entender o que o software faz. Além disso, esta documentação sempre estará atualizada com a aplicação.
• Facilita refactorings: quanto mais testes existem no sistema, maior é a segurança para fazer refactorings. Um erro causado por algum refactoring dificilmente vai passar desapercebido quando um ou mais testes falharem após a mudança.
Em TDD, um teste é um pedaço de software. A diferença entre teste e o código que está sendo produzido é que os testes têm 2 funções principais:
• De especificação, ou seja, definir uma regra que seu software deve obedecer
• De validação, ou seja, verificar que a regra é obedecida pelo software
Geralmente os testes são criados com algum framework do tipo xUnit (jUnit, nUnit Test::Unit etc) , mas também podem ser feitos num nível de funcionalidades (através de softwares como o FitNesse e Selenium) . Estas ferramentas servem basicamente para organizar os testes e facilitar na criação das verificações.
O processo de criação de programação orientado a testes é simples:
1. Escreva um teste que falhe. Pense no que o código deve fazer, descreva o contexto e defina quais são as verificações que precisam ser feitas. Não há um limite no número de testes, então quanto menos coisa cada teste descrever/verificar, melhor. No início também não é preciso se preocupar se a classe/método ainda não existe. Pense primeiro no teste e só depois que este estiver pronto crie o esqueleto de código necessário para que ele compile e falhe ao rodar.
2. Faça o teste passar. Agora chegou o ponto crucial: escreva o mínimo de código para que o teste passe. Controle o instinto natural do programador de tentar prever tudo que o código vai fazer e apenas faça o teste passar. Mesmo que tenha certeza que o código deve fazer mais coisas, fazer os testes passarem deve ser a única preocupação agora.
3. Refatore. Uma vez que o teste passou, verifique o que no código pode ser melhorado. Geralmente para um teste passar é preciso inserir duplicação através de constantes (técnica conhecida como Fake It). Agora é a hora de melhorar o código e remover as duplicações, lembrando que os testes devem continuar passando.
Estes 3 passos são repetidos até que não se consiga pensar em novos testes, o que indica que a funcionalidade está pronta.