• Nenhum resultado encontrado

TDD. Benefícios do. medidos na prática. capa_

N/A
N/A
Protected

Academic year: 2021

Share "TDD. Benefícios do. medidos na prática. capa_"

Copied!
9
0
0

Texto

(1)

medidos na prática

TDD

TDD

capa_

Com a popularização dos

méto-dos ágeis, difundiu-se

bastan-te o desenvolvimento orientado

a testes (TDD – Test-driven

Development), disciplina

focada em design de

sof-tware que consiste em

apli-car pequenos ciclos iterativos de

teste-codificação-refatoração à

medida que a funcionalidade é

desenvolvida. O princípio central

do TDD consiste em escrever os

testes antes do código a ser

im-plementado. Essa prática

costu-ma ser vista de costu-maneira

reticen-te e cética pelas pessoas que não

possuem experiência com TDD.

Afinal de contas, será que TDD

ajuda mesmo ou apenas “está na

moda”? Existe vantagem real em

escrever os testes antes ou eles

podem ser escritos ao final com

os mesmos benefícios? Neste

artigo, examinaremos algumas

destas questões, considerando

também alguns estudos

empíri-cos publicados na área.

Benefícios do

Em poucas palavras, Test-driven Development

(TDD) é uma abordagem iterativa para

desenvol-vimento de software cujo princípio básico está em escrever testes automatizados para a funcionalida-de a ser implementada antes funcionalida-de ela ser codificada. O fato é que não é natural resolver um problema de programação desta maneira para a maioria de nós. Normalmente, o que fazemos é analisar os requisi-tos, pensar na solução e, simplesmente, codificá-la. Isto é: mãos à obra! Queremos resolver o problema!

Essa dificuldade inerente do TDD suscita bas-tante ceticismo em muitos desenvolvedores (espe-cialmente os mais antigos) quanto à efetividade des-ta prática. Será que os diversos ciclos de TDD não acarretam perda de produtividade? E as constantes refatorações não são um retrabalho desnecessário? Quer dizer que o desenvolvedor não precisa mais pensar em arquitetura? Afinal de contas, os próprios testes automatizados se pagam? Se sim, existe mes-mo algum benefício substancial em escrevê-los an-tes do código?

Neste artigo, o objetivo é refletir um pouco so-bre essas questões à luz de alguns estudos empíricos realizados sobre o uso de TDD em meio acadêmico e industrial. Primeiramente, faremos uma introdução básica sobre TDD e discutiremos aspectos mais ge-rais relacionados ao tema de testes automatizados e sua importância para todo desenvolvedor profissio-nal. Mais a frente, analisaremos questões pertinen-tes ao uso de TDD, tais como efeitos em produtivida-de e qualidaprodutivida-de.

(2)

Alexandre Gazola | alexandregazola@gmail.com | @alexandregazola Bacharel em Ciência da Computação pela Universidade Federal de Viçosa (UFV) e mestre em Informática pela PUC-Rio. Trabalha como Analista de Sistemas no BNDES, desenvolve em Java desde 2003 e possui as certificações SCJP e SCWCD. É articulista e editor-técnico da revista MundoJ, além de possuir um blog em http://alexandregazola.wordpress.com.

Dada à relevância do assunto, TDD e testes au-tomatizados são um tema frequentemente explora-do em artigos desta revista. Neste texto, partimos do pressuposto que o leitor já possui familiaridade com esses tópicos. Não obstante, tentamos deixar o artigo o mais autocontido possível, de maneira a permitir que o mesmo seja também proveitoso para os mais leigos no assunto. Para mais informações e artigos introdutórios já publicados sobre o tema, ver quadro “Para saber mais”, ao final do texto.

Test-driven Development

Em poucas palavras, Test-driven Development

(TDD) é uma abordagem iterativa para

desenvol-vimento de software baseada em testes automa-tizados. A ideia é aplicar pequenos ciclos de teste--codificação-refatoração, também conhecido como

red-green-refactor, como mostra a figura 1.

Escrever o teste antes, prática conhecida como test-first, obrigatoriamente força com que o códi-go produzido seja testável por construção, e códicódi-go testável implica código com baixo acoplamento, as-pecto extremamente importante em qualquer bom design de software.

Além disso, existe o feedback quanto à “corre-tude” do que acabou de ser feito. Tem-se um critério claro e não ambíguo do que significa uma funcio-nalidade estar pronta. Também é uma boa prática para evitar o problema de over-engineering, ou seja, implementar mais do que o estritamente

necessá-rio. TDD pode ser aplicado tanto para as pequenas partes que compõem um sistema (testes de unidade para métodos de classes) quanto para componentes maiores (testes de integração para métodos de uma camada de serviço ou testes via GuI, por exemplo). Além do benefício em design, “de quebra” o TDD traz o grande benefício colateral de fazer com que seja criada uma extensa suíte de testes de regressão para o sistema, com elevado nível de cobertura.

Robert Martin (uncle Bob) resumiu o TDD nas seguintes “leis”:

» Não é permitido escrever nenhum código de produção a menos que seja para fazer com que um teste que esteja falhando passe;

» Não é permitido escrever mais código de teste do que o suficiente para que ele falhe (consi-derando que compilação também é uma falha) – em outras palavras, o código do teste deve ser simples e exercitar um único aspecto da funcionalidade a ser implementada;

» Não é permitido escrever mais código de pro-dução do que o suficiente para que o único tes-te que está falhando passe.

Se você não está acostumado a programar usan-do TDD, muito provavelmente sentirá bastante difi-culdade em manter a disciplina necessária para se-guir essas três leis de maneira estrita. Sendo assim, antes de entrar nos méritos ou não do TDD, vamos dar um passo atrás e dar uma olhada no assunto mais geral a respeito de testes automatizados.

Testes automatizados são

imprescindíveis e ponto final

Testes automatizados são uma ferramenta ex-tremamente valiosa de feedback, ajudando a garantir tanto a qualidade externa (aquela perceptível pelo usuário) quanto à qualidade interna (aquela percep-tível pelo desenvolvedor).

A facilidade de poder executar continuamente os testes permite a identificação imediata de altera-ções indesejadas no sistema. Por essa razão, os testes existentes para um sistema também são conhecidos como testes de regressão, pois asseguram que um sis-tema não irá regredir do ponto atual em que se en-contra. Acréscimo de funcionalidade pode ser feito sem medo de quebrar algo que já funcionava.

Os testes também favorecem a prática saudável da refatoração, que consiste em aplicar pequenas transformações na estrutura interna de um software,

Figura 1. O Ciclo básico do Test-Driven Development [http://blog.

(3)

preservando o comportamento externo, com o obje-tivo de melhorar o código, tornando-o mais legível e mais aderente às manutenções pelas quais deverá passar. É curioso como muitos criticam essa práti-ca, não entendendo que refatoração é uma técnica

disciplinada para manter a boa qualidade da base de

código. Ao longo do tempo, uma base de código que não passa por refatoração tenderá a ser cada vez me-nos coesa e fatalmente começará a “cheirar mal”. A lei da entropia é cruel. Além disso, escrever código é, em muitos aspectos, como escrever um texto. Di-ficilmente um autor estará satisfeito com a primeira versão do que produziu. Código, assim como texto, deve passar por refinamentos sucessivos até atingir um estado suficientemente bom (note que escrevi

su-ficientemente bom, e não, perfeito).

Software, como o próprio nome diz, é maleável e feito para mudar. Além disso, as boas práticas de desenvolvimento (RuP, xP, Scrum etc.) pregam o de-senvolvimento iterativo e incremental, de forma que alterações e acréscimos devem ser continuamente realizados sobre algo já construído. Por isso a rede de segurança provida por uma boa suíte de testes de re-gressão automatizados é tão importante.

Testes de unidade ou de integração?

Outra questão que desperta discussão e dúvida entre os desenvolvedores: escrever testes de unida-de ou unida-de integração? Algumas pessoas advogam que apenas os testes de integração devem ser feitos ou priorizados, visto que estão mais próximos do uso real do sistema, abarcam porções maiores do código e (em tese) tendem a ser mais estáveis quando compa-rados a testes de unidade. Essas pessoas consideram que os testes de unidade possuem um acoplamento muito forte com as entidades menores do sistema (e.g.: classes e métodos) e, por isso, são mais sensí-veis a qualquer mudança e, logo, ocasionam maiores custos de manutenção.

Esse argumento é razoável, porém deixa de lado um benefício valioso que os testes de unidade ofe-recem: feedback1 localizado e instantâneo quando

algum erro acontece. É muito mais eficiente saber que o “DAO de pedidos” não persistiu o objeto cor-retamente no banco de dados do que simplesmente houve um erro na execução da rotina de fechamento da compra do cliente. Isso é feedback localizado.

Outra questão bastante relevante diz respeito ao tempo necessário para rodar os testes. Testes de integração, por envolverem diversas camadas e es-tarem mais próximos do ambiente real, levam muito mais tempo (e alguns segundos é muito tempo) para

1

Peço desculpas por repetir mais uma vez a palavra

feedback. É porque é disso que se trata praticamente

todas as boas práticas de desenvolvimento de software.

executar que um conjunto de testes de unidade que exercitam o software de maneira isolada de recur-sos externos. Ter um conjunto de testes de unidade de rápida execução encoraja o desenvolvedor a exe-cutá-los mais frequentemente e, por isso, permitem a obtenção de feedback instantâneo. O ideal é que os desenvolvedores possam executar todos os testes de unidade constantemente à medida que desenvolvem e, se não todos, pelo menos uma porção significativa dos testes de integração antes de fazer um commit. A execução completa frequente de todos os testes do sistema deve ficar a cargo de um servidor de integra-ção contínua.

Dito isto, vamos às exceções. Existem casos em que, em minha opinião, testes de integração podem ser suficientes. Por exemplo, quando se trata de com-ponentes para acesso a dados (DAOs, repositórios, arquivos etc.). No caso de DAOs, costumo escrever testes de integração usando Dbunit e banco de da-dos HSQLDb em memória, pois não creio haver muito benefício em mockar as interfaces do Hibernate para fazer testes de unidade. Outro caso é quando existem componentes bastante orientados a dados, em que pode ficar difícil e enfadonho realizar a configuração programática (muito stubbing ou mocking) necessária para os testes. um terceiro exemplo poderia ser uma classe de managed bean (Java Server Faces) que não possua lógica de negócios significativa e cujo com-portamento consista simplesmente na delegação de tarefas a outros objetos e exibição de mensagens na tela. Neste caso, testes realizados pela interface web podem ser uma boa opção. Portanto, recomenda-se bom senso na hora de decidir pelo tipo de teste a es-crever.

De qualquer forma, nunca é demais repetir: é

vi-tal que existam testes automatizados que sejam fáceis e rápidos de serem executados. Caso contrário, os testes

serão executados poucas vezes e bugs serão desco-bertos de forma tardia no ciclo de desenvolvimento.

Para finalizar esta parte, apenas quero fazer um comentário acerca da questão relativa ao acoplamen-to entre as classes de testes de unidade e suas corres-pondentes classes de produção. De fato um acopla-mento mínimo existe, pois os testes são uma espécie de especificação sobre o que o sistema deve fazer (ótima fonte de documentação para as APIs do siste-ma, por sinal). uma vez que surja a necessidade de al-terar a implementação, então é natural que os testes precisem ser alterados para refletir o novo comporta-mento esperado. É a ideia da “engrenagem”: os dois (código de teste e de produção) funcionam juntos e se validam mutuamente. Às vezes, inclusive, o código de produção ajuda a descobrir problemas no código de teste!

De qualquer forma, o nível de acoplamento nos testes também depende de como eles são

(4)

implemen-tados. Por exemplo, testes de unidade que fazem uso extensivo de mock objects para validar interações es-peradas de uma classe com suas dependências ten-dem a ter um acoplamento desnecessariamente mais alto.

Criar testes automatizados não é

trabalho para os testadores ou para a

equipe de Q.A.?

Para começar, muitas empresas não possuem a famosa equipe de “qualidade” (Quality Assurance ou

Q.A.) ou uma equipe de testadores. Neste caso ou no

caso em que a equipe use TDD, não resta outra opção. É trabalho para a equipe de desenvolvimento. Para as empresas que possuem pessoas dedicadas a testes, uma boa maneira de trabalhar poderia ser da seguin-te forma:

» Os testadores, a partir dos requisitos, elaboram os casos de testes para uma determinada fun-cionalidade;

» Os desenvolvedores implementam a funciona-lidade e seus testes [ou vice-versa], a partir dos cenários fornecidos pela equipe de teste; » Com a funcionalidade concluída, os testadores

realizam testes exploratórios a partir da inter-face gráfica do sistema, à procura de eventuais vulnerabilidades ou defeitos que possam não haver sido cobertos pelos cenários de testes criados inicialmente.

Em relação à automatização dos testes, se a equi-pe de testadores também possui conhecimento de desenvolvimento, também é possível utilizá-la para automatizar os testes de aceitação. Estes são testes que simplesmente especificam como o sistema deve se comportar do ponto de vista do usuário externo. Quando se usa xP, normalmente usa-se a termino-logia given-when-then para especificar esses testes (dado x quando y então z). Esses testes podem ser in-clusive definidos pelo próprio cliente ou especialis-ta de domínio. Normalmente, os testes de aceiespecialis-tação automatizados são realizados via interface gráfica ou na camada imediatamente inferior. Também é pos-sível aplicar TDD neste nível mais macro dos testes de aceitação, normalmente usando técnicas como ATDD (Acceptance Test-Driven Development) ou BDD (Behavior-Driven Development), que são de uso mais recente na indústria (FIT e JBehave são ferramentas que têm sido usadas com esse propósito).

Para complementar a discussão dos tipos de tes-tes, é interessante darmos uma olhada nos chamados “Quadrantes dos Testes Ágeis”, exibidos na figura 2. Esses quadrantes foram apresentados no livro “Agile Testing”, de Lisa Crispin e Janet Gregory.

No desenvolvimento de software “tradicional”, os testes se concentram mais nos quadrantes do lado direito (3 e 4), ou seja, possuem foco maior no

produ-to (qualidade externa) e são realizados manualmente (ainda que com o suporte de algumas ferramentas). Em especial, observe que, no quadrante 4, situam-se os testes de características não funcionais do produto (desempenho, disponibilidade, escalabilidade, etc.), os quais são aspectos não endereçados por testes de unidade e são de mais difícil automatização.

No lado esquerdo da figura, encontram-se os testes importantes para promover também a quali-dade interna do software e uma maior aderência do sistema a seus requisitos de negócio. No quadrante 1, estão localizados os testes que suportam o desen-volvimento (testes de unidade e integração), ao passo que temos, no quadrante 2, a interseção entre os tes-tes que suportam o desenvolvimento e os tes-testes-tes de aceitação. Vale ressaltar que todas as facetas de testes presentes nos quatro quadrantes possuem a sua im-portância.

E o TDD? Será que vale a pena?

Nesta seção, discutiremos alguns aspectos rela-cionados à TDD e ao tema de testes automatizados. usaremos como auxílio alguns resultados obtidos por estudos empíricos na área.

TDD e Design

Mais uma vez: TDD é sobre design. A suíte de tes-tes automatizados resultante é um excelente efeito

colateral. Quando se programa test-first, o

desenvol-vedor é forçado a pensar sobre as interfaces do siste-ma do ponto de vista do cliente, ou seja, de quem vai usar aquele código. Essa simples mudança de foco faz com que sejam criadas interfaces que revelam mais claramente a sua intenção (o foco recai mais em “o quê?” e ”por quê?” em vez de “como?” as coisas de-vem ser feitas). Além disso, para que o teste seja de unidade, torna-se necessário isolar as dependências por meio de injeção de dependências, resultando em um design com baixo nível de acoplamento.

O interessante é que essas decisões são feitas e

(5)

manifestadas através de código de teste antes que o código de produção seja escrito. Essa ideia de escre-ver código como se outro pedaço de código existisse também é conhecida como programação por

inten-ção. A figura 3 ilustra esse conceito por meio de um

exemplo trivial de teste escrito para uma calculadora financeira. Repare a quantidade de decisões de

de-sign tomadas apenas para escrever esse simples teste.

Note também que as partes sublinhadas em vermelho referem-se a código que ainda não existe (i.e. classes

Calculadora e CotadorMoeda, método converterPara-Reais etc.).

Tudo bem que TDD é uma excelente ferramenta de design. O que muitos não entendem é que TDD não é um substituto para as disciplinas de arquitetura e

design, como comenta uncle Bob em seu post “TDD

Triage” (ver referências). Existem decisões arquite-turais que precisam ser feitas antes que se comece qualquer codificação. Por exemplo, qual linguagem de programação será usada, qual banco de dados será usado, como a aplicação será distribuída... Ob-viamente, quanto mais essas decisões puderem ser adiadas, melhor. Tudo depende do ambiente e da or-ganização.

Em termos de design, continua sendo essencial saber criar modelos, conhecer os bons princípios, práticas e padrões de projeto. É saudável e bastante recomendável (eu diria até que é o ponto de parti-da) discutir soluções com a equipe antes de começar a codificar, seja apenas numa conversa informal ou por meio de um esboço de diagrama de classes numa folha de papel ou quadro branco... O papel do TDD será validar a decisão de design já realizada.

Esse tipo de abordagem é benéfico e recomen-dável, porém, lembre-se que deve ser feita num con-texto iterativo, considerando-se apenas o pedaço de funcionalidade em questão, com base nos requisitos existentes – evitando o BDuF – Big Design Up-front. Estão na moda os termos design emergente e

arquite-tura evolucionária, cuja ideia é a de implementar

ape-nas o que se sabe que será necessário, balanceando a tensão entre as mudanças antecipadas e preparando--nos para as mudanças não antecipadas (ver série de artigos do Neal Ford no developerWorks e a edição 50 da MJ sobre Arquitetura Ágil).

Erros comuns que as pessoas cometem

Ao longo do aprendizado e amadurecimento no uso de TDD, muitos erros são cometidos pelos desen-volvedores. Mauricio Aniche, numa pesquisa on-line feita com 218 programadores, identificou diversos erros que as pessoas cometem ao praticar TDD (ou tentar praticar TDD). Vejamos alguns deles e como podemos tentar evitá-los:

» Falta de refatoração (no código de produção

e no código de teste) – refatoração contínua é passo fundamental para a manutenção do bom estado do sistema, e isso inclui o próprio có-digo de testes! Depois de obter a barra verde, veja se o código de produção e dos testes está legível, fácil de entender e manter. Elimine os

maus cheiros encontrados.

» Perda de foco com a refatoração – o outro

extremo é refatorar demais, inclusive outras partes do código que não estão diretamente relacionadas com a funcionalidade sendo de-senvolvida no momento. É importante manter o foco e deixar eventuais melhorias em outras áreas de código para um momento oportuno. » Escrita de cenários de testes muito complexos

– isto pode ser um sinal de que a unidade para a qual o teste está sendo escrito provavelmente está absorvendo muitas responsabilidades. O recomendável é que o teste foque em apenas um aspecto, de forma a gerar código coeso. É comum novos casos de testes virem à mente à medida que um teste está sendo escrito. Anote--os numa lista separada para implementação posterior. Comece também pelo teste mais simples, de forma que o código possa evoluir aos poucos. Também é importante verificar o valor que cada caso de teste está agregando e se não estão ocorrendo sobreposições entre testes (vários testes testando a mesma coisa). » Uso de nomes ruins para os testes – os nomes

dos métodos de teste devem descrever correta-mente o que o teste faz, não importa o tamanho do nome necessário para isso. Não se esqueça de que um dos benefícios dos testes automati-zados é o de documentar as APIs do sistema e, para isso, bons nomes são fundamentais! » Execução apenas do teste que está atualmente

falhando – nos diversos ciclos do TDD, muitas

vezes é comum a execução apenas da classe de teste ou, pior ainda, apenas do teste que está falhando. Não é raro descobrir mais tarde, ao rodar todos os testes, que outros cenários de testes deixaram de passar. Lembre-se de execu-tar todos os testes de unidade frequentemente! Eu diria que outro erro que as pessoas cometem bastante é dizer que estão usando TDD só porque es-tão programando com testes automatizados. É como

Figura 3. Decisões de design feitas no estilo Test-first (TDD).

(6)

alguém disse: “TDD é a prática ágil mais citada, mas a menos usada”.

Qualidade

Vejamos o que alguns estudos falam acerca do impacto de TDD na qualidade dos sistemas produ-zidos. Grosso modo, existem dois eixos de qualidade a serem considerados: a qualidade interna e a qua-lidade externa. A primeira diz respeito a código em si e a última normalmente esta associada à taxa de defeitos encontrados no software. Obviamente, essas duas facetas da qualidade estão inter-relacionadas e são afetadas pelo TDD. No entanto, como já frisamos, o foco do TDD é em design e, por conseguinte, há uma maior preocupação com a qualidade interna.

Num experimento conduzido em dois diferentes ambientes da Microsoft, foi observado um aumento de qualidade significativo do código (mais que duas vezes) para projetos desenvolvidos com TDD compa-rados a projetos similares não-TDD (mas com testes feitos depois) [2]. Os autores também relataram 15% de tempo extra up-front para a escrita dos testes. O mesmo artigo menciona outro paper da IBM relativo a um estudo realizado por um ano em que se verifica-ram 40% menos defeitos que os encontrados em um produto similar desenvolvido de uma forma mais tra-dicional. Segundo eles, a produtividade do time não foi afetada pela necessidade de escrita dos testes [3].

A maioria dos estudos indica que existe melhoria significativa de qualidade em termos de redução do número de defeitos nos sistemas produzidos [4]. Nos estudos realizados, chegou-se a verificar uma redu-ção de bugs de 40 a 50%.

Em termos de qualidade interna, um estudo de

caso comparando diferentes projetos open-source, desenvolvidos com TDD e sem TDD, indicou que projetos desenvolvidos com TDD de fato obtiveram melhor qualidade, traduzida em menor acoplamento, classes menores e maior testabilidade [5]. No entan-to, houve um reporte negativo em termos de coesão, que, como admitem os autores, é uma métrica difícil de medir. No experimento, eles utilizaram o método de Brian-Henderson-Seller conhecido como LCOMS (Lack of Cohesion of Methods), que é uma das muitas técnicas cuja ideia é analisar o grau de compartilha-mento de variáveis de instância entre métodos de uma classe (ver referências do artigo para mais de-talhes).

Produtividade

Vários estudos não relatam diferenças significa-tivas ou qualquer diferença em termos de produtivi-dade [4, 7, 8], ao passo que outros dizem ter observa-do alguns desvios.

Boby George e Laurie Williams relatam que ob-servaram menor produtividade [1]. Os experimentos que fizeram mostraram que os desenvolvedores que usaram TDD levaram 16% mais tempo que os que não usaram. Apesar disso, os próprios autores afir-mam que a discrepância em desempenho dos times era grande e o grupo não-TDD praticamente não de-senvolveu nenhum teste automatizado que agregas-se valor, o que realmente torna o resultado bastante questionável. Ademais, os experimentos foram feitos com base em uma aplicação de tamanho pouco sig-nificativo (cerca de 200 linhas de código) e com um grupo relativamente pequeno de desenvolvedores (12 duplas).

Outros estudos, como o destacado na subseção seguinte, relatam aumento de produtividade em comparação com abordagens do tipo test-last. Kau-fmann and Janzen observaram um aumento de 50% em produtividade num estudo em meio acadêmico [6].

Segundo Hakan Erdogmus, o esforço para codi-ficar os testes é menor quando o código de produção é de natureza mais algorítmica [10] (e.g.: calcular os juros compostos de um financiamento até uma data, ordenar uma lista de coisas segundo algum critério, achar o maior elemento de uma lista etc.).Neste tipo de código, uma fração bem maior do tempo é gasta implementando-se a funcionalidade em si (o algorit-mo) e pouco esforço tende a ser gasto para se escrever os testes (normalmente tem-se uma entrada e uma saída bem definidos, obtidos diretamente da formu-lação matemática do problema). Já quando o código de produção está centrado em lógica de domínio ou aspecto de negócio, o esforço despendido nos testes tende a ser maior, algumas vezes inclusive superior ao esforço para se implementar a funcionalidade

“Arquitetura Ágil” – MJ50

“Cinto de Utilidades: Testes Automatizados” – MJ 47 “Behavior-driven Development em Java na prática, com JBehave” – MJ 44

“Powermock: Melhorando seu relacionamento com código legado” – MJ 43

“A cultura TDD e o BDD” – MJ 43

“Evolução do Design através de Testes e o TDD” – MJ 41 “Automatização de testes de persistência com FIT, DBUnit e HSQLDB” – MJ 38

“Testes de Unidade para Camadas de Persistência no Mundo Real” – MJ 24

“Testes Unitários para Camadas de Negócios no Mundo Real“ – MJ 23

(7)

(existe mais refatoração envolvida também).

Na minha experiência, normalmente se gasta um tempo grande para criar a infraestrutura básica para escrever os primeiros testes. uma vez estabelecida essa infraestrutura, o tempo necessário para criar no-vos casos de testes do mesmo tipo tende a ser menor. Alguns chamam essa prática de “Arquitetura de Tes-tes”, cuja ideia é justamente investir um tempo inicial definindo como o teste de cada componente da arqui-tetura deverá ser realizado. Isso facilita a criação de novos testes, principalmente para os que ainda não sabem criar testes ou possuem pouca experiência. Steve Freeman e Nat Pryce recomendam que sejam escritos testes para um “walking skeleton” (esqueleto

ambulante) – uma implementação básica grosseira da

arquitetura do sistema. Esta técnica pode ser útil no início do projeto tanto para a realização das macro--decisões de arquitetura quanto para a definição de uma boa infraestrutura de testes.

Voltando ao TDD, é oportuno um comentário a respeito do tema produtividade. Nesses estudos, nor-malmente se comparam grupos que usaram TDD com grupos que deixaram os testes automatizados para o final do desenvolvimento (test-last). Agora, quando se compara o esforço de desenvolvimento de uma fun-cionalidade com teste automatizado (seja test-first ou

test-last) com a mesma funcionalidade sem qualquer

teste automatizado, num curto prazo, o tempo de de-senvolvimento da equipe que faz testes normalmente é maior. Isto é, ocorre uma aparente “queda” na pro-dutividade. Num longo prazo, porém, esse esforço é compensado devido à qualidade do código produzi-do com testes e a boa suíte de testes de regressão, os quais fazem com que o desenvolvimento das pró-ximas funcionalidades ocorra de maneira facilitada.

Em outras palavras, consegue-se um ciclo sustentável de desenvolvimento.

Test-first vs. Test-last

Analisemos agora mais de perto as diferenças en-tre as abordagens test-first (teste antes da implemen-tação, como dita o TDD) e test-last (escrever os testes depois da implementação).

Erdogmus, Morisio e Torchiano resolveram in-vestigar, por meio de um experimento descrito no pa-per “On the Effectiveness of the Test-First Approach to

Programming” [9,] as diferenças entre as abordagens test-first (teste antes da implementação, como dita

o TDD) e test-last (escrever os testes depois da im-plementação). O experimento foi realizado com dois grupos em meio acadêmico, cada qual usando uma das duas abordagens (test-first e test-last). As con-clusões foram as seguintes: o grupo test-first acabou escrevendo 52% mais testes do que o grupo test-last, algo bastante significativo. Em termos de qualidade externa, pelo estudo, não pareceu haver diferenças significativas entre as duas abordagens.

Na verdade, houve evidências de que a qualidade externa estaria mais relacionada ao número de testes escritos do que quando estes são escritos. Segundo os autores, apesar de a abordagem test-first por si só não aumentar a qualidade (em comparação com a abor-dagem test-last), esta pareceu ter um efeito positivo sobre a produtividade dos desenvolvedores, possivel-mente por causa dos seguintes fatores:

» Melhor compreensão do problema. Especificar o teste antes força o desenvolvedor a refletir de maneira mais profunda e completa sobre o problema a ser resolvido. Criar cenários de tes-tes antes-tes ajuda a “provar” a robustez da solução

Papers referenciados no artigo

[1] – “An Initial Investigation of Test-Driven Development in Industry” – Boby George e Laurie Williams

[2] – “Evaluating the efficacy of Test-Driven Development: Industrial Case Studies” – Thirumalesh Bhat e Nachiappan Nagappan [3] – “Test-Driven Development as a Defect-Reduction Practice” - Laurie Williams, E. Michael Maximilien e Mladen Vouk [4] – “Test-Driven Development – Empirical Body of Evidence” – Maria Siniaalto

[5] – “Does Test-Driven Development really improve software design quality?” – David Janzen e Hossein Saiedian

[6] - “Implications of test-driven development: a pilot study” -

Kaufmann and Janzen

[7] - “Towards Empirical Evaluation of Test-Driven Development in a University Environment” - Pancur et. al.

[8] – “An empirical evaluation of the impact of Test-Driven Development on software quality” – David Janzen [9] – “On the Effectiveness of the Test-First Approach to Programming” - H. Erdogmus, M. Morisio, and M. Torchiano [10] – “An Overview of Test-Driven Development” - H. Erdogmus [11] – “Iterative and Incremental Development: A Brief History” - Craig Larman, Victor R. Basili.

Outras referências

“Test-driven – Practical TDD and Acceptance TDD for Java

(8)

proposta.

» Melhor foco na tarefa a ser feita. Existe uma menor carga cognitiva sobre o desenvolvedor, pois ele estará concentrado em resolver apenas uma pequena porção do problema, somente o suficiente para atender o teste existente fa-lhando.

» Aprendizado mais rápido. O desenvolvedor saberá mais rapidamente se a funcionalidade implementada está de acordo com o esperado. Além disso, existe um critério inequívoco para saber quando o trecho de funcionalidade está realmente pronto.

» Menos esforço de retrabalho. Numa abordagem test-last, corre-se o risco de “ir com muita sede ao pote” e implementar de uma vez a funcio-nalidade, incorrendo em retrabalho à medida que os testes forem criados posteriormente e apontarem outros problemas.

O que o estudo mostrou é que o TDD acaba por encorajar a escrita de um número maior de testes e, se a qualidade externa pode ser influenciada pela quantidade de testes, então, em última análise, o TDD também desencadearia um aumento da quali-dade. Seria interessante vermos novos estudos que pudessem consubstanciar esse fato.

Afinal de contas, vale a pena ou não

usar TDD?

Neste ponto, o leitor deve estar um pouco con-fuso, pois os diversos estudos parecem obter resulta-dos contraditórios, principalmente quanto à questão qualidade/produtividade com e sem o uso de TDD. Bom, sabemos que todos esses estudos possuem

apli-cabilidade limitada, pois muitos deles foram reali-zados apenas em meio acadêmico (que obviamente é bastante diferente da indústria), com desenvolve-dores com diferentes perfis e projetos de naturezas diferentes. Por essas questões, é bastante difícil obter conclusões mais concretas por meio de experimentos comparativos. Afinal de contas, vale a pena usar TDD ou uma abordagem test-last é suficiente?

É perfeitamente possível escrever código desa-coplado apenas pensando na testabilidade. Com o tempo, é possível fazer isso suficientemente bem, basicamente empregando o padrão de injeção de de-pendências, em sistemas orientados a objeto. Nor-malmente, não há maiores dificuldades para se es-crever os testes imediatamente depois (note o uso da palavra “imediatamente”). Em outros tipos de ambientes onde o desenvolvedor esteja menos fami-liarizado, o recomendável é que o teste seja escrito antes, pois a refatoração posterior poderá ser muito custosa ou inviável.

Os problemas da abordagem test-last são diver-sos: primeiro, perde-se o benefício da programação por intenção, ou seja, evoluir o software a partir do ponto de vista de como se espera que seja usado. Se-gundo, corre-se o risco de over-engineering, isto é, es-crever código para coisas que talvez não sejam neces-sárias. Terceiro, corre-se o risco de que simplesmente os testes não sejam escritos, afinal, o código já está pronto e aparentemente funciona bem (como ocorreu em um dos estudos que referenciamos). Por último, o número de testes tende a ser menor (como mostra-ram alguns estudos) e, consequentemente, a cober-tura dos testes tende a ser menor (numa abordagem

test-last, é fundamental utilizar uma ferramenta de

cobertura, como o EclEmma ou o Cobertura, para

Developers” – Lasse Koskela

“Growing Object-Oriented Software, Guided by Tests” – Steve Freeman, Nat Pryce

“Test-driven Development: By Example” – Kent Beck

- “Pragmatic Unit Testing in Java with JUnit” – Andy Hunt e Dave Thomas

- “Agile Testing” – Lisa Crispin and Janet Gregory - “Extreme Programming” – Vinicius Teles

- http://www.ebah.com.br/content/ABAAABNfUAG/conceitos-beneficios-tdd

- Chaplin, D. “Test first programming”, TechZone, 2001. - http://alistair.cockburn.us/Walking+skeleton - http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd - http://blog.objectmentor.com/articles/2009/10/06/echoes-from-the-stone-age - http://blog.objectmentor.com/articles/2009/10/08/tdd-triage - http://codebetter.com/darrellnorton/2004/05/10/notes-from-test-driven-development-by-example-kent-beck/ - http://blog.jonasbandi.net/2010/02/agile-testing-quadrants.html - http://haacked.com/archive/2008/01/22/research-supports-the-effectiveness-of-tdd.aspx - http://gsd.ime.usp.br/seminars/2010/presentation_mauricio.pdf - http://evidencebasedse.com/?q=node/90 - http://www.infoq.com/news/2009/03/TDD-Improves-Quality - http://www.ibm.com/developerworks/java/library/j-eaed1/index. html

(9)

identificar quais partes significativas do código pro-duzido não possuem testes).

O tamanho dos passos no TDD também é um as-pecto que pode ser decidido pelo desenvolvedor. O que se recomenda é que sejam utilizados pequenos passos (baby-steps). Por exemplo, no primeiro passo após o teste, que consiste em aplicar a implementa-ção mais simples para que o teste passe, muitos usam a técnica da falsificação (“fake it”), retornando um valor hard-coded e, em seguida, acrescentam mais um teste que force à generalização da implementa-ção. Essa é uma abordagem de baby-steps, mas nada impede que o desenvolvedor codifique de uma vez a funcionalidade para que o teste passe.

Acredito que seja possível utilizar de maneira eficaz uma abordagem híbrida, não sendo tão radical em aplicar estritamente as “três leis do TDD” men-cionadas no início. O próprio uncle Bob afirma que TDD nem sempre é apropriado e existem situações nas quais ele quebra a disciplina e escreve código an-tes dos an-tesan-tes (apesar de serem poucas as vezes). Ou-tro exemplo de variação que alguns desenvolvedores adotam consiste em, dependendo da funcionalidade, escrever mais de um teste (dois ou três) que falhe antes de realizar a implementação. A questão é que

o benefício maior está em haver uma alternância entre código de testes e código de produção, não importando tanto a ordem.

Como com qualquer metodologia, prática ou tec-nologia, é importante o conhecimento e a aplicação de maneira contextual, entendendo os reais benefícios e realizando as adaptações que se fizerem necessárias. Note que mencionei a questão do conhecimento. Mui-tos consideram que é cusMui-toso ou inviável usar TDD ou testes automatizados, simplesmente porque não tomaram o tempo necessário para estudar e aprender as técnicas e ferramentas necessárias e aplicá-las de maneira efetiva (sobre esse tema, recomendo meu ar-tigo “Cinto de utilidades: Testes Automatizados”, na MJ 47). Como curiosidade, vale a observação de que a filosofia por trás do TDD não é nova. Seu uso esteve presente no projeto Mercury da NASA, de 1960! [11]

Considerações finais

A motivação para este artigo veio de uma palestra a que assisti em 2009 intitulada “Test-Driven

Develo-pment: Overview and Empirical Results” (“TDD: Visão

geral e resultados empíricos”), apresentada pelo Dr. Hakan Erdogmus, editor-chefe da IEEE Software, que na ocasião estava visitando a uFRJ. Segundo ele, a efetividade do TDD não é comprovada, porém, exis-tem evidências da sua efetividade, apesar de ainda não haver nada definitivo em termos da análise qua-lidade/produtividade. Para ele, o próprio nome TDD não teria sido uma boa escolha. Em suas palavras: “TDD incorpora e mistura atividades associadas

tra-dicionalmente com diferentes ‘fases’: requisitos, design, implementação, documentação e testes”.

Em termos subjetivos, porém, normalmente os que aplicam TDD (ou dizem aplicar) possuem uma percepção positiva. Numa pesquisa realizada com 24 desenvolvedores profissionais, em média, 80% afir-maram que consideram TDD uma abordagem eficaz e 78% acreditam que haja melhora em produtividade. 87,5% declaram que TDD contribui para um melhor entendimento dos requisitos e 95,8% dizem que TDD reduz o esforço em debugging. Apesar disso, 56% dos desenvolvedores consideram difícil pensar “do jeito TDD” [1]. A figura 4 exibe os resultados da pesquisa em formato gráfico.

É fato que o TDD exige uma mudança de paradig-ma bastante radical ao que tradicionalmente fomos condicionados. Fomos educados a desenvolver sof-tware com o pensamento design-codificação-testes. Mas o TDD inverte esse ciclo para testes-codificação--design. Não é fácil aprender a pensar com base nessa outra perspectiva, mas acredito que valha a pena ex-perimentar. E como alguém disse: “se você não con-segue escrever teste para o que você está prestes a codificar, então você nem deveria estar pensando em codificar!”.

Para concluir, reitero a importância da criação dos testes automatizados, preferencialmente de ma-neira intercalada com o código de produção sendo desenvolvido (seja de forma TDD ou não-TDD). Sof-tware é feito para mudar (o lema do xP é o de “abra-çar a mudança”, lembra-se?) e, para isso, é de suma importância a existência de um bom conjunto de tes-tes automatizados. Aprenda as técnicas e ferramen-tas relacionadas a testes. Experimente o TDD. Adapte e use o que funcionar melhor para você, mas busque conhecer a fundo a técnica e os princípios que a mo-tivam e suportam.

Não tenha tanta confiança assim no seu código. Prove que funciona!

“O crisol prova a prata, e o forno, o ouro; mas aos corações prova o Senhor.” (Pv 17:3)

Referências

Documentos relacionados

A diferença é que o certificado NF-e pode ser emitido em nome de um funcionário, ficando apenas para emissão de Nota Fiscal, sem acesso aos demais dados da empresa. Já o e-CNPJ

A pesquisa “Estratégias para o varejo brasileiro – Reflexões sobre os anseios do consumidor” realizada pela Deloitte em 2010 apresentou de forma muito objetiva o que

Como profissional de marketing digital, você precisa da permissão de um consumidor, cliente ou visitante do site para enviar a eles qualquer forma de comunicação que

E) CRIE NO SEU CADERNO UM TÍTULO PARA ESSA HISTÓRIA EM QUADRINHOS.. 3- QUE TAL JUNTAR AS SÍLABAS ABAIXO PARA FORMAR O NOME DE CINCO SUGESTÕES DE PRESENTE PARA O DIA

Processo de se examinar, em conjunto, os recursos disponíveis para verificar quais são as forças e as fraquezas da organização.

É também utilizado, juntamente com outros fármacos, para tratar o cancro da mama que se disseminou, o cancro dos ovários em estadio avançado e o cancro do pulmão de células

Os radicais livres podem contribuir para o surgimento de alguns problemas de saúde, como o enfraquecimento do sistema imunológico e o envelhecimento, bem como de distúrbios

Pelo fato de a administração possuir o ser humano como um de seus centros de atenção, ela necessita buscar conceitos de compreensão com base em diversas ciências, e o