Você está na página 1de 42

http://devagil.wordpress.

com/2007/01/28/21-desenvolvimento-dirigido-por-testes/

Desenvolvimento Dirigido por Testes


Cdigo limpo que funciona (ou Clean code that Works), foi para alcanar este objetivo que o TDD surgiu. Um cdigo ser tanto mais limpo quanto melhor for seu design. Uma forma de sempre ter cdigo limpo que funciona, utilizar o desenvolvimento fortemente guiado por testes automatizados que so escritos antes do cdigo. Este estilo de desenvolvimento foi chamado de Desenvolvimento Guiado por Testes ou Test Driven Development (TDD). TDD uma forma disciplinada de escrever cdigo limpo que funciona, e consiste de duas regras principais: y Cdigo novo s escrito se um teste automatizado falhar; y Todas as duplicaes devem ser eliminadas. So duas regras simples, mas que tem conseqncias complexas no comportamento individual e coletivo dos desenvolvedores. As implicaes tcnicas so as seguintes: 1. Desenvolvimento orgnico, onde cdigo em execuo gera o retorno necessrio para a tomar as decises que orientam o prprio desenvolvimento; 2. Cada desenvolvedor deve escrever seus testes; 3. O ambiente de desenvolvimento deve fornecer respostas rpidas para pequenas mudanas; 4. O projeto deve ter alta coeso, componentes fracamente acoplados, exatamente para permitir testar facilmente?????????????????????????? As duas regras tambm implicam numa ordem para as tarefas de programao 1. Vermelho Escreva um pequeno teste que no passa, e provavelmente at mesmo no compila na primeira vez; 2. Verde Faa o teste passar rapidamente, cometendo qualquer pecado que seja necessrio durante o processo; 3. Refatore Torne o cdigo mais fcil de entender e modificar. Pode-se comear eliminando todas as duplicaes criadas meramente para fazer o teste passar, por exemplo. Vermelho / verde / refatore O mantra do TDD.

1.1. Porque usar TDD


- Aprendizado Quanto maior for o tempo entre a insero de um erro no cdigo e sua descoberta pelo desenvolvedor, maior ser a probabilidade deste erro repetir -se em outros locais do sistema e menor a possibilidade de aprendizado. Por outro lado, se o erro descoberto alguns segundos aps ser introduzido, so maiores as chances do desenvolvedor corrigi -lo rapidamente, aprender com isto e passar a codificar melhor; conseqentemente reduzse muito as chances de problemas semelhantes repetirem-se no futuro. Para um melhor aprendizado e reduo da probabilidade de propagao de erros, fundamental que o ambiente de desenvolvimento fornea respostas rpidas para pequenas mudanas - Reduo dos custos e vulnerabilidades do sistema Escrever os testes antes, da forma que prega o TDD, equivale a prevenir-se de problemas futuros. As fases de desenvolvimento e depurao ocupam a maior parte do tempo de um projeto, sendo que a depurao geralmente s feita quando o software apresenta um ou mais erros (Bugs). O que torna a depura o muito custosa o tempo entre a insero do bug e o momento em que este detectado. Quanto maior o tempo, maior o

custo de depurao, porque para corrigir um problema, o desenvolvedor precisa recuperar o contexto em que este foi inserido, entender uma srie de coisas relacionadas, alm de descobrir o que pode estar gerando o erro. Ou seja, preciso re-aprender sobre a funcionalidade para que se possa corrigi-la. Devido a grande complexidade envolvida no processo de desenvolvimento de software comum ocorrerem erros dos mais variados tipos. impossvel evitar que estes erros ocorram ao longo de um projeto, entretanto possvel fazer alguma coisa em relao quando estes defeitos so detectados e qual o impacto que causaro no cronograma do projeto. S isto, causa um grande impacto na velocidade de desenvolvimento e na qualidade do software. O desenvolvimento dirigido por testes segue o caminho da preveno. Para isso preciso incorporar hbitos que resultem numa menor probabilidade de ocorrncia de erros. Mesmo assim, possvel que erros ocorram. Neste caso, os testes fazem com que a correo do erro seja mais barata por dois motivos: y O teste expe o erro assim que ele entra no sistema, o que evita muita perda de tempo com depuraes demoradas. y Caso um erro seja introduzido em uma parte do sistema diferente daquela que se est trabalhando no momento, os testes expem o local exato do erro, permitindo que este seja encontrado e corrigido muito mais rapidamente. - Aumento da qualidade e produtivi dade Sempre que um teste detecta uma falha rapidamente, evitam -se longas sesses de depurao que costumam tomar boa parte do tempo dos projetos. Com mais tempo disponvel e melhores oportunidades de aprendizado, os desenvolvedores codificam mais rapidamente e com maior qualidade, ou seja, aumenta -se a produtividade e reduz -se a incidncia de defeitos. [BECK, 2002] [FOWLER, 2004] [HUNT, 2004] [TELES, 2004]

http://www.infoblogs.com.br/view.action?contentId=203921

O que TDD?

Ciclo com passos de beb: y y y Escrever um teste que falha; Fazer um teste que passe Refatore.

Prioridade do Teste

y y y y

Conhecer design; Testar; Implementar; Boa notcia: presso, estresse e falta de tempo no deixam os testes para trs.

Consideraes finais sobre TDD:

y y y y y

Design evolui com o conhecimento adquirido com projeto; Desenvolvimento com passos de beb; Expressa a boa inteno do programador em relao aos testes; Servem de documentao. Sobre cdigo o que podemos dizer:

1. Nome dos teste definem o comportamento esperado; 2. Sem duplicao; 3. Alta cobertura; 4. Anti-cdigo intil; 5. Alta qualidade do cdigo. Refatorao so seguras com testes automatizados;

http://www.eduardodeboni.com/blog/?p=47
O TDD ou desenvolvimento dirigido por testes (do ingls: Teste-drive design) Beck 2003 ; Astels 2003 ) uma abordagem revolucionria de desenvolvimento que combina o test first development onde se escreve o teste antes de se escreve o cdigo apenas nece ssrio para atender aquele teste com a refatorao. Qual o objetivo principal do TDD? Uma viso que o objetivo do TDD de especificao e no de validao (Martin, Newkirk, and Kess 2003 ). Em outras palavras, uma forma de pensar no seu projeto antes de escrever o cdigo funcional. Uma outra viso que o TDD uma tcnica de programao. Como Ron Jeffries gosta de dizer, o objetivo do TDD escrever um cdigo limpo que funcione. Eu acho que existe mrito nos dois argumentos, apesar que me inclino para a viso da especificao, mas deixo para voc decidir. Conteudo 1. O que TDD? 2. TDD e o teste tradicional 3. TDD e documentao 4. Desenvolvimento de Banco de Dados Dirigido por Teste 5. Escalando o TDD via o Desenvolvimento Dirigido pelo Modelo gil (AMDD) 6. Por que o TDD? 7. Mitos e conceitos errados 8. Sumrio 9. Ferramentas 1. O que o TDD? Os passos para o TFD Test First Design so vistos no diagrama de atividade da UML na figura 1. O primeiro passo adicionar rapidamente um teste, basicamente uma parte do cdigo vai falhar. A seguir se roda os testes, em geral toda o conjunto de testes apesar de que para ser mais rpido voc pode decidir executar apenas um subconjunto, para garantir que o novo

teste realmente falha. Voc ento atualiza a parte funcional do cdigo para fazer ele passar nos novos testes. O quarto passo executar os testes novamente. Se eles falharem voc deve atualizar seu cdigo funcional e testar novamente. Uma vez os testes tendo passado o prximo passo comear novamente (voc pode precisar refatorar qualquer duplicidade do seu projeto, transformando o TFD em TDD).

Figura 1. Os passos do TFD. Eu gostaria de descrever o TDD por meio de uma frmula bem simples: TDD = Refatorao + TFD TDD muda completamente o desenvolvimento tradicional. Quando voc vai implementar uma nova funcionalidade, a primeira questo que se pergunta se o projeto existente o melhor projeto possvel que permita voc implementar esta funcionalidade. Se for, voc avana com a abordagem do TFD. Se no for, voc refatora localmente para mudar a poro do projeto afetada pela nova funcionalidade, permitindo voc adicion-la o mais facilmente possvel. Como um resultado voc estar sempre melhorando a qualidade do projeto, consequentemente fazendo ele mais fcil de ser trabalhado no futuro.

Ao contrrio de escrever um cdigo funcional em primeiro lugar e depois o cdigo de teste como um trabalho posterior, se que voc vai escrever os testes, voc deve escrever o cdigo dos testes antes do cdigo do funcional. Alm disso, voc vai fazer isso em passo muito pequenos um teste e um pouco do cdigo funcional correspondente por vez. Um programador que adotar a abordagem do TDD recusa escrever uma nova funo at que exista um cdigo que falhe porque esta funo no esta presente. Na verdade, eles se recusam a adicionar uma simples linha de cdigo at que exista uma teste para ela. Uma vez o teste est no lugar ento ele deve garantir que agora o conjunto de testes passam (o novo cdigo pode quebrar alguns dos testes existentes assim como o novo teste). Isso parece um princpio simples, mas quando voc esta iniciando na abordagem do TDD exige-se uma grande disciplina porque fcil escorregar e escrever o cdigo funcional sem escrever um novo teste. Uma das vantagens da programao em pares que o seu par pode ajud a se manter na -lo linha. Uma das hipteses assumidas pelo TDD que voc deve ter um ambiente de teste de unidades disponvel para voc. Os desenvolvedores geis de software geralmente usam uma ferramenta de cdigo aberto da famlia XUnit, como o JUnit ou VBUnit, apesar de que existem opes em ferramentas comerciais disponveis. Sem tais ferramentas o TDD virtualmente impossvel. A Figura 2 apresetna um diagrama de estados da UML sobre como as pessas trabalham, tipicamente, com as ferramentas xUnit. Este diagrama me foi sugerido por Keith Ray.

Figura 2. Testando com o Framework xUnit. Kent Beck, que popularizou o TDD na programao extrema (XP) (Beck 2000 ), define duas regras simples para o TDD (Beck 2003 ). Em primeiro lugar, voc deve escrever um novo cdigo de negcio somente quando um teste automatizado falhar. Em segundo lugar, deve eliminar qualquer duplicidade que encontrar. Beck explica como estas duas regras simples geram um comportamento complexo em grupo e individualmente: y y Voc projeta organicamente, com um cdigo executvel oferecendo repostas entre as decises. Voc escreve seus prprios teste porque voc no pode esperar 20 vezes por dia que algum faa isso para voc.

Seu ambiente de desenvolvimento deve oferecer uma resposta rpida para pequenas mudanas (isto , voc precisa um compilador rpido e um conjunto de testes de regresso) Seus projetos precisam ter componentes altamente coesivos, fracamente acoplados (por exemplo, seu projeto deve ser altamente normalizado) para fazer os testes mais simples (isto tambm faz com que a evoluo e a manuteno do seu sistema mais fcil tambm).

Para desenvolvedores a implicao que eles precisam aprender a escrever testes de unidade eficazes. A experincia de Beck que bons testes: y y y y y Executem rpido (eles tem um rpidos setup, execuo e finalizao) Executam isoladamente (deve-se ser capaz de reorden-los) Usam dados que fazem eles fceis de ler e entender Usam dados reais (por exemplo, cpias de dados da produo) quando so necessrios Representam um passo na direo de um gol maior.

2. TDD e o teste tradicional O TDD , principalmente, uma tcnica de projeto com um efeito colateral de garantir que o cdigo fonte completamente testado na unidade. Entretanto, existe mais teste a fazer do que isso. Voc precisa ainda considerar outras tcnicas que testes geis como o teste gil de aceitao e o teste investigativo. Muito deste teste pode ser tambm realizado cedo no seu projeto se voc escolher fazer assim (e voc deveria). Na verdade, os testes de aceitao do XP para uma [estria do usurio] so especificados pelos stakeholders do projeto seja antes ou em paralelo codificao, dando aos stakeholders a confiana que o sistema na realidade atende aos seus requisitos. Com o teste tradicional um teste de sucesso o que acha um ou mais defeitos. Acontece a mesma coisa com o TDD; quando um teste falha voc esta fazendo um progresso porque quando voc precisa resolver o problema. Mais importante, voc tem uma medida clara do sucesso quando o teste no falha mais. o TDD aumenta a sua confiana que o sistema verdadeiramente atende os requisitos definidos para ele, que o seu sistema funciona de verdade e portanto que voc pode prosseguir com confiana. Assim como com o teste tradicional, quanto maior o perfil de risco do sistema, mais completo seus testes deve ser. Com ambos os testes tradicionais e o TDD voc no est buscando a perfeio, ao contrrio, voc est testando a importncia do sistema. Parafraseando Agile Modeling (AM), deve-se testar com um propsito, e saber porque se est testando alguma soia e qual o nvel que ela deve ser testada. Uma consequncia interessante do TDD que voc consegue testar com 100% de cobertura cada linha de cdigo testada algo que o teste tradicional no garante (apesar de recomendar). Em geral, eu acho que bem razovel dizer que apesar que o TDD uma tcnica de especificao, um valioso efeito que resulta em um cdigo significativamente melhor testado que as tcnicas tradicionais.

Se vale a pena construir, vale a pena testar. Se no vale a pena testar, porque voc esta perdendo seu tempo com isso? 3. TDD e Documentao Gostem ou no, a maioria dos programadores no leem documentao escrit para um a sistema, ao contrrio, eles preferem trabalhar com o cdigo. E no h nada errado com isso. Quando tentam entender uma classe ou uma operao, a maioria dos programadores vo olhar, em primeiro lugar para a amostra de cdigo que as envolve. Testes de unidade bem escritos fazem exatamente isso oferecem uma especificao para o cdigo funcional e como resultado os testes de unidade passam a ser uma importante parte da sua documentao tcnica. A implicao que as expectativas da turma pr-documentao precisa refletir esta realidade. Analogamente, os testes de aceitao podem formar uma parte importante da documentao de requisitos. Isso faz muito sentido quando se para para pensar sobre isso. Seus testes de aceitao definem exatamente o que os seus stakeholders esperam do sistema, por isso eles especificam os requisitos crticos. Seu conjunto de testes de regresso, particularmente com a abordagem do test-first, efetivamente se tornam uma especificao detalhada executvel. Os testes podem ser uma documentao suficiente? Muito provavelmente no, mas eles formam uma parte importante dela. Por exemplo, voc est prestes a descobrir que ainda precisa da documentao do usurio, uma viso geral do sistema, documentao operacional e de suporte. Voc ainda pode at descobrir que precisa uma documentao resumida cobrindo todo o processo de negcio que o seu sistema suporta. Quando voc aborda a documentao com a mente aberta, Eu suspeito que voc vai concordar que estes dois tipos de teste cobrem a maioria das suas necessidades em documentao para desenvolvedores e stakeholders. Ainda mais eles so bons exemplos das prticas de gerencia gil como a pratica do ponto nico de informao e uma importante parte do esforo global para se manter o mais gil possvel apesar da documentao. 4. Desenvolvimento de Banco de Dados Dirigido por Testes Enquanto este texto estava sendo escrito uma importante pergunta est sendo feita na comunidade gil o TDD pode ser usado para desenvolvimento de Bancos de Dado Quando s? se olha o processo descrito na Figura 1 importante notar que nenhum dos passos especifica uma linguagem orientada a objetos como Java ou C#, apesar de que estes so os ambientes em que o TDD tipicamente aplicado. Por que voc no poderia escrever um teste antes de fazer uma mudana no seu esquema de banco de dados? Por que no se pode fazer uma mudana, executar os testes e refatorar seu esquema como requerido? Me parece que voc s precisa escolher trabalhar assim. Minha idia que os termos banco de dados TDD ou talvez TDDD (Test Driven Database Design), no vai funcionar to bem quanto a aplicao TDD. O primeiro desafio o suporte de ferramentas. Apesar de estarem disponveis ferramentas para teste de unidade como o DBUnit,

ainda existe uma tecnologia emergente enquanto este artigo esta sendo escrito. Alguns DBAs esto melhorando a qualidade dos testes que esto executando, mas ainda no vi ninguem adotando uma abordagem do TDD para o desenvolvimento de bancos de dados. Um desafio que as ferramentas de teste de unidade ainda no so bem aceitas na comunidade de banco de dados, apesar de que isso est mudando, assim a minha expectativa que nos prximos anos o TDD para banco de dados vai crescer. Em segundo lugar, o conceito de desenvolvimento evolucionrio novo para a maioria dos profissionais de banco de dadso porque uma mentalidade serial ainda domina na comunidade tradicional e a maioria das ferramentas no suporta o desenvolvimento evolutivo. Minha esperana que os fornecedores de ferramenta vo alcanar esta mudana de paradigma, mas minhas expectativas so que teremos que desenvolver ferramentas de cdigo aberto. Em terceiro lugar, minha experincia que a maioria das pessoas que trabalham orientadas a dados parecem preferir uma abordagem orientada a modelos e no orientada a testes. Uma causa disso parece que porque uma abordagem orientada a testes ainda no foi amplamente considerada at agora, outra razo pode ser que os profissionais de bancos de dados so mais visuais e por isso preferem a abordagem orientada a modelos. 5. Escalando o TDD via AMDD (Desenvolvimento gil Dirigido a Modelos) TDD muito bom como uma especificao detalhada e validao, mas no muito boa para pensar em problemas maiores como uma viso geral do projeto, como as pessoas vo usar o sistema, ou o projeto da interface (por exemplo). Modelagem, ou ou o AMDD (Desenvolvimento gil Dirigido a Modelos) (o ciclo de vida apresentado na figura 3) mais adaptada para isso. O AMDD atende o problema de escalar os problemas do TDD no resolve.

Figura 3. O ciclo de vida do AMDD (Desenvolvimento gil Dirigido a Modelos) Comparando o TDD e o AMDD: y y y O TDD abrevia a realimentao da programao enquanto o AMSS abrevia a realimentao da modelagem. O TDD prov uma especificao detalhada (testes) enquanto o AMDD melhor para pensar os grandes problemas. O TDD promove o desenvolvimento de software de alta qualidade enquanto o AMDD promove uma comunicao de alta qualidade com os stakeholders e outros desenvolvedores. O TDD fala com os programadores enquanto o AMDD fala com os analistas de negcio, stakeholders e profissionais de dados O TDD oferece uma realimentao concreta muito fina e da ordem de minutos enquanto o AMDD permite uma realimentao verbal da ordem de minutos (uma realimentao concreta requer que os desenvolvedores sigam a prtica de provar com o cdigo e ento se tornam dependentes de tcnicas no AM) O TDD orientada no-visual enquanto a AMDD orientada visualmente. Ambas as tcnicas so novas para os desenvolvedores tradicionais e por isso podem ser uma ameaa para eles. Ambas as tcnicas suportam o desenvolvimento evolutivo.

y y

y y y

Que abordagem eu demo adotar? A resposta depende das suas preferencias cognitivas e de seus colegas. Algumas pessoas so primeiramente um pensador visual, tambm chamado de pensadores espaciais. 6. Porque TDD? Uma vantagem significativa do TDD que ele permite que voc tome pequenos passos quando estiver escrevendo um software. Esta prtica que eu tenho promovido por ano porque s muito mais produtivo que tentar codificar em passos maiores. Por exemplo, assuma que voc adicionou um cdigo funcional, compilou e testou. A probabilidade boa que os testes iro ser quebrados por defeitos que existem no seu cdigo novo. muito mais fcil encontrar, e corrigir, estes defeitos se voc tiver escrito duas linhas de cdigo do que duzentas. A consequncia que quanto mais rpido o seu compilador e conjunto de teste, mais atrativo prosseguir em passos mais e mais pequenos. Eu geralmente prefiro adicionar poucas linhas de cdigo no cdigo funcional, tipicamente menos de dez, antes de recompilar e rodar os testes. Eu acho que Bob Martin confirma que o ato de escrever um teste de unidade mais um ato de projeto do que de verificao. Ele tambm mais um ato de documentao do que verificao. O ato de escrever um teste de unidade fecha um grande nmero de laos de realimentao, no mnimo aqueles que se procura na verificao de uma funo. A primeira reao que muitas pessoas tem das tcnicas geis que elas funcionam bem em projetos pequenos, talvez envolvendo um pequeno nmero de pessoas por poucos meses, mas que eles no vo funcionar em projetos reais que so muito maiores. Isto simplesmente no verdade. Beck (2003) relata trabalhar em um sistema Smalltalk completamente com a abordagem dirigida a testes que levou 4 anos, 40 pessoas-ano de esforo, resultando em 250000 linhas de cdigo funcional e 250000 linhas de cdigo de teste. Eram 4000 testes sendo executados em menos de 20 minutos, com o conjunto completo de testes sendo executada vrias vezes por dia. Apesar de que existem muitos sistemas grandes, eu pessoalmente trabalhei em sistemas com muitas centenas de pessoas-ano de esforo e est claro que o TDD funciona para sistemas de bom tamanho.

Figura 4. Viso geral de teste para as equipes geis. 8. Sumrio O Desenvolvimento Dirigido por Testes (TDD) uma tcnica de desenvolvimento onde voc deve escrever primeiro os testes que falham antes de voc escrever um novo cd funcional. igo O TDD est sendo adotado rapidamente pelos desenvolvedores de software para desenvolvimento uma aplicao de cdigo fonte e est sendo at adotada pelos DBAs geis para o desenvolvimento de bancos de dados. TDD deve ser visto como complement r a a abordagem do AMDD (Desenvolvimento gil Dirigido por Modelos) e os dois podem e devem ser usados em conjunto. O TDD no substitui TDD does not replace traditional testing, instead it defines a proven way to ensure effective unit testing. A side effect of TDD is that the resulting tests are working examples for invoking the code, thereby providing a working specification for the code. My experience is that TDD works incredibly well in practice and it is something that all software developers should consider adopting.

Figura 5. Como as equipes geis validam o seu trabalho. 9. Ferramentas y y y y y y y cppUnit csUnit (.Net) CUnit DUnit (Delphi) DBFit DBUnit HTMLUnit

y y y y y y y y y y y y y

HTTPUnit JMock JUnit NDbUnit NUnit OUnit PHPUnit PyUnit (Python) SimpleTest TestNG Test::Unit (Ruby) VBUnit XTUnit

10. Referncias E Sugestes De Leitura On -Line

y y y y y y y y y y y y y y y y y y

Agile Database Best Practices Agile/Evolutionary Data Modeling Agile Documentation: Strategies for Agile Software Development Agile Model Driven Development (AMDD) Agile Testing and Quality Strategies: Discipline Over Rhetoric AMDD and TDD Go Hand-In-Hand Behavior-Driven Database Development (Pramod Sadalage) Comparing the Various Approaches to Modeling in Software Development The Cultural Impedance Mismatch Between Data Professionals and Application Developers Database Regression Testing Ensuring Database Quality Executable Specifications: An Agile Best Practice Evolutionary Development: How Data Activities Fit In Prioritized Requirements: An Agile Best Practice The Process of Database Refactoring
Single Source Information: An Agile Practice for Effective Documentation

Survey Results (Agile and Data Management) Test Driven Database Development (TDDD): September 2006 issue of Quality Software and
Testing.

y y y y

Test Driven Development, A Portable Methodology Test Driven Development: A Practical Guide by Dave Astels Test Driven Development: By Example by Kent Beck Test Driven Development of Relational Databases (IEEE Software, May/June 2007)

http://dojofloripa.wordpress.com/2006/11/07/introducao-ao-desenvolvimento-orientado-atestes/

Introduo ao Desenvolvimento Orientado a Testes (TDD)


Posted by Ivan Sanchez em Tera-feira, Novembro 7, 2006 Test-Driven Development (TDD) ou Desenvolvimento Orientado a Testes uma maneira diferente de escrever software. A diferena entre esta abordagem e a que voc provavelmente est acostumado que com TDD voc vai evoluindo seu cdigo aos poucos, conforme vai explorando o problema com o uso de testes automatizados escritos antes da soluo sequer existir. Algumas vantagens desta abordagem so:

Incentiva a simplicidade: como a soluo vai surgindo pouco a pouco, a tendncia que no se perca tempo com aquilo que no tem certeza que ser usado em seguida. Expresses como You Aint Gonna Need It (YAGNI) e Keep It Simple, Stupid (KISS) so recorrentes quando se est programando orientado a testes. Aumenta a confiana no cdigo: o sistema funciona de uma determinada maneira porque existem testes que foram utilizados durante sua criao 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 no ir se repetir. Ajuda como documentao: os testes quando bem definidos so mais simples de ler que o cdigo e, embora nem sempre sirvam como uma especificao para o usurio final, eles so uma fonte eficiente para entender o que o software faz. Alm disso, esta documentao sempre estar atualizada com a aplicao. Facilita refactorings: quanto mais testes existem no sistema, maior a segurana para fazer refactorings. Um erro causado por algum refactoring dificilmente vai passar desapercebido quando um ou mais testes falharem aps a mudana.

Em TDD, um teste um pedao de software. A diferena entre teste e o cdigo que est sendo produzido que os testes tm 2 funes principais:

y y

De especificao, ou seja, definir uma regra que seu software deve obedecer De validao, ou seja, verificar que a regra obedecida pelo software

Geralmente os testes so criados com algum framework do tipo xUnit (jUnit, nUnit Test::Unit etc) , mas tambm podem ser feitos num nvel de funcionalidades (atravs de softwares como o FitNesse e Selenium) . Estas ferramentas servem basicamente para organizar os testes e facilitar na criao das verificaes.

O processo de criao de programao orientado a testes simples: 1. Escreva um teste que falhe. Pense no que o cdigo deve fazer, descreva o contexto e defina quais so as verificaes que precisam ser feitas. No h um limite no nmero de testes, ento quanto menos coisa cada teste descrever/verificar, melhor. No incio tambm no preciso se preocupar se a classe/mtodo ainda no existe. Pense primeiro no teste e s depois que este estiver pronto crie o esqueleto de cdigo necessrio para que ele compile e falhe ao rodar. Faa o teste passar. Agora chegou o ponto crucial: escreva o mnimo de cdigo para que o teste passe. Controle o instinto natural do programador de tentar prever tudo que o cdigo vai fazer e apenas faa o teste passar. Mesmo que tenha certeza que o cdigo deve fazer mais coisas, fazer os testes passarem deve ser a nica preocupao agora. Refatore. Uma vez que o teste passou, verifique o que no cdigo pode ser melhorado. Geralmente para um teste passar preciso inserir duplicao atravs de constantes (tcnica conhecida como Fake It). Agora a hora de melhorar o cdigo e remover as duplicaes, lembrando que os testes devem continuar passando.

2.

3.

Estes 3 passos so repetidos at que no se consiga pensar em novos testes, o que indica que a funcionalidade est pronta.

http://www.ebah.com.br/content/ABAAABNfUAG/conceitos-beneficios-tdd
Conceitos e Benefcios do Test Driven Development Eduardo N. Borges Instituto de Informtica Universidade Federal do Rio Grande do Sul (UFRGS) Caixa Postal 15.064 91.501-970 Porto Alegre RS Brazil enborges@inf.ufrgs.br Abstract. This paper describes the development style called Test Driven Development (TDD). The origin, definition of the method, and some related research that is being currently carried through are boarded. The benefits of this boarding in terms of the implementation total time, final code quality and maintainability are presented. Finally, test scripts are demonstrated through a detailed example of the TDD use. Resumo. Este artigo descreve o estilo de desenvolvimento de software orientado a teste chamado Test Driven Development (TDD). abordada a origem do mtodo, bem como sua definio e algumas pesquisas relacionadas que esto sendo realizadas atualmente. So apresentados os benefcios desta abordagem quanto ao tempo total de implementao, qualidade e manutenibilidade do cdigo final. Por fim, so demonstrados scripts de teste atravs de um exemplo detalhado do uso do TDD. 1. Introduo Atualmente, as falhas de software so grandes responsveis por custos e tempo no processo de desenvolvimento de software. Embora no seja possvel remover todos os erros existentes em certa aplicao, possvel reduzir consideravelmente o nmero dos mesmos utilizando uma infra-estrutura de testes mais elaborada, que permita identificar e remover defeitos mais cedo e de forma mais eficaz. Estes defeitos podem resultar de diversas causas como erros de conhecimento, comunicao, anlise, transcrio, codificao, etc. Existem essencialmente trs formas de tratar falhas de software: 1. Evitar falhas (fault-avoidance): com atividades apropriadas de especificao, projeto, implementao e manuteno sempre visando evitar falhas em primeiro lugar. Inclui o uso de mtodos de construo de software avanados, mtodos formais e reuso de blocos de software confiveis.

2. Eliminar falhas (fault-elimination): compensao analtica de erros cometidos durante a especificao, projeto e implementao. Inclui verificao, validao e teste. 3. Tolerar falhas (fault-tolerance): compensao em tempo real de problemas residuais como mudanas fora da especificao no ambiente operacional, erros de usurio, etc. Devido ao fato de fault-avoidance ser economicamente impraticvel para a maioria das empresas, a tcnica de eliminao de falhas geralmente a adotada pelos fabricantes de software. Uma pesquisa publicada pelo National Institute of Standards and Technology (NIST) e pelo Departamento de Comrcio dos Estados Unidos revela que os erros de software custam cerca de 60 bilhes de dlares economia norteamericana a cada ano [NIST 2002]. Portanto, faz-se necessrio o estudo de tcnicas orientadas a testes, pois este pode proporcionar uma economia considervel para empresas de desenvolvimento e aumentara qualidade do software produzido. Visando esta economia e aumento da qualidade, o Test driven development (TDD) foi criado para antecipar a identificao e correo de falhas durante o desenvolvimento do software. 2. Definio O TDD um estilo de desenvolvimento de software gil derivado do mtodo Extreme Programming (XP) [Beck 2000] e do Agile Manifesto [Agile Alliance 2000]. baseado tambm em tcnicas de desenvolvimento utilizadas h dcadas [Gelperin and Hetzel 1987] [Larman and Basili 2003]. A prtica envolve a implementao de um sistema comeando pelos casos de teste de um objeto. Escrevendo casos de teste e implementando estes objetos e mtodos, surge a necessidade de outros mtodos e objetos. No TDD, desenvolvedores usam testes para guiar o projeto do sistema durante o desenvolvimento. Eles automatizam estes testes para que sejam executados repetidamente. Atravs dos resultados (falhas ou sucessos) julgam o progresso do desenvolvimento. Os programadores fazem continuamente pequenas decises aument ndo as funcionalidades do a software a uma taxa relativamente constante. Todos estes casos de teste devem ser realizados com sucesso sucessivamente antes de o novo cdigo ser considerado totalmente implementado. O TDD pode ser visto como um conjunto de iteraes realizadas para completar uma tarefa [Beck 2002]. Cada iterao envolve os seguintes passos, os quais so destacados na Figura 1. Escolher a rea do projeto ou requisitos da tarefa para melhor orientar o desenvolvimento. Projetar um teste concreto to simples quanto possvel quando requerida a orientao do desenvolvimento. Checar se o teste falha. Alterar o sistema para satisfazer este teste e outros possveis testes. Possivelmente refatorar o sistema para remover redundncia, sem quebrar nenhum teste. Uma importante regra no TDD : If you cant write a test for what you are about to code, then you shouldnt even be thinking about coding [Chaplin 2001]. Outra regra no TDD diz que quando um defeito de software encontrado, casos de teste de unidade so adicionados ao pacote de teste antes de corrigir o cdigo.

       )  8         ) &     )              )  % %       %     8                 A 8       )  %      %        %    5          8      8
i i E l t l t l i . l i , l i t i t i l t i it i i i t i i t l t l i i ti t fi t i f i l , l i .P t t , t i tfi , t t i t i t i i , t . Al i lf t t l f i i i l t i i ifi , t i t t i t lt t t i , t t t t i l i i t i t i i t i i i t t fi t i i , lt t i . i i , . , t l j l t t t t i j ti j fi i l ifi t t if il. . Al it t t i l i i ifi t , t it i . t i

          % )   8  8  @ %9         &    $        8 %  3 )            5   5  8    % )  5                  8   ) )   % %   8             ! (44         3 1         "  )      $  )    )      )      %     )      % %               %                  6  5           %     '7            % % %  )            %               %        )  % %        6     5           5   ! 3443 2   1
[ A A t [ ll .P j i ft i i i l ifi i t t l i l i .O t . i t t i l .A t t t , l . ]f i i ti l i l t i j t t , t i .E it l i li l l i ] l. E t t fi i l ti t i tfi f l t i t t i t .B t .O t i tfi .Pi i t fi i t i t .E t , fi i i t t i .P i . i t i i t t t t t l i l t t l t t i .A i , f t , ti . li t t . l t

  0 ) (  % % '         ! %  $  " & #     !                          
E t i ft .P . l i i ,t t . t . j t i t t ti t t ifi ,

A l

Figura 2. Passos do mtodo cientfico. Outro experimento realizado por [George and Williams 2004] mede a qualidade do software atravs do nmero de casos de teste caixa preta realizados com sucesso em ambas as abordagens de desenvolvimento. Todos os participantes implementam em duplas (pair programming). So alocados de forma que cada dupla contenha um programador experiente na abordagem de desenvolvimento e outro iniciante. Apesar do tamanho do cdigo desenvolvido ser relativamente pequeno (cerca de 200 linhas), e cada grupo ser formado por 6 duplas, os resultados foram bastante expressivos. A prtica TDD forneceu cdigo com qualidade superior quando comparada prtica de desenvolvimento tradicional. Alm disso, programadores que praticam TDD codificaram mais rpido (16%) do que desenvolvedores que no a utilizaram. Este tempo foi medido em horas de trabalho considerando todo o processo de desenvolvimento. O grupo de desenvolvimento da IBM responsvel pela construo de drivers para vrios dispositivos implantou o TDD como padro no desenvolvimento de releases para uma nova plataforma [Williams et al 2003]. Foram criados 2400 casos de teste automatizados aps a concluso dos diagramas de classes e seq ncia UML. O grupo realizou cerca de 40% de reduo de tempo na verificao de defeitos em funes e mtodos quando comparado ao grupo de desenvolvimento de drivers para plataformas j suportadas, o qual construa a 7 release do driver. Isto comprovou o grande impacto de produtividade que o TDD proporciona. 4. Benefcios TDD utiliza uma das tcnicas do XP chamada refactoring [Beck 2000] para conseguir a compreenso do cdigo e gerenciar a complexidade do mesmo. Como um grande programa ou sistema continuamente modificado, ele torna-se muito complexo, sendo extremamente necessria a facilidade de manuteno [Lehman and Belady 1985]. Esta tcnica essencial para reduzir a complexidade do software e torn-lo manutenvel. A pequena granularidade do ciclo test-then-code d um feedback contnuo ao programador. Falhas so identificadas mais rapidamente, enquanto o novo cdigo adicionado ao sistema. Assim, o tempo de depurao diminui compensado pelo tempo de escrita e execuo dos casos de teste. Alguns estudos indicam que cerca de 50% das tarefas no processo de manuteno de software so envolvidas no processo de entendimento do cdigo [Corbi 1989]. A abordagem TDD ajuda na compreenso do programa porque os casos de teste e prprio cdigo explicam melhor o funcionamento do sistema. Entretanto, esta prtica permite somente o entendimento de uma parte do software. Para a compreenso da sua totalidade, necessrio que se faa uso de vrias abstraes. Os resultados de mtodos ou funes so testados automaticamente. Estes valores so comparados aos resultados esperados ainda na etapa de codificao. J na etapa de manuteno, as unidades de teste criadas anteriormente permitem avaliar mais facilmente novos defeitos que podem ter sido inseridos no software. Este benefcio essencial para o desenvolvimento e controle de novas releases, reduzindo a injeo de novas falhas no sistema. 5. Scripts de Teste TDD geralmente faz uso de ferramentas e de um framework para a criao de unidades de teste orientadas a objetos. Um exemplo de framework o JUnit (http://w.junit.org), onde unidades de caso de teste so adicionadas uma por classe pblica, usualmente com o nome <ClassName>TestCase. Para cada mtodo pblico de uma classe existe pelo menos um teste test<method>. Mltiplos testes podem ser adicionados quando checam diferentes comportamentos do mtodo. Antes da execuo de cada teste, um mtodo setUp executado na classe <ClassName>TestCase

para inicializar instncias da <ClassName> as quais sero utilizadas na execuo dos testes. Classes TestCase so usualmente agrupadas logicamente em classes TestSuite, as quais permitem a execuo das classes TestCase em grupos. O exemplo abaixo [Teles 2005] trata do algoritmo Crivo de Eratstenes [Wikipdia 2006], o qual gera uma lista de nmeros primos a partir de um nmero N que passado como parmetro de entrada. utilizado o Framework JUnit e a IDE Java Eclipse. Primeiramente criada uma classe de teste geral com o teste mais simples possvel que se possa imaginar. Espera-se que a classe gere uma string, com uma lista de nmeros primos, separados por vrgula, menores ou iguais ao valor passado como argumento. Por exemplo, a busca por nmeros primos at 10 retorna como resultado a string "2, 3, 5, 7". import junit.framework.TestCase; public class GeradorPrimosTeste extends TestCase { public void testePrimosGeradosAteNumeroDois() throws Exception { GeradorPrimos geradorPrimos = new GeradorPrimos(); assertEquals("2", geradorPrimos.gerarPrimosAte(2)); } O mtodo assertEquals("2", geradorPrimos.gerarPrimosAte(2)) o teste preliminar que checa se possvel gerar nmeros primos corretamente at o nmero 2. O cdigo ainda no compilvel pois no existe a classe GeradorPrimos. Isso comum quando utilizada a abordagem TDD, pois escrever o cdigo conseq ncia do sucesso ou falha dos testes. Ento, necessria a criao desta classe. public class GeradorPrimos { public String gerarPrimosAte(int i) { return null; } Isto j suficiente para a compilao. Somente aps a execuo do teste que saberemos o que necessrio implementar. Executado o teste, verificamos que ele falha. Isto era de se esperar, pois o mtodo gerarPrimosAte() ainda no resolve o problema. No TDD importante ter certeza de que o teste falhar em situao que realmente deve falhar. Em seguida, feita uma implementao simples do mtodo gerarPrimosAte(). public String gerarPrimosAte(int i) { return "2"; } Executando o teste novamente percebe-se que tudo ocorre normalmente. Sendo assim, o programador tem a segurana de que o teste falha quando tem absoluta certeza de que deveria falhar, e que passa quando tem total confiana de que deveria passar. Isto ocorr e atravs de solues obviamente simples e depois, com a segurana de que o teste est correto, implementa-se o funcionamento complexo da classe. Esta tcnica de incrementar solues atravs de pequenos passos conhecida como baby steps e usada frequent emente no XP. Ento, cria-se novos casos de teste que geram a necessidade da implementao da classe GeradorPrimos. O cdigo funciona para nmeros at 3? public void testePrimosGeradosAteNumeroTres() throws Exception { GeradorPrimos geradorPrimos = new GeradorPrimos(); assertEquals("2, 3", geradorPrimos.gerarPrimosAte(3)); } Executando o teste, naturalmente percebe-se que ele falha. Ento so feitas modificaes no mtodo para que o teste passe. public String gerarPrimosAte(int i) { if (i == 2) return "2"; else return "2, 3"; Essa soluo funciona para o argumento 3, mas nota-se que gera duplicao inc moda no cdigo, onde os mtodos testePrimosGeradosAteNumeroDois(), e

testePrimosGeradosAteNumeroTres() possuem implementao quase idntica. Alm disso, a varivel i no expressa bem sua inteno. Um princpio bsico no TDD chamado de Don't Repeat Yourself (DRY). Este princpio trata da importncia de eliminar duplicaes para tornar o cdigo mais claro, coeso e manutenvel. Para isto, utiliza-se a tcnica de refatorao do XP (refactoring). Ento, um mtodo extrado desta duplicao. public void testePrimosGeradosAteNumeroDois() throws Exception { verificaPrimosGerados("2", 2); } public void testePrimosGeradosAteNumeroTres() throws Exception { verificaPrimosGerados("2, 3", 3); } private void verificaPrimosGerados(String listaEsperada, int numeroMaximo) throws Exception { GeradorPrimos geradorPrimos = new GeradorPrimos(); assertEquals(listaEsperada, geradorPrimos.gerarPrimosAte(numeroMaximo)); } public class GeradorPrimos { public static final int MENOR_PRIMO = 2; public String gerarPrimosAte(int valorMaximo) { if (valorMaximo == MENOR_PRIMO) return "2"; else return "2, 3"; } Ainda necessrio criar testes de excees para nmeros menores o MENOR_PRIMO. Utilizando a tcnica de refatorao para adicionar estes testes nota-se a necessidade de testes para nmeros negativos. A refatorao normalmente demanda um pequeno investimento inicial, porm gera economia de tempo futura, mantendo o cdigo organizado. comum ocorrer situaes em que so extrados mtodos que so utilizados inmeras vezes em uma mesma classe de teste.

Ento, cria-se novos casos de teste para os argumentos 4, 5,que geram a


necessidade da implementao real do algoritmo Crivo de Eratstenes. preciso uma lista representando possveis candidatos de nmeros primos. Um vetor do tipo boolean, no qual true indica que o nmero primo resolve o problema. Mtodos de inicializao, verificao e atribuio precisam ser criados, sempre seguindo a abordagem TDD, onde os testes so gerados primeiramente e o cdigos das classes e mtodos so implementados consequentemente. Abaixo, uma possvel soluo para problema proposto (foram omitidas algumas etapas de criao de casos de teste e refatorao): public String gerarPrimosAte(int valorMaximo) throws ValorMaximoInvalidoException { if (valorMaximo >= MENOR_PRIMO) { return numerosPrimos(valorMaximo); } else { throw new ValorMaximoInvalidoException(); } private String numerosPrimos(int valorMaximo) { boolean [] ehPrimo = inicializaListaCandidatos(valorMaximo); for (int valor = MENOR_PRIMO; valor <= valorMaximo; valor++) { if (ehPrimo[valor]) { for (int naoPrimos = MENOR_PRIMO * valor; naoPrimos <= valorMaximo; naoPrimos += valor) { ehPrimo[naoPrimos] = false; } } return apresentaResultado(valorMaximo, ehPrimo); } private String apresentaResultado(int valorMaximo, boolean[] ehPrimo) { String resultado = String.valueOf(MENOR_PRIMO); for (int i = MENOR_PRIMO + 1; i <= valorMaximo; i++) { if (ehPrimo[i]) {resultado += ", " + i;} } return resultado; } boolean[] inicializaListaDePrimosPotenciais(int valorMaximo) { boolean [] resultado = new boolean[valorMaximo + 1]; resultado[0] = resultado [1] = false; for (int i = MENOR_PRIMO; i < resultado.length; i++) { resultado[i] = true; } return resultado; } 6. Concluses Unido s tcnicas como pair-programming, refactoring e outras, o TDD responsvel pelo sucesso dos projetos que utilizam XP. Diversas empresas j adotaram o TDD como padro no desenvolvimento de software, pois aumentaram o nvel de compreenso do cdigo gerado economizando tempo de manuteno. Com o uso desta tcnica possvel reduzir a complexidade do software, aumentando a manutenibilidade do mesmo. Falhas so facilmente identificadas ainda na etapa de

desenvolvimento graas ao contnuo feedback dado ao programador. As unidades de teste criadas permitem avaliar mais facilmente novos defeitos que podem ter sido inseridos no software facilitando o desenvolvimento de sucessivas releases. Pesquisadores tm notado que tcnicas e notaes de desenvolvimento de software tm sido integradas ao processo de implementao [Perry, D.E. and Wolf 1992]. Tal integrao tende a confundir projeto e implementao. A prtica TDD tambm integra as diferentes fases do desenvolvimento: projeto, implementao e teste. dada maior nfase em como os elementos necessitam ser implementados e menor nfase nas estruturas lgicas. Portanto, se adotado fielmente, o TDD pode resultar na falta do esboo do sistema. Decises importantes de projeto podem ser perdidas devido falta da documentao formal do projeto. Referncias Agile Alliance, The Manifesto for Agile Software Development, vol. 2003: Agile Alliance, 2000. Beck, K. eXtreme Programming Explained, Addison Wesley, 2000. Beck, K. Test Driven Development: By Example, Addison Wesley, 2002. Chaplin, D. Test first programming, TechZone, 2001. Corbi, T.A. Program understanding challenge for the 1990s, IBM Systems Journal 28 (1989) 294306. Gelperin, D. and Hetzel, W. Software quality engineering, Fourth International Conference on Software Testing, Washington, DC, June 1987. George, B. and Williams, L.A. A structured experiment of test-driven development. Information & Software Technology 46 (2004) 337342 Larman, C. and Basili, V. A history of iterative and incremental development, IEEE Computer 36 (2003) 4756. Lehman, M. and Belady L. Program Evolution: Processes of Software Change, Academic Press, London, 1985. Mugridge, R. Test driven development and the scientific method. Agile Development Conference, 2003. ADC 2003. 47- 52. M ller, M.M. and Hagner, O. Experiment about test-first programming, Empirical Assessment In Software Engineering EASE02, Keele, April 2002 National Institute of Standards and Technology (2002) Software Errors Cost U.S. Economy $59.5 Billion Annually, http://w.nist.gov/public_affairs/releases/n02- 10.htm, June 28. Perry, D.E. and Wolf, A.L. Foundations for the study of software architecture, ACM SIGSOFT 17 (1992) 4052. Teles, V. M. (2005) Desenvolvimento Orientado a Testes, disponvel em http://w.improveit.com.br/xp/tdd.jsp. Williams, L.; Maximilien, M.; Vouk, M. Test-driven development as a defectreduction practice. IEEE International Symposium on Software Reliability Engineering, Denver, CO, 2003. Wikipdia (2006) Eratstenes, http://pt.wikipedia.org/wiki/Erat%C3%B3stenes.

TEST-DRIVEN DEVELOPMENT NO RAILS: UNIT TESTS


Todo mundo fala que Test -Driven Development aumenta sua produtividade, reduz a quantidade de erros do seu cdigo e deixa todo mundo mais feliz. O quem ningum fala como fazer isso, quando voc no conhece nada de testes. Por isso, resolvi escrever este texto, mostrando o pouco que aprendi nas ltimas semanas sobre esse tema. Test-Driven Development (TDD) Desenvolvimento Orientado a Testes ou Desenvolvimento Guiado por Testes uma tcnica de desenvolvimento de software onde primeiro so criados os testes e somente depois escrito o cdigo necessrio para passar por eles. Dessa maneira, voc escrever cdigos melhores e, o que mais importante, muito mais rapidamente. Veja como o ciclo de TDD, segundo o livro Test-Driven Development by Example , de Kent Back (ISBN-0321146530): Crie um teste: Cada nova funcionalidade deve comear com um teste escrito. Este teste deve falhar antes da funcionalidade ser implementada. Voc deve conhecer claramente os requisitos e especificaes da funcionalidade. Execute todos os testes: Voc saber que a rotina de testes est funcionando corretamente e que o novo teste no passou sem que o teste da funcionalidade tenha sido implementado. Escreva o cdigo: Escreva o cdigo que ir passar naquele teste que voc criou na etapa anterior, sem se preocupar em torn -lo elegante/otimizado. muito importante que o cdigo implementado reflita somente o teste escrito. Execute novamente todos os teste: Se todos os testes passarem, voc ter certeza que o cdigo atende todos os requisitos testados e que esta nova funcionalidade no afetou outras parte s do sistema. Refatore o cdigo: Agora voc pode "limpar" o cdigo, se for necessrio. Lembre-se de executar os testes constantemente durante esta etapa, pois s assim voc saber se o sistema no foi modificado de maneira incorreta, gerando erros. Os testes, quando devidamente implementados, oferecem uma certa "garantia" de que a aplicao est funcionando da maneira como deveria.

1. 2. 3. 4. 5.

TDD no Rails
Este texto no tem a pretenso de ser o "guia definitivo" de TDD; ao invs disso, voc ver uma abordagem simples e direta do assunto, utilizando Ruby on Rails. No irei explicar detalhadamente como desenvolver em Rails; para isso voc tem outras fontes um tanto quanto completas.

O que um teste? Teste um mtodo que contm asseres segundo o dicionrio Houaiss, assero significa "afirmao categrica" e que representam um cenrio de testes em particular. Um teste s passar caso todas as asseres sejam verdadeiras. No Ruby, um teste um mtodo iniciado por "test"; assim, voc pode nomear seu mtodo como "test_", "testing_", "testando_", e por a vai! O Rails trabalha com alguns tipos diferentes de testes. Existem os testes unitrios que so responsveis pelos testes de modelos; existem os testes funcionais, responsveis por testar os controllers; e, por ltimo, temos os testes de integrao, responsveis por testar mltiplas camadas de seu aplicativo e a integrao entre elas. O teste unitrio ser, provavelmente, o primeiro lugar onde voc ir trabalhar em qualquer projeto. Isso acontece porque no preciso escrever muito cdigo vou alm e digo que no preciso escrever nenhumcdigo para se criar tais testes, a no ser o prprio teste. Quando estamos fazendo TDD, importante que todos os seus testes iniciais no passem na validao, pois voc precisa identificar os itens a serem validados para depois corrigi -los. Voc deve tambm criar pelo menos um teste que passe na validao.

Nosso exemplo
Ns vamos criar um sistema de blog muito mais poderoso que o WordPress :P totalmente feito em Rails. Ento, a primeira coisa que temos que fazer pensar nos requisitos de nosso projeto. Isso importante, pois permite ter uma viso melhor do que precisa ser feito. Obviamente, podemos ajustar tais requisitos ao longo do tempo. A princpio, nosso blog deve : permitir configuraes sobre o autor (nome, email, etc) criar posts com resumo permitir que usurios postem comentrios, informando email, nome e website Completo, no? :) Para comear, vamos criar nossa aplicao. Digite o comando rails blog . Nosso projeto ser criado e a lista dos arquivos ser exibida. Iremos, ento, criar nosso banco de dados MySQL, neste exemplo tanto de desenvolvimento quanto de testes. Se voc no se sente confortvel com a linha de comandos, faa da maneira como est acostumad o. ~$ mysqladmin -u root create blog_development ~$ mysqladmin -u root create blog_test Abra o arquivo "config/database.yml" e insira o usurio e senha que tero acesso aos bancos de dados. Meu arquivo se parece com isso: development: adapter: mysql database: blog_development username: root password: socket: /var/run/mysqld/mysqld.sock

y y y

test: adapter: mysql database: blog_test username: root password: socket: /var/run/mysqld/mysqld.sock production: adapter: mysql database: blog_production username: root password: socket: /var/run/mysqld/mysqld.sock

muito importante que voc defina 2 bancos diferentes para desenvolvimento e testes, uma vez que o banco de dados "testes" apagado quando estamos testando nossa aplicao. Quando nosso desenvolvimento ori entado a testes, voc inicialmente s cria os modelos e, logo depois, parte para os testes. Controllers? No, agora. Voc s ir cri-los muito mais frente. Vamos trabalhar inicialmente no modelo "usurio". ~/blog$ script/generate model User exists app/ models/ exists test/unit/ exists test/fixtures/ create app/models/user.rb create test/unit/user_test.rb create test/fixtures/users.yml create db/migrate create db/migrate/001_create_users.rb O Rails nos permite trabalhar com DDLs muito facilmente atravs das migrations. Ento, neste texto no iremos lidar com SQL diretamente, mas Ruby. Abra o arquivo "db/migrate/001_create_users.rb". Nossa tabela de usurios ter os campos "name", "email" e "password". Sua migrao dever ser algo como: class CreateUsers < ActiveRecord::Migration defself.up create_table :users do |t| t.column :name, :string, :null =>false t.column :email, :string, :null =>false t.column :password, : string, :null =>false end end
defself.down drop_table :users end end

Execute o comando rake db:migrate para criar a tabela "users". ~/blog$ rake db:migrate (in /home/ nando/blog) == CreateUsers: migrating ===================================================== -- create_table(:users) -> 0.0035s

== CreateUsers: migrated (0.0037s) ============================================

Com a tabela criada, podemos meter a mo na ma ssa! Abra o arquivo "test/unit/user_test.rb", que foi gerado automaticamente quando criamos nosso modelo. Uma das vantagens de se desenvolver em Rails justamente esta; to simples de se criar testes para uma aplicao, com arquivos criados automaticame nte, que voc deve se sentir envergonhado de no faz-lo. Este arquivo possui uma nica assero chamada test_truth . Apesar de parecer intil, ela ajuda a corrigir algumas configuraes do ambiente, como quando o banco de dados de teste no existe, por exe mplo. require File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase fixtures :users # Replace this with your real tests. def test_truth asserttrue end end

Para rodarmos nossos testes unitrios, devemos executar o comando rake test:units . O Ruby ir executar os testes unitrios e receberemos uma resposta como esta: Started . Finished in 0.03095 seconds.
1 tests, 1 assertions, 0 failures, 0 errors

y y y

Esta resposta bastante direta e fcil de entender. Cada ponto exibido na tela (logo abaixo da linha "Started") representa um teste que passou. Temos tambm uma linha que nos diz que foi executado 1 teste, com 1 assero, mas que no retornou erro ou falha. O teste que vem por padro no faz muita coisa, ento vamos criar o nosso! Nosso primeiro modelo a ser testado o User . Alguns testes possveis so: nome, email e senha so obrigatrios a senha deve ter no mnimo 6 caracteres o e-mail nico Podemos escrever um teste genrico para ver se o usurio criado quando no passamos nenhuma informao. def test_should_be_invalid user = User. create assert !user. valid?, "User shouldn't be created" end Primeiro, ns criamos um usurio ( User.create ) sem passar nenhuma informao. Se nosso modelo tivesse uma validao utilizando os mtodos disponveis do ActiveRecord, o mtodo user.valid? retornaria false e nossa aplicao passaria nos testes. Rodando os testes temos uma surpresa: ~/blog$ rake test:u nits Started F Finished in 0.050156 seconds.
1) Failure:

test_should_be_invalid(UserTest) [./test/unit/user_test.rb:8]: User shouldn't be created. <false> is not true. 1 tests, 1 assertions, 1 failures, 0 errors rake aborted!

Alguma coisa no est func ionando direito! Nosso teste deveria receber false do mtodo valid?,o que no aconteceu. No se preocupe em fazer o teste passar. Lembre-se que antes devemos criar os outros testes. Vamos, ento, criar cada um dos testes em separado. No sei se voc notou, mas ficou complicado entender a condio assert !user.valid? no teste que criamos. Para estes casos, podemos utilizar helpers, semelhantes ao que utilizamos nas views, mas que aqui so especficos para os testes. Abra o arquivo "tests/test_helper.rb" e ad icione os mtodos abaixo: def deny(condition, message='') assert !condition, message end
def assert_invalid (record, message='') deny record. valid?, message end O mtodo deny faz a negativa de assert e o mtodo assert_invalid apenas d uma fora, evitando que tenhamos que explicitar o .valid? toda vez. No se preocupe em verificar se o mtodo valid? existe ou no; nos testes,

assumimos um ambiente e ele deve ser verdadeiro e, caso no seja, investigamos as causas do erro que fo i apontado para ento corrig -lo. Troque o mtodo test_should_be_invalid que criamos anteriormente por este que utiliza nossos helpers. def test_should_be_invalid user = User. create assert_invalid user, "User shouldn't be created" end Muito melhor, certo? E assim, voc vive sem a culpa de ir contra o princpio DRY Agora, temos que adicionar outros testes. Antes disso, j prevendo mais um pouco de repetio, vamos criar um mtodo chamado create para nos ajudar. assim que sua classe de testes deve estar neste momento. require File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase fixtures :users def test_should_be_invalid user = create (:name =>nil, :email => nil, :password => nil) assert_invalid user, "User shouldn't be created" end private def create(options={}) User.create({ :name =>"Homer Simpson" , :email => "homer@simpsons.com" , :password => "test" }.merge(options)) end end

O mtodo create ser responsvel por definir os valores padro para os campos. Assim, no teremos que digit -los toda vez que quisermos adicionar um teste. Os outros testes que iremos criar iro verificar as condies impostas l em cima. Vamos comear pelo teste que verifica se o nome foi informado. def test_should_require_name user = create (:name =>nil) assert user. errors.invalid?(:name), ":name should be required" assert_invalid user, "User shouldn't be created" end No mudou muita coisa do primeiro teste que fizemos. Apenas adicionamos mais uma assero que verifica se o campo "name" invlido. No ActiveRecord, temos os mtodos validates_* que necessitam do nome do campo; toda vez que uma validao no passa, um erro adicionado ao campo. Alm de verificar se nosso campo possui um erro, poderamos verificar se uma mensagem tambm foi definida. A seguinte assero faz justamente isso. assert_not_nil user. errors.on(:name), ":name should have had a error message" E os outros testes: require File.dirname(__FILE__) + '/../test_helper'
class UserTest < Test::Unit::TestCase fixtures :users def test_should_be_invalid user = create (:name =>nil, :email => nil, :password => nil) assert_invalid user, "User shouldn't be created" end def test_should_require_name user = create (:name =>nil) assert user. errors.invalid?(:name), ":name should be required" assert_invalid user, "User shouldn't be created" end def test_should_require_email user = create (:email => nil) assert user. errors.invalid?(:email), ":email should be required" assert_invalid user, "User shouldn't be created" end def test_should_deny_bad_email user = create (:email => 'bad@ format') assert user. errors.invalid?(:email), ":email should be in a valid format" assert_invalid user, "User shouldn't be created" end def test_should_require_password user = creat e(:password => nil) assert user. errors.invalid?(:password ), ":password should be required" assert_invalid user, "User shouldn't be created" end def test_should_require_longer_password user = create (:password => 't' )

assert user. errors.invalid?(:password ), ":password should be 4 characters or longer" assert_invalid user, "User shouldn't be created" end def test_should_deny_duplicate_user user = create assert_valid user user = create assert_invalid user, "User shouldn't be created" end private def create(options={}) User.create({ :name =>"Homer Simpson" , :email => "homer@simpsons.com" , :password => "test" }.merge(options)) end end

Execute os testes e veja que uma longa lista de erros ir aparecer. ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/r ake_test_loader Started F.FFFFFF Finished in 0.073839 seconds.
1) Failure: test_should_be_invalid(UserTest) [./test/unit/../test_helper.rb:29:in `deny' ./test/unit/../test_helper.rb:33:in `assert_invalid' ./test/unit/user_test.rb:8:in `test_ should_be_invalid']: User shouldn't be created. <false> is not true. 2) Failure: test_should_deny_bad_email(UserTest) [./test/unit/user_test.rb:25]: :email should be in a valid format. <false> is not true. 3) Failure: test_should_deny_duplicate_user (UserTest) [./test/unit/../test_helper.rb:29:in `deny' ./test/unit/../test_helper.rb:33:in `assert_invalid' ./test/unit/user_test.rb:46:in `test_should_deny_duplicate_user']: User shouldn't be created. <false> is not true. 4) Failure: test_should_require_email(UserTest) [./test/unit/user_test.rb:19]: :email should be required. <false> is not true. 5) Failure: test_should_require_longer_password(UserTest) [./test/unit/user_test.rb:37]: :password should be 4 characters or longer.

<false> is no t true. 6) Failure: test_should_require_name(UserTest) [./test/unit/user_test.rb:13]: :name should be required. <false> is not true. 7) Failure: test_should_require_password(UserTest) [./test/unit/user_test.rb:31]: :password should be required. <false> is not true. 8 tests, 9 assertions, 7 failures, 0 errors

Foram executados 8 testes, com 9 asseres, sendo que 7 falharam. O nico teste que passou foi test_should_create_user , como era de se esperar. O que temos que fazer agora? Criar o cdigo que ir pas sar nestes testes. No caso dos testes unitrios isso bastante simples. Voc trabalha basicamente com modelos, ento, abra o arquivo "app/models/user.rb". Voc no precisa resolver os testes que falharam na ordem em que foram exibidos. Comece pelo que voc julgar ser mais simples e com menor dependncia. Que tal comearmos pela falha 4: :email should be required . Esta falha bastante simples de se resolver, bastando que voc coloque o mtodo validates_presence_of no modelo. Por equivalncia, tambm podemo s resolver as falhas 6 e 7. class User < ActiveRecord::Base validates_presence_of :email validates_presence_of :name validates_presence_of :password end Execute os testes Agora voc ver que 12 asseres foram executadas mas que apenas 3 falharam. Muito ma is interessante que o nosso teste anterior! ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started ..FF.F.. Finished in 0.065069 seconds.
1) Failure: test_should_deny_bad_email(UserTest) [./test/unit/user_test.rb:25]: :email should be in a valid format. <false> is not true. 2) Failure: test_should_deny_duplicate_user(UserTest) [./test/unit/../test_helper.rb:29:in `deny' ./test/unit/../test_helper.rb:33:in `assert_invalid' ./test/unit/user_test.rb:46:in `test_should_deny_duplicate_user']: User shouldn't be created. <false> is not true. 3) Failure: test_should_require_longer_password(UserTest) [./test/unit/user_test.rb:37]: :password should be 4 characters or longer. <false> is not true. 8 tests, 12 asse rtions, 3 failures, 0 errors

Vamos validar o atributo password : ele no deve ter menos que 6 caracteres. Basta adicionar o validador abaixo ao seu modelo. validates_length_of :password, :minimum => 4 Mais uma vez, execute os testes. Apenas 2 testes falharam: test_should_deny_bad_email etest_should_deny_duplicate_user . Para, finalmente, passar por todos os testes, adicione os mtodos abaixo. validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => /^ ([^@\s]+)@((?:[-a-z0-9]+\.)+[az]{2,})$/i Ao executar os testes, teremos uma resposta muito mais agradvel! ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started ........ Finished in 0.082506 seconds.
8 tests, 14 assertions, 0 failures, 0 errors

y y y

Sim! Todos os nossos testes passaram e no sei se voc percebeu mas o esforo foi praticamente nulo. Agora, seguindo nossos requisitos, iremos implementar os posts. No modelo Post, devemos escrever testes para va lidar os seguintes itens: um autor pode ter inmeros posts os comentrios podem ser permitidos ou no o resumo opcional, mas se for informado no deve ultrapassar 250 caracteres Como ainda no temos o modelo Post, vamos cri-lo: script/generate model Pos t Abra o arquivo de migrao 002_create_posts.rb e adicione o cdigo abaixo. class CreatePosts < ActiveRecord::Migration defself.up create_table :posts do |t| t.column :title, :string, :limit => 250, :null =>false t.column :excerpt, : string, :limit => 250, :null =>true t.column :body, :text, : null =>false t.column :created_at, :datetime t.column :updated_at, :datetime t.column :allow_comments, :boolean, :default => true, :null =>false t.column :user_id, : integer, :null =>false end end
defself.down drop_table :posts end end

O cdigo acima dispensa maiores explicaes. Execute o comando rake db:migrate para criarmos a tabela de posts. ~/blog$ script/generate model Post exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/post.rb create test/unit/post_test.rb create test/fixtures/posts.yml exists db/migrate create db/migrate/002_create_posts.rb J podemos criar os testes necessrios para validar o modelo de posts. Como nossos testes dependem do modelo User o post pertece a um autor temos

que carregar alguns usurios no banco de dados. Isso pode ser feito com fixtures. O que so fixtures? Fixtures so contedos de um modelo ou modelos que sero carregados no banco de dados para a execuo dos testes. As fixtures podem ser carregadas atravs de SQL ( INSERT INTO ... ), arquivos CSV ou, preferencialmente, arquivos YAML. Cada arquivo YAML de conter dados de um nico modelo. O nome do arqu ivo de fixtures deve ser igual ao nome da tabela do banco de dados com a extenso .yml. O Rails cria estes arquivos para voc, automaticamente, toda vez que voc cria uma migrao ou modelo. # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtur es.html one: id: 1 two: id: 2 O arquivo de fixtures composto por diversos blocos que so equivalentes a registros do banco de dados. Lembre -se: use tabulao separadas por espaos. Vamos editar o arquivo "test/fixtures/users.yml" para adicionar alguns usurios vlidos. bart: id: 1 name: Bart Simpson email: bart@simpsons.com password: test
krusty: id: 2 name: Krusty The Clown email: krusty@simpsons.com password: test

Agora, abra o arquivo "test/unit/post_test.rb" e carregue as fixtures de usurios. fixtures :posts, :users O mais interessante de se utilizar fixtures que voc recebe automaticamente um mtodo com o mesmo nome da tabela de banco de dados e cada registro pode ser acessado pelo nome bart e krusty, no nosso caso que voc definiu no arquivo de fixtures. Utilize nomes significativos sempre que puder. Vamos aproveitar e j criar algumas fixtures de posts. Abra o arquivo "test/unit/fixtures/posts.yml" e adicione o texto abaixo. rails_rules: id: 1 title: Rails rules body: Rails is a killer fram ework built with Ruby created_at: <%= Time.now %> updated_at: <%= Time.now %> user_id: 1 allow_comments: false
ruby_rules: id: 2

title: Ruby also rules body: Ruby is a charming language created_at: <%= Time.now %> updated_at: <%= Time.now %> user_id: 1 allow_comments: true

Sim, voc pode utilizar cdigo Ruby dentro do arquivo de fixtures! Isso extramente til quando voc precisa chamar algum mtodo de um modelo (para criptografar a senha, por exemplo) ou trabalhar com datas, como o nosso caso. Vamos preparar a nossa classe, adicionando o mtodo create, da mesma maneira que criamos nos testes do modelo User. require File.dirname(__FILE__) + '/../test_helper'
class PostTest < Test::Unit::TestCase fixtures :posts, :users # Replace this with your real tests. def test_truth asserttrue end private def create(options={}) Post.create({ :title => 'Title', :excerpt => 'Excerpt', :body => 'Body', :allow_comments => true, :user_id => 1 }.merge(options)) end end

Nossos primeiros teste iro validar os campos obrigatrios. def test_should_be_invalid post = create (:title => nil, :excerpt => nil, :body =>nil, :allow_comments => nil, :user_id => nil) assert_invalid post, "Post shouldn't be created" end
def test_should_require_title post = cr eate(:title => nil) assert post. errors.invalid?(:title), ":title should be required" assert_invalid post, "Post shouldn't be created" end def test_should_require_body post = create (:body =>nil) assert post. errors.invalid?(:body), ":body should be required" assert_invalid post, "Post shouldn't be created" end def test_should_require_author post = create (:user_id => nil) assert post. errors.invalid?(:user_id), ":user_id should be required" assert_invalid post, "Post shouldn't be created" end

O resumo pode ter no mximo 250 caracteres mas opcional. Ento vamos aos testes. def test_should_accept_excerpt post = create (:excerpt => 'Testing excerpt' ) deny post. errors.invalid?(:excerpt), ":excerpt should have been valid" assert_valid post end
def test_should_deny_long_excerpt post = create (:excerpt => "a" * 251) assert post. errors.invalid?(:excerpt), ":excerpt should have had an error" assert_invalid post, "Post shouldn't be created" end

Temos que verificar agora se o usurio existe e se o post foi corretamente associado a ele. Nossos testes: def test_should_deny_non_integer_user post = create (:user_id => 'a' ) assert post. errors.invalid?(:user_id), ":user_id should have had an error" assert_invalid post, "Post shouldn't be created"
post = create (:user_id =>1.397) assert post. errors.invalid?(:user_id), ":user_id should have had an error" assert_invalid post, "Post shouldn't be created" end def test_should_check_post_authorship # check all fixtures were loaded assert_equal 2, users(:bart).posts.size, "user should have had 2 posts" # assign a post without user_id post = create (:user_id => nil) # then, assign a post using the relationship method users(:bart).posts<< post #now, check if user have one more post assert_equal 3, users(:bart).posts.size, "user should have had 3 posts" # assign a post to a user that doesn't exist post = create (:user_id => 100) assert post. errors.invalid?(:user), "User doesn't exist, so it should be required" end E aqui temos um novo mtodo de assero: assert_equal . Esse mtodo

verifica se dois valores so iguais. Veja alguns mtodos de assero que voc pode usar. assert(boolean, message) Se o parmetro boolean for nil ou false a assero ir falhar.
assert_equal(expected, actual, message) assert_not_equal(expected, actua l, message)

A assero ir falhar a menos que expected e actual sejam iguais/diferentes.

assert_nil(object, message) assert_not_nil(object, message)

A assero ir falhar a menos que object seja/no seja nil.


assert_raise(Exception, ..., message) { block... } assert_not_raise(Exception, ..., message) { block... }

A assero ir falhar a menos que block dispare/no dispare um erro da exceo especificada.
assert_match(pattern, string, message) assert_no_match(pattern, string, message)

A assero ir falhar a menos que string seja/no seja correspondente expresso regular pattern .
assert_valid(record)

Falha a menos que record no tenha erros de validao. Na parte dois deste artigo voc ver outros mtodos de assero disponveis para testes dos controllers. E ao rodarmos os testes unitrios, temos... ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started .FEFFFFF....... Finished in 0.098366 seconds.
1) Failure: test_should_be_invalid(PostTest) [./test/unit/../test_he lper.rb:30:in `deny' ./test/unit/../test_helper.rb:34:in `assert_invalid' ./test/unit/post_test.rb:9:in `test_should_be_invalid']: Post shouldn't be created. <false> is not true. 2) Error: test_should_check_post_authorship(PostTest): NoMethodError: undefined method `posts' for #<User:0xb7334d2c> /usr/lib/ruby/gems/1.8/gems/activerecord 1.15.3/lib/active_record/base.rb:1860:in `method_missing' ./test/unit/post_test.rb:49:in `test_should_check_post_authorship' 3) Failure: test_should_deny_long_excerpt(PostTest) [./test/unit/post_test.rb:38]: :excerpt should have had an error. <false> is not true. 4) Failure: test_should_deny_non_integer_user(PostTest) [./test/unit/../test_helper.rb:30:in `deny' ./test/unit/../test_helpe r.rb:34:in `assert_invalid'

./test/unit/post_test.rb:44:in `test_should_deny_non_integer_user']: Post shouldn't be created. <false> is not true. 5) Failure: test_should_require_author(PostTest) [./test/unit/post_test.rb:26]: :user_id should be requir ed. <false> is not true. 6) Failure: test_should_require_body(PostTest) [./test/unit/post_test.rb:20]: :body should be required. <false> is not true. 7) Failure: test_should_require_title(PostTest) [./test/unit/post_test.rb:14]: :title should be required. <false> is not true. 15 tests, 21 assertions, 6 failures, 1 errors

... uma verdadeira catstrofe! Um erro no teste test_should_check_post_authorship nos diz que o mtodo posts no existe. Mas parando para pensar, faz todo sentido, j que ns ainda no definimos o relacionamento entre os modelos. Vamos tratar este erro apenas colocando o relacionamento no modelo User. class User < ActiveRecord::Base has_many :posts, :dependent => :destroy
#[...] end

Note que apenas exibi o cdigo relev ante a esta alterao; as validaes anteriores permanecem e so representadas aqui por #[...] . Aps adicionar esta linha, voc j tem o relacionamento entre posts e usurios e se voc rodar os testes agora, apenas as falhas sero exibidas. ~/blog$ rake te st:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started .FFFFFFF........ Finished in 0.142015 seconds.
1) Failure: test_should_be_invalid(PostTest) [./test/unit/../test_helper.rb:29:in `deny' ./test/unit/../test_helper.rb:33:in `ass ert_invalid' ./test/unit/post_test.rb:9:in `test_should_be_invalid']: Post shouldn't be created. <false> is not true. 2) Failure: test_should_check_post_authorship(PostTest) [./test/unit/post_test.rb:63]: User doesn't exist, so it should be required. <false> is not true. 3) Failure: test_should_deny_long_excerpt(PostTest) [./test/unit/post_test.rb:38]: :excerpt should have had an error. <false> is not true.

4) Failure: test_should_deny_non_number_user(PostTest) [./test/unit/post_test.rb:44]: :user_id should have had an error. <false> is not true. 5) Failure: test_should_require_body(PostTest) [./test/unit/post_test.rb:20]: :body should be required. <false> is not true. 6) Failure: test_should_require_title(PostTest) [./test/unit/post_test.rb:14]: :title should be required. <false> is not true. 7) Failure: test_should_require_user(PostTest) [./test/unit/post_test.rb:26]: :user_id should be required. <false> is not true. 16 tests, 25 assertions, 7 failures, 0 errors

Vamos s validaes mais triviais utilizando o mtodo validates_presence_of . class Post < ActiveRecord::Base validates_presence_of :title validates_presence_of :body validates_presence_of :user_id end ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started ..FFF........... Finished in 0.133536 seconds.
1) Failure: test_should_check_post_authorship(PostTest) [./test/unit/post_test.rb:63]: User doesn't exist, so it should be required. <false> is not true. 2) Failure: test_should_deny_long_excerpt(PostTest) [./t est/unit/post_test.rb:38]: :excerpt should have had an error. <false> is not true. 3) Failure: test_should_deny_non_number_user(PostTest) [./test/unit/post_test.rb:44]: :user_id should have had an error. <false> is not true. 16 tests, 28 assertions, 3 failures, 0 errors

A coisa j melhorou bastante. As trs falhas restantes so relativamente simples de resolver. Primeiro vamos verificar se o user_id um nmero. validates_numericality_of :user_id, :only_integer => true A falha relativa ao tamanho do res umo pode ser resolvido com uma validao como esta: validates_length_of :excerpt, :maximum => 250, :if => :check_excerpt?
private

def check_excerpt? !self.excerpt.blank? end

E agora, s mais uma falha para corrigir. Estamos ficando bons nisso! ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader Started ..F............. Finished in 0.149596 seconds.
1) Failure: test_should_check_post_authorship(PostTest) [./test/unit/post_test.rb:67]: User doesn't exist, so it should be required. <false> is not true. 16 tests, 32 assertions, 1 failures, 0 errors

Para corrigir esta falha, voc deve primeiro definir que um post est associado a um usurio. Ns fizemos apenas o outro caminho, dizendo que um usurio possui diversos posts. Altere o seu modelo Post, adicionando o relacionamento belongs_to :user . Agora, voc poder adicionar as validaes relativas a esta falha. class Post < ActiveRecord::Base belongs_to :user
validates_associated :user validates_presence_of :user #[..] end

Perceba que estamos validando a presena do atributo/mtodo user e no user_id . A mesma coisa est sendo feita na segunda parte do teste test_should_check_post_authorship . Isso deve ser feito para se validar a associao entre um post e um usurio, de modo qu e o usurio deve realmente existir; caso contrrio, teriamos uma associao incorreta no teste, j que o usurio com id 100 no existe. Parabns! Mais um modelo foi devidamente testado. ~/blog$ rake test:units Loaded suite /usr/lib/ruby/1.8/rake/rake_test_ loader Started ................ Finished in 0.141916 seconds.
16 tests, 32 assertions, 0 failures, 0 errors

Falta apenas mais um modelo para testarmos: so os comentrios. Crie o modelo Comment e abra o arquivo de migrao "db/migrate/003_create_comments. rb". Ele deve se parecer com isto: class CreateComments < ActiveRecord::Migration defself.up create_table :comments do |t| t.column :post_id, : integer, :null =>false t.column :name, :string, :limit => 100, :null =>false t.column :email, :string, :limit => 100, :null =>false t.column :url, :string, :limit => 255, :null =>true t.column :created_at, :datetime t.column :active, :boolean, :default => false, :null =>false t.column :body, :text, : null =>false end

end defself.down drop_table :comments end end

y y y

Os requisitos para este modelo so: um comentrio deve estar associado a um post s possvel comentar em posts que esto com esta opo ativada nome, comentrio e email so obrigatrios; a URL opcional. Execute a migrao e abra o arquivo "test/unit/comment _test.rb". Vamos criar nossos testes. Os testes so muito semelhantes aos criados anteriormente, por isso, irei apenas coloc -los aqui, sem explicaes. require File.dirname(__FILE__) + '/../test_helper'
class CommentTest < Test::Unit::TestCase fixtures :comments, :posts def test_should_be_created comment = create (:post_id => posts (:ruby_rules ).id) assert_valid comment end def test_should_be_invalid comment = create (:email =>nil, :name => nil, :url => nil, :body => nil) assert_invalid comment, "Comment shouldn't be created" end def test_should_require_name comment = create (:name =>nil) assert comment. errors.invalid?(:name), ":name should have had an error" assert_invalid comment, "Comment shouldn't be created" end def test_should_require_email comment = create(:email =>nil) assert comment. errors.invalid?(:email), ":email should have had an error" assert_invalid comment, "Comment shouldn't be created" end def test_should_deny_bad_email comment = create (:email => 'bad@ format') assert comment. errors.invalid?(:email), ":email should be in a valid format" assert_invalid comment, "Comment shouldn't be created" end def test_should_require_comment comment = create (:body =>nil) assert comment. errors.invalid?(:body), ":body should have had an error" assert_invalid comment, "Comment shouldn't be created" end def test_should_require_post comment = create (:post_id => nil)

assert comment. errors.invalid?(:post_id), ":post_id should have had an error" assert_invalid comment, "Comment shouldn't be created" comment = create (:post_id => 100) assert comment. errors.invalid?(:post), "Post doesn't exist so it should be required" end def test_cannot_comment_because_post_is_closed comment = create (:post_id => posts (:rails_rules ).id) assert_invalid comment, "Comment shouldn't be created" end private def create(options={}) Comment.create({ :email => 'burns@simpsons. com', :name => 'Mr Burns', :url => 'http://thesimpsons. com/burns/', :body =>"Get em', Smithers." , :post_id => 2 }.merge(options)) end end

Abra tambm o arquivo "test/fixtures/comments.yml" e adicione as fixtures abaixo: comment_on_ruby_post: id: 1 name: Bart Simpson email: bart@thesimpsons.com url: http://thesimpsons.com/bart body: Heya! post_id: 2 created_at: <%= Time.now %> active: 0
another_comment_on _ruby_post: id: 2 name: Bart Simpson email: bart@thesimpsons.com url: http://thesimpsons.com/bart body: Heya! post_id: 2 created_at: <%= Time.now %> active: 0 comment_on_rails_post: id: 3 name: Principal Skinner email: skinner@thesimpsons.com url: http://thesimpsons.com/skinner body: Bart, you'll be in detention forever! post_id: 1 created_at: <%= Time.now %> active: 1

Ao executar estes testes teremos muitas falhas e apenas 1 erro. Novamente, o erro est ligado ao relacionamento que no criamos. Par a corrig-lo, altere o modelo Post. class Post < ActiveRecord::Base

has_many :comments, :dependent => :destroy #[...] end

Para validar nosso modelo, basta adicionar os validadores abaixo: class Comment < ActiveRecord::Base belongs_to :post
validates_associated :post validates_presence_of :post validates_presence_of :post_id validates_numericality_of :post_id, :only_integer => true validates_presence_of :name validates_presence_of :email validates_presence_of :body, :message => "Don't you wann a comment this post?" validates_format_of :email, :with => /^ ([^@\s]+)@((?:[-a-z0-9]+\.)+[az]{2,})$/i private def validate if post errors.add_to_base ("Comments are closed" )unless post.allow_comments end end end A nica coisa que voc pode estranhar o mtodo validate . Nele, verifico se

um post foi passado para, ento, checar se o post est aberto para comentrio ou no.

Dicas e consideraes
Autotest
Se voc se irrita em ter que executar manualmente o rake test:units ou acha que testar muito lento, d uma olhada no ZenTest, especificamente no autotest. O autotest roda os testes para voc automaticamente e o que melhor, apenas os mtodos que foram alterados. Dessa forma, voc tem um feedback muito mais rpido. Para instal-lo, execute sudo gem install zentest --include-dependencies y. Depois, basta executar autotest na raz do seu aplicativo. No nuby on rails tem um screencast mostrando o autotest em funcionamento.

Usando o logger dentro dos mtodos de teste


Se voc, por alguma razo, quiser uilizar o logger dentro dos Testes de Unidade, adicione o seguinte mtodo como helper de teste. def logger RAILS_DEFAULT_LOGGER end Lembre-se que isso tambm pode ser feito no seu modelo.

Teste, codifique, teste, codifique

Voc pode demorar algum tempo at se acostumar em testar antes de codificar, mas acredite, vale muito a pena! Voc se torna mais produtivo e seu cdigo ter menos bugs. Quanto mais completo seus testes forem, maior a certeza de que tudo est funcionando co mo deveria. O caminho contrrio codificar primeiro, testar depois pode parecer mais fcil no comeo, mas medida que seu cdigo cresce, voc acabar esquecendo de testar alguma funcionalidade importante. Se isso acontecer, tora para que no acontea nenhum bug.

Nossa, seu exemplo no nada DRY


No sei se voc percebeu mas as mensagens de erro se repente em diversos testes. Fiz isso para deixar o exemplo o mais didtico possvel. Se isto te incomoda, remova todas as mensagens. Elas so opcionais.

Queima! Ele no criptografou as senhas....


No nosso exemplo, a senha foi armazenada sem nenhum tipo de criptografia. Nunca, jamais, em hiptese alguma, armazene informaes importantes como a senha de maneira "raw". No fiz isso aqui para no complicar.

E para finalizar...
Os seus testes podem ser mais completos que estes que fizemos. Se voc no tem idia do que testar, faa sempre uma lista dos requisitos para resolver o problema em questo. Do ponto de vista tcnico, procure por projetos desenvolvidos em Rails e analise a sute de testes. Com certeza voc encontrar muito coisa legal. A continuao deste artigo ser sobre Testes Funcionais. No tenho a menor idia de quando irei escrev -lo, mas espero que no demore. Dvidas, crticas ou sugestes? Poste um comentrio!

http://www.argonavis.com.br/palestras/fenasoft2003/index.html

Congresso Fenasoft 2003 - Palestras da Argo Navis


Esta pgina ir disponibilizar as palestras ministradas por Helder da Rocha no evento Congresso Fenasoft 2003 aps a sua realizao. As palestras ocorrero nos dias 28 (Ant) e 30 (JUnit) de maio. Estaro disponveis as palestras em PDF e cdigo-fonte dos exemplos para download.

Desenvolvimento guiado por testes com o JUnit: uma introduo


prtica de testes de unidade em Java utilizando metodologia test-first (test-driven development) e JUnit. Realizar testes de unidade uma prtica fundamental em qualquer projeto de desenvolvimento de software orientada a objetos. Em projetos baseados na linguagem Java, esta prtica pode ser implementada com a ajuda do JUnit, o mais popular framework de testes para Java. Escrito por Kent Beck (inventor do eXtreme Programming) e Erich Gamma (autor de Design Patterns), o JUnit faz parte da famlia de ferramentas de testes xUnit, que inclui implementaes do framework para SmallTalk, C++, Delphi e outras linguagens. Apesar de simples, JUnit oferece um ambiente completo para a realizao de testes de unidade em cdigo Java, e ainda suporta extenses - algumas bastante populares como o Jakarta Cactus, usado em testes para na plataforma J2EE. Nesta palestra apresentaremos o JUnit e demonstraremos como utiliz-lo para implementar uma metodologia de desenvolvimento guiada por testes (Test Driven Development - TDD). Exploraremos tambm as melhores prticas, padres e discutiremos algumas dificuldades relativas prtica de testar, tais como tcnicas para descobrir testes, como lidar com dependncias, testes em GUIs, etc. O material disponibilizado conter ainda exemplos de uso de Cactus e HttpUnit - framework de testes para J2EE. Se voc desenvolve em Java e no faz testes de unidade, no perca esta palestra! Com o JUnit seu cdigo ter qualidade muito melhor, ser mais confivel e fcil de manter. Aposente seu debugger, ganhe noites de sono, diminua o stress aprendendo a usar o JUnit para testar seu cdigo antes de escrev-lo! Download da palestra em PDF (zipada) y y Verso para visualizao na tela (1 slide por pgina) (396kB) Verso para impresso (2 slides por pgina) (446kB)

Cdigo-fonte de demonstraes para download y y TDD Example (0 kB): exemplos mostrados de TDD. Em breve. JUnit demos (0kB): coleo de exemplos que demonstram a utilizao do JUnit. Em breve.

Construo de Aplicaes Java com o Apache Ant: Apresentao do


Apache Ant, demonstrao de suas principais tarefas e tipos, exemplos de buildfiles para construir aplicaes de objetos distribudos, Web, EJB, XML e grficas. Ant uma ferramenta indispensvel em qualquer ambiente de desenvolvimento Java. Com Ant possvel automatizar tudo, desde a compilao at a gerao de documentao, execuo de testes, JAR, deployment, execuo, gerao de PDFs, envio de e-mails, etc. Ant no difcil de aprender. Sabendo usar uma dzia de tags XML do Ant j suficiente para escrever um script que no

mnimo faz a mesma coisa que qualquer "boto build" do mais sofisticado IDE. Ant escrito em Java e roda em Linux, DOS, Solaris, Mac, e outras plataformas. E de graa! Nesta palestra apresentaremos o Ant, mostraremos como usar suas principais tarefas, tipos de dados e tags. Mostraremos como executar o Ant e descreveremos suas mil e uma utilidades. No final, mostraremos exemplos de buildfiles que podero ser usados para construir vrios tipos de aplicaes. Como material adicional (que no ser apresentado), os slides disponibilizados para download iro conter tambm material sobre integrao contnua e extenso do Ant. Se voc desenvolve em Java e nunca ouviu falar do Ant, no perca esta palestra. Se voc j ouviu falar mas no conhecia o que Ant capaz de fazer, comparea, veja e discuta. O Ant tornar seu ambiente de desenvolvimento muito mais produtivo e organizado, e voc economizar tempo e reduzir custos.