81,9(56,'$'()('(5$/'25,2*5$1'('268/ ,167,7872'(,1)250È7,&$ 7HVW'ULYHQ'HYHORSPHQW Otávio Gaspareto REJDVSDUHWR#LQIXIUJVEU ,QWURGXomR 7HVWGULYHQGHYHORSPHQW, ou TDD, também conhecido por WHVWILUVWGHYHORSPHQW, é um conjunto de técnicas associadas com ([WUHPH 3URJUDPPLQJ(XP) e métodos ágeis. TDD, no processo XP, é a única maneira de se codificar [1]. Segundo Miller [12], TDD reivindica um desenvolvimento incremental do código que inicia com testes, incluindo freqüentemente testes de regressão. Uma das regras do TDD é a seguinte: “se você não consegue escrever um teste para aquilo que você está codificando, então você nem deveria pensar sobre codificar”. Esta técnica de desenvolvimento é antiga, mas tornou-se conhecida, após sua adoção em metodologias ágeis de desenvolvimento, tornando assim, um desenvolvimento incremental, onde o código é escrito para passar pelos testes já escritos previamente. Quando uma nova funcionalidade é adicionada ao sistema, isto deve manter a estrutura simples inicial intacta, e pode ser que seja necessário reestruturar algo. Isto, entretanto, pode quebrar alguma outra funcionalidade que já estivesse funcionado. Para garantir que isto não irá acontecer, testes automatizados devem acontecer [6]. Atualmente, TDD tem sido usado em muitas áreas de tecnologia, além do desenvolvimento de software, como também no desenvolvimento de sistemas embarcados [5], onde além do software, é desenvolvido o hardware sob o qual ele irá ser executado. Em algumas universidades, TDD está sendo ensinado junto com as cadeiras básicas de programação, a fim de enfatizar testes como sendo uma prática fundamental para a carreira de informata [13] [15]. Este artigo pretende realizar uma revisão mostrando do que trata a TDD e o que existe de atual nesta disciplina, como técnicas associadas, SDWWHUQV e ferramentas disponíveis. 2TXHp7HVW'ULYHQ'HYHORSPHQW De acordo com Kent Beck [18], um método ágil é comparável ao ato de dirigir um carro: você deve observar a estrada e fazer correções contínuas para se manter no caminho. Neste contexto onde a agilidade é fundamental, o testador seria aquele que ajuda o motorista a chegar com segurança ao seu destino, impedindo que sejam feitas conversões incorretas durante o percurso, evitando que o motorista se perca e fazendo com que ele pare e peça instruções quando necessário. Neste ambiente, destaca-se o TDD, como sendo uma abordagem evolutiva na qual o desenvolvedor escreve o teste antes de escrever o código funcional necessário para satisfazer aquele teste [6]. Segundo Marrero [15], o objetivo principal do TDD é especificação e não validação. Em outras palavras, é uma forma de refletir sobre a modelagem antes de escrever código funcional. Já segundo Baumeister [6], desenvolvimento dirigido por testes é uma técnica de programação onde o principal objetivo é escrever código funcional limpo a partir de um teste que tenha falhado. Como efeito colateral, obtém-se um código fonte bem testado. Após escrever os casos de teste, que devem especificar apenas uma pequena parte da funcionalidade, inicialmente nem devem compilar, pois a funcionalidade ainda nem foi escrita, o desenvolvedor deve implementar o código necessário apenas para passar pelo teste. Feito isto, fica a cargo do desenvolvedor realizar o UHIDFWRU do código, garantindo que continue a forma mais simples de código para satisfazer a determinada funcionalidade [2]. O ciclo, de uma visão macro, poderia ser visto como: escrever poucos casos de teste, implementar o código; escrever poucos casos de teste, implementar o código, e assim por diante. A figura 1 ilustra como é feito o processo de TDD. )LJXUD±&LFORGR7'' O processo XP sugere, que outras disciplinas sejam utilizadas junto com o TDD, como por exemplo, a SDLUSURJUDPPLQJ (programação em par) e o UHIDFWRULQJ. Existem várias questões que são levantadas da adoção do TDD em um projeto. Uma delas é em relação ao que não deve ser testado. Kent Beck [2] diz que devem ser testados condições, ORRSV, operações e polimorfismos, porém, somente aqueles que o próprio desenvolvedor escreveu. Quanto à questão sobre se os testes estão bons, há três pontos que devem ser observados: i) se são necessárias muitas linhas de código criando objetos para uma simples asserção, então há algo de errado; ii) se não é possível encontrar facilmente um lugar comum para o código de inicialização, então existem muitos objetos firmemente associados. iii) testes que quebram inesperadamente sugerem que uma parte da aplicação está afetando outra parte. É necessário projetar até que esta distância seja eliminada, ou quebrando esta conexão ou trazendo as duas partes juntas. Uma vantagem significativa do TDD é que ele possibilita ao desenvolvedor dar pequenos e controlados passos durante a codificação do software [6]. Esta prática é mais aconselhável do que escrever grandes quantidades de código de uma só vez. Alguns motivos para a adoção do TDD, segundo Kaufmann [10], são: i) o desenvolvimento parte do princípio dos objetivos, após isto, é pensado nas possíveis soluções; ii) o entendimento do sistema pode ser feito a partir da leitura dos testes; iii) não é desenvolvido código desnecessário; iv) não existe código sem teste; v) uma vez um teste funcionando, é sabido que ele irá funcionar sempre, servindo também, como teste de regressão; vi) os testes permitem que seja feito UHIDFWRU, pois eles garantirão que as mudanças não alteram o funcionamento do sistema. Segundo Mugridge [7], como conseqüências do uso do TDD, têm-se que o código é escrito de maneira testável isoladamente, pois geralmente, códigos escritos da maneira tradicional, possuem alto acoplamento e uma pobre orientação a objetos. Os testes atuam como uma documentação do sistema, pois eles seriam os primeiros clientes a usarem as classes desenvolvidas, mostrando ao desenvolvedor, o que é necessário fazer. Outros benefícios provindos da adoção do modelo de desenvolvimento TDD foram abordados por Müller [1], no qual, foi feita uma análise do retorno de investimento em se utilizar TDD ao invés de modelos convencionais de desenvolvimento. Nele, obteve-se a qualidade final do código, era superior ao modelo convencional, pois não seria necessário perder uma grande fatia de tempo no final do projeto para a correção de defeitos, pois estes eram capturados e corrigidos ao longo do desenvolvimento do sistema. A figura 2 mostra de forma clara a diferença existente entre os dois modelos. )LJXUD±&RPSDUDomR7''[0RGHORFRQYHQFLRQDOGHGHVHQYROYLPHQWR>@ 7''HWHVWHVWUDGLFLRQDLV TDD é primariamente uma técnica de programação que garante que o código de um sistema esteja inteiramente testado de forma unitária [20]. Entretanto, há mais teste do que isto, pois ainda é necessário que sejam realizados os tradicionais testes, como testes funcionais, testes de usuário, testes de integração, entre outros. A maioria destes testes podem ser realizados tardiamente ao sistema [20]. Com a realização de testes tradicionais, serão encontrados mais defeitos. Isto ocorre com o TDD; quando um teste falha, o desenvolvedor sabe que necessita resolver o problema [20]. TDD aumenta a credibilidade de que o sistema realmente funciona, e está de acordo com os requerimentos propostos [2]. Um interessante efeito do TDD, é que é possível cobrir 100% do sistema com testes, pois cada linha de código é testada, sendo algo que não é garantido com testes tradicionais. Estudos comprovam que sistemas desenvolvidos utilizando-se de TDD resultam com melhor código e menos defeitos do que sistemas que utilizaram somente testes tradicionais [9] [13]. )HUUDPHQWDV Existem atualmente diversas ferramentas, tanto RSHQVRXUFH como proprietárias, que apóiam o desenvolvimento de sistemas baseados em TDD, dentre elas, podemos citar IUDPHZRUNV para testes unitários, os chamados xUnits, entre os quais, o mais famoso é o JUnit, e para a criação do PRFNobjects, como também editores de código que auxiliam no desenvolvimento tanto do código como na execução dos testes. -8QLW JUnit é um IUDPHZRUN implementado inteiramente em Java, seguindo a filosofia do código-livre, que possibilita a criação de suítes de testes [19]. Uma suíte de testes, nada mais é, do que uma grande coleção de classes de testes, onde cada conjunto de classes é responsável por testar uma classe ou uma pequena funcionalidade do código que será colocado em produção [16]. Outra facilidade que a ferramenta apresenta é uma interface gráfica que facilita a execução da suíte de teste, e conta também, com uma barra de progresso, mostrando ao usuário o sucesso ou falha dos métodos de testes individualmente, assim que eles são executados. A figura 3 apresenta um teste que foi executado com sucesso. Nota-se que a barra verde está relacionado com o lema do JUnit, que é “NHHSWKHEDUJUHHQWRNHHSWKH FRGHFOHDQ” [19]. )LJXUD±6XFHVVRQDH[HFXomRGHWHVWH>@ Quando algum teste falhar, a barra troca de cor, e passa de verde para vermelha, alertando assim o desenvolvedor que algo está errado. Esta interface gráfica, também exibe a causa do erro, e mostra informações completas sobre a execução da suíte de testes, como é exibido na figura 4. )LJXUD±)DOKDQDH[HFXomRGHWHVWH>@ Com JUnit, um test case é implementado como sendo uma subclasse de junit.framework.TestCase. Para realizar o teste de comparação entres os valores esperados com os valores obtidos, são utilizados métodos como assertEquals, assertTrue, assertNull, entre outros [14]. O método setUp() é invocado para inicializar a suíte de testes antes da execução dos mesmos, enquanto que o método tearDown() é usado após finalizar a suíte de testes. Os métodos que irão realizar os testes no sistema devem iniciar com a palavra “test”, pois é utilizada a reflexão do Java para a chamada dos mesmos; não devem retornar nenhum valor, ou seja, serem métodos void; e não devem receber nenhum parâmetro. 0RFN2EMHFWV 0RFN2EMHFWV são utilizados com o propósito de escrever testes como se tivéssemos tudo o que realmente necessitamos do ambiente [11]. Este processo apresenta ao desenvolvedor o que o ambiente deve prover para o desenvolvimento do sistema. Ao se testar partes isoladas do sistema, como apenas um objeto, por exemplo, o programador deve levar em consideração a interação deste objeto em questão com outros objetos. A figura 5 apresenta como é realizado um teste usando-se PRFNREMHWFV. No caso, têm-se um objeto A, que necessita interar-se com um serviço que é disponibilizado pelo objeto S, mas como ainda não se tem uma implementação de S, ou este será implementado por terceiros, realiza-se o PRFN do objeto S, forçando assim, o uso de interfaces para o desenvolvimento. )LJXUD±8VRGHPRFNREMHFWV>@ Um conhecido IUDPHZRUN utilizado para o desenvolvimento de PRFN REMHFWV é o jMock[11], que assim como o JUnit, é escrito em Java. O ponto de entrada principal do jMock, é a classe MockObjectTestCase, que faz uso da classe TestCase do JUnit. Os PRFN objetos são criados a partir do método mock(...), o qual recebe como parâmetro um objeto Class representando um tipo de interface, e retorna um objeto Mock, que imlpementa este interface. 3DWWHUQVSDUD7'' Existem inúmeros padrões para várias atividades na área de informática, entre eles, os mais conhecidos são os GHVLJQ SDWWHUQV, que são utilizados na construção de sistemas. Para a disciplina de TDD não é diferente, e existem alguns padrões, que no âmbito geral, tornam-se mais dicas sobre o que testar em um sistema. 7HVWHVLVRODGRV A execução de um teste não pode afetar outro teste. Segundo Beck [2], os testes devem ser desenvolvidos de maneira que sejam rápidos de serem executados, assim, eles podem ser rodados a todo o momento, não necessitando de um momento específico de execução de testes. Dessa forma, os testes isolados servem para que uma execução de um determinado teste, não interfira na execução do teste seguinte, não necessitando assim, iniciar e parar o sistema assim que cada teste é executado. /LVWDGHWHVWHV De acordo com Beck [2], antes de iniciar a codificação, devem ser feita uma lista com todos os testes que o desenvolvedor acredita que será necessário escrever. Assim, quando o desenvolvedor sentar para começar a programar, ele terá um guia do que é necessário fazer, e não corre o risco de esquecer algo. 7HVWHSULPHLUR Os testes devem ser escritos antes do código que será testado [2]. Os testes servem para dar um IHHGEDFN rápido do desenvolvimento do sistema. Assim que o tempo passa, mais estressado fica o desenvolvedor, e, portanto, caso os testes sejam deixados para o final, o desenvolvedor irá aumentar o nível de estresse, diminuindo a quantidade de testes no sistema, e impactando na qualidade do produto final. 'DGRVGHWHVWH Segundo Beck [2], devem ser utilizados dados que tornem os testes fáceis de ler e seguir. Caso o sistema tenha múltiplas entradas, então, devem existir testes que abranjam estas entradas. Porém, é desnecessário ter uma lista com dez entradas, se com apenas três, cobrem-se todas as alternativas [2]. 'DGRVHYLGHQWHV Devem ser incluídos nos testes os dados esperados e o resultado atual, e tentar exibir um relacionamento aparente entre eles, pois os testes não são escritos apenas para o computador, e sim, para que futuros desenvolvedore possam entender o código como sendo uma parte da documentação do sistema [2]. 'LVFLSOLQDVUHODFLRQDGDVGR;3 Mesmo TDD sendo uma disciplina do XP [18], esta não se encontra isolada no universo do ([WUHPH 3URJUDPPLQJ. Abaixo, uma lista de disciplinas que são complementares e/ou completadas usando-se o TDD [2]: • 3URJUDPDomR HP SDUHV – os testes escritos no TDD são excelentes peças de conversação quando se está trabalhando de forma pareada. A programação pareada melhora o TDD no momento em que o programador que está codificando o código está cansado, assim, o outro pra ficaria se encarregaria de codificar; • ,QWHJUDomR FRQWtQXD – testes são um excelente recurso para esta disciplina, permitindo que sempre seja feita uma integração. Os ciclos são definidos em períodos curtos, algo entre quinze e trinta minutos, ao invés de uma ou duas horas de codificação; • 'HVLJQ VLPSOHV – codificando apenas o necessário para os testes, e removendo código duplicado, automaticamente obtém-se um design adaptado para a necessidade atual. • 5HIDFWRULQJ – a regra de remoção de código duplicado é outra maneira de se fazer UHIDFWRULQJ. Mas os testes conferem ao desenvolvedor, que grandes mudanças feitas no sistema, não alteraram o funcionamento do mesmo. Quanto mais seguro de que mudanças feitas não alteram o sistema, mais agressivo se torna o UHIDFWRULQJ aplicado pelo desenvolvedor. • (QWUHJD FRQWtQXD – com testes realizados no sistema, têm-se mais confiança na entrega de partes do sistema, e o código vai para produção mais rápido. &RQVLGHUDo}HVILQDLV Diante das possibilidades existentes atualmente para o desenvolvimento de sistemas, nota-se que TDD torna-se uma alternativa viável para as organizações utilizarem em seus processos de desenvolvimento iterativo. 5HIHUrQFLDV [1] M. Müller, F. Padberg, About the Return on Investment of Test-Driven Development. Disponível em http://www.ipd.uka.de/mitarbeiter/ muellerm/publications/edser03.pdf. [2] K. Beck, Test-Driven Development By Example. Addison Wesley, 2000. Estados Unidos [3] L. Crispin, T. House, The Need for Speed: Automatic Acceptance Testing in an Extreme Programming Environment. Tensegrent, EUA [4] H. Heinecke, C. Noak, Integrating Extreme Programming and Contracts. Disponível em http:// www.heinecke.com/publications/XPandContracts.pdf [5] J. Grenning, Progress before hardware. Disponível em http://www.objectmentor.com/resources/articles/ObjectMentor/resources/articles/Pr ogressBeforeHardware [6] H. Baumeister, M. Wirsing, Applying Test-First Programming and Iterative Development in Building an E-Business Application. Disponível em http:// www.pst.informatik.uni-muenchen.de/ projekte/caruso/ssgrr2002w.pdf [7] R. Mugridge, Test Driven Development and the Scientific Method. Disponivel em http://www.agiledevelopmentconference.com/ 2003/files/P6Paper.pd [8] D. Saff, M. Ernst, An Experimental Evaluation of Continuous Testing During Development. Disponivel em http://pag.csail.mit.edu/~mernst/ pubs/ct-user-studyissta2004.pdf [9] B. George, L. Williams, An Initial Investigation of Test Driven Development In Industry. Disponivel em http://collaboration.csc.ncsu.edu/laurie/Papers/TDDpaperv8.pdf [10] R. Kaufmann, D. Janzen, Implications of Test-Driven Development A Pilot Study. Disponivel em http://people.bethelks.edu/djanzen/Papers/pos12kaufmann.pdf [11] S. Freeman, T. Mackinnon, N. Pryce, Mock Roles, Not Objects. Disponivel em http://www.jmock.org/oopsla2004.pdf [12] K. W. Miller, Test Driven Development on the Cheap: Text Files and Explicit Scaffolding. Disponivel em http://www.ccsc.org/northwest/docarchive/ 2004/augustmailing2004final.doc [13] C. G. Jones, Test-Driven Development Goes to School. Disponível em http://portal.acm.org/citation.cfm?id=1040261& dl=ACM&coll=GUIDE&CFID=15151515&CFTOKEN=6184618 [14] M. Olan, Unit Testing: Test Early, Test Often. Disponivel em http:// www.citeulike.org/user/tinkha/article/126490 [15] W. Marrero, A. Settle, Testing First: Emphasizing Testing in Early Programming Courses. Disponivel em http://portal.acm.org/ft_gateway.cfm?id=1067451&type=pdf&coll=GUIDE&dl=G UIDE&CFID=69236573&CFTOKEN=4183298 [16] J. Langr, Evolution of Test and Code Via Test-First Design. Disponivel em http://www.objectmentor.com/resources/articles/tfd.pdf [17] Wikipedia. driven_development Disponível em http://en.wikipedia.org/wiki/Test- [18] K. Beck, Extreme Programming Explained. Addison Wesley, 2000. Estados Unidos [19] JUnit. Disponível em: http://www.junit.org [20] Agile Data. Disponível em http://www.agiledata.org/essays/tdd.html