Você está na página 1de 215

TDD

DESENVOLVIMENTO
GUIADO POR TESTES
KENT BECK
Sobre o autor
Um dos mais criativos e aclamados lderes da indstria de software, Kent Beck
passionalmente emprega padres, XP e TDD. filiado ao Three Rivers Institute,
ele o autor dos livros Smalltalk Best Practice Patterns (Prentice Hall, 1997),
Programao Extrema Explicada (Bookman, 2004) e Planning Extreme Program-
ming (com Martin Fowler, Addison-Wesley, 2001), e colaborador em Refatorao
(de Martin Fowler, Bookman, 2004).

B393t Beck, Kent.


TDD desenvolvimento guiado por testes [recurso eletrnico]
/ Kent Beck ; traduo: Jean Felipe Patikowski Cheiran ; reviso
tcnica: Marcelo Soares Pimenta. Dados eletrnicos. Porto
Alegre : Bookman, 2010.

Editado tambm como livro impresso em 2010.


ISBN 978-85-7780-747-5

1. Cincia da computao. 2. TDD Tcnica de


desenvolvimento de software. I. Ttulo.

CDU 004.42

Catalogao na publicao: Ana Paula M. Magnus CRB-10/Prov-009/10

Iniciais_Eletronico.indd ii 7/8/10 1:45:45 PM


Traduo:
Jean Felipe Patikowski Cheiran

Consultoria, superviso e reviso tcnica desta edio:


Marcelo Soares Pimenta
Doutor em Informtica pela Universit Toulouse1/Frana
Professor do Departamento de Informtica/UFRGS

Verso impressa
desta obra: 2010

2010

Iniciais_Eletronico.indd iii 6/24/10 11:53:55 AM


Obra originalmente publicada sob o ttulo Test Driven Development: By Example, 1st Edition
ISBN 978-0-321-14653-3

Authorized translation from the English language edition, entitled TEST DRIVEN DEVELOP-
MENT: BY EXAMPLE, 1st Edition by KENT BECK, published by Pearson Education,Inc., pu-
blishing as Addison-Wesley Professional, Copyright 2003. All rights reserved. No part of this
book may be reproduced or transmitted in any form or by any means, electronic or mechanical,
including photocopying, recording or by any information storage retrieval system, without per-
mission from Pearson Education,Inc.

Portuguese language edition published by Bookman Companhia Editora Ltda, a Division of Art-
med Editora SA, Copyright 2010.

Traduo autorizada a partir do original em lngua inglesa da obra intitulada TEST DRIVEN
DEVELOPMENT: BY EXAMPLE, 1 Edio de autoria de KENT BECK, publicado por Pearson
Education, Inc., sob o selo Addison-Wesley Professional, Copyright 2003. Todos os direitos
reservados. Este livro no poder ser reproduzido nem em parte nem na ntegra, nem ter partes ou
sua ntegra armazenado em qualquer meio, seja mecnico ou eletrnico, inclusive fotoreprografa-
o, sem permisso da Pearson Education,Inc.

A edio em lngua portuguesa desta obra publicada por Bookman Companhia Editora Ltda,
uma diviso da Artmed Editora SA, Copyright 2010.

Capa: Rogrio Grilho (arte sobre capa original)

Preparao de original: Leonardo Zilio

Editora Snior: Denise Weber Nowaczyk

Projeto e editorao: Techbooks

Reservados todos os direitos de publicao, em lngua portuguesa,


ARTMED EDITORA S.A.
(BOOKMAN COMPANHIA EDITORA uma diviso da ARTMED EDITORA S.A.)
Av. Jernimo de Ornelas, 670 - Santana
90040-340 Porto Alegre RS
Fone (51) 3027-7000 Fax (51) 3027-7070

proibida a duplicao ou reproduo deste volume, no todo ou em parte,


sob quaisquer formas ou por quaisquer meios (eletrnico, mecnico, gravao,
fotocpia, distribuio na Web e outros), sem permisso expressa da Editora.

SO PAULO
Av. Embaixador Macedo Soares, 10.735 - Pavilho 5 - Cond. Espace Center
Vila Anastcio 05095-035 So Paulo SP
Fone (11) 3665-1100 Fax (11) 3667-1333

SAC 0800 703-3444

IMPRESSO NO BRASIL
PRINTED IN BRAZIL
Para Cindee: suas prprias asas
Agradecimentos

Obrigado a todos os meus muitos revisores poderosos. Eu assumo total responsabi-


lidade pelo contedo, mas este livro teria sido muito menos legvel e muito menos
til sem a ajuda deles. Na ordem que os digitei, eles so: Steve Freeman, Frank
Westphal, Ron Jeffries, Dierk Knig, Edward Hieatt, Tammo Freese, Jim Newki-
rk, Johannes Link, Manfred Lange, Steve Hayes, Alan Francis, Jonathan Rasmus-
son, Shane Clauson, Simon Crase, Kay Pentecost, Murray Bishop, Ryan King, Bill
Wake, Edmund Schweppe, Kevin Lawrence, John Carter, Phlip, Peter Hansen, Ben
Schroeder, Alex Chaffee, Peter van Rooijen, Rick Kawala, Mark van Hamersveld,
Doug Swartz, Laurent Bossavit, Ilja Preu, Daniel Le Berre, Frank Carver, Justin
Sampson, Mike Clark, Christian Pekeler, Karl Scotland, Carl Manaster, J. B. Rains-
berger, Peter Lindberg, Darach Ennis, Kyle Cordes, Justin Sampson, Patrick Logan,
Darren Hobbs, Aaron Sansone, Syver Enstad, Shinobu Kawai, Erik Meade, Patrick
Logan, Dan Rawsthorne, Bill Rutiser, Eric Herman, Paul Chisholm, Asim Jalis,
Ivan Moore, Levi Purvis, Rick Mugridge, Anthony Adachi, Nigel Thorne, John
Bley, Kari Hoijarvi, Manuel Amago, Kaoru Hosokawa, Pat Eyler, Ross Shaw, Sam
Gentle, Jean Rajotte, Phillipe Antras, Jaime Nino e Guy Tremblay.
A todos os programadores com quem tenho codificado guiado por testes, eu
certamente aprecio sua pacincia compactuando com o que era uma ideia que so-
ava maluca, especialmente nos primeiros anos. Eu aprendi muito mais com todos
vocs do que eu poderia pensar. No querendo ofender os demais, mas Massimo
Arnoldi, Ralph Beattie, Ron Jeffries, Martin Fowler e, por ltimo, mas certamente
no menos importante, Erich Gamma se destacam em minha memria como con-
dutores de testes com os quais aprendi muito.
Eu gostaria de agradecer a Martin Fowler pela ajuda oportuna do Frame-
Maker. Ele deve ser o consultor tipogrfico mais bem pago do planeta, mas feliz-
mente me deixou (at agora) pendurar a conta.
Minha vida como um programador de verdade comeou como discpulo e
colaborador de Ward Cunningham. s vezes, eu vejo Desenvolvimento Guiado
por Testes (TDD) como uma tentativa de dar a qualquer engenheiro de software,
viii Agradecimentos

trabalhando em qualquer ambiente, a sensao de conforto e intimidade que tive-


mos com nosso ambiente Smalltalk e nossos programas Smalltalk. No h formas
de separar a fonte de ideias, uma vez que duas pessoas dividam um crebro. Se
voc assume que todas as boas ideias aqui so do Ward, ento no estar muito
errado.
um pouco clich reconhecer o sacrifcio de uma famlia quando um dos
seus membros assume a peculiar aflio mental que resulta em um livro. Isso por-
que os sacrifcios da famlia so to necessrios para a escrita de um livro como
o papel. s minhas crianas, que esperaram o caf da manh at que eu terminasse
um captulo e, acima de tudo, minha esposa que passou dois meses repetindo
tudo trs vezes, meus mais profundos e adequados agradecimentos.
Obrigado a Mike Henderson pelo encorajamento gentil e a Marcy Barnes por
correr para me socorrer.
Finalmente, ao autor desconhecido de um livro que li quando era um garoto
esquisito de 12 anos, que sugeriu digitar na fita de sada esperada de uma fita de
entrada real, ento codificar at que os resultados reais correspondessem ao resul-
tado esperado, obrigado, obrigado, obrigado.
Prefcio

Cdigo limpo que funciona, em uma frase concisa de Ron Jeffries, o objetivo do
Desenvolvimento Guiado por Testes (TDD). Cdigo limpo que funciona uma
meta valiosa por um bocado de razes.

uma forma previsvel de desenvolver. Voc sabe quando acabou sem ter
que se preocupar com uma longa trilha de erros.
D a voc uma chance de aprender todas as lies que o cdigo tem para
ensinar. Se voc apenas fizer s pressas a primeira coisa que pensar, ento
nunca ter tempo para pensar em uma segunda coisa melhor.
Melhora as vidas dos usurios de seu software.
Permite que seus colegas de equipe contem com voc, e voc com eles.
bom escrev-lo.

Mas como obtemos cdigo limpo que funciona? Muitas foras nos desviam
de cdigo limpo, ou mesmo de cdigo que funciona. Sem pedir conselhos aos nos-
sos medos, aqui est o que fazemos: conduzimos o desenvolvimento com testes
automatizados, um estilo de desenvolvimento chamado Desenvolvimento Guiado
por Testes (TDD). No Desenvolvimento Guiado por Testes,

Escrevemos cdigo novo apenas se um teste automatizado falhou


Eliminamos duplicao

Essas so duas regras simples, mas geram um complexo comportamento in-


dividual e de grupo com implicaes tcnicas, tais como:

Devemos projetar organicamente com cdigo, executando e fornecendo


feedback entre as decises.
x Prefcio

Devemos escrever nossos prprios testes, pois no podemos esperar 20


vezes por dia para outra pessoa escrever um teste.
Nosso ambiente de desenvolvimento deve fornecer resposta rpida a pe-
quenas mudanas.
Nosso projeto deve consistir em muitos componentes altamente coesos e
fracamente acoplados para tornar os testes fceis.

As duas regras implicam em uma ordem para as tarefas de programao.

1. Vermelho Escrever um pequeno teste que no funcione e que talvez nem


mesmo compile inicialmente.
2. Verde Fazer rapidamente o teste funcionar, mesmo cometendo algum
pecado necessrio no processo.
3. Refatorar Eliminar todas as duplicatas criadas apenas para que o teste
funcione.

Vermelho/verde/refatorar o mantra do TDD.


Supondo por um momento que tal estilo de programao possvel, ain-
da pode-se reduzir significativamente a densidade de defeitos de cdigo e fazer o
tema de trabalho claro como cristal para todos os envolvidos. Se assim for, ento
escrever apenas aquele cdigo que requerido por testes que falham tambm tem
implicaes sociais.

Se a densidade de defeitos pode ser suficientemente reduzida, ento a ga-


rantia da qualidade (Quality Assurance QA) pode mudar de trabalho
reativo para trabalho pr-ativo.
Se o nmero de surpresas desagradveis pode ser suficientemente reduzi-
do, ento os gerentes de projeto podem estimar de forma precisa o bastan-
te para envolver clientes reais no desenvolvimento dirio.
Se os tpicos das conversas tcnicas passam a ser suficientemente claros,
ento os engenheiros de software podem trabalhar em uma colaborao
que acontece minuto-a-minuto em vez de diria ou semanalmente.
De novo, se a densidade de defeitos pode ser suficientemente reduzida, en-
to podemos ter software pronto com novas funcionalidades a cada dia,
levando a novos relacionamentos de negcios com clientes.

Ento o conceito simples, mas qual minha motivao? Por que um enge-
nheiro de software teria o trabalho adicional de escrever testes automatizados? Por
que um engenheiro de software trabalharia em passos to pequenos quando sua
cabea capaz de grandes feitos? Coragem.
Prefcio xi

Coragem
Desenvolvimento Guiado por Testes uma forma de administrar o medo durante
a programao. No quero falar de medo de uma forma ruim pow widdle prwo-
gwammew needs a pacifiew* , mas medo no sentido legtimo de esse--um-pro-
blema-difcil-e-eu-no-consigo-ver-o-fim-a-partir-do-comeo. Se dor o jeito da
natureza dizer Pare!, ento medo o jeito da natureza dizer tome cuidado.
Ser cuidadoso bom, mas o medo tem uma srie de outros efeitos.

O medo o faz hesitante.


O medo o faz querer comunicar-se menos.
O medo o faz afastar-se do feedback.
O medo o faz mal-humorado.

Nenhum desses efeitos til quando programamos, especialmente quando


programamos algo difcil. Ento a questo se torna como ns encaramos uma
situao difcil e,

Em vez de sermos hesitantes, comearmos a aprender concretamente to


rpido quanto possvel.
Em vez de nos calarmos, comunicar mais claramente.
Em vez de evitar o feedback, procurar feedback til e concreto.
(Voc ter de trabalhar seu mal-humor sozinho.)

Imagine que programar como girar uma manivela para puxar um balde
com gua de um poo. Quando o balde pequeno, uma manivela sem catraca
tima. Quando o balde grande e est cheio de gua, voc ficar cansado antes
do balde ser completamente puxado. Voc precisa de um mecanismo de catracas
para descansar entre as sries de maniveladas. Quanto mais pesado o balde, mais
prximos os dentes da catraca precisam estar.
Os testes no desenvolvimento guiado por testes so os dentes da catraca. Uma
vez que tenhamos um teste funcionando, sabemos que est funcionando agora e
para sempre. Estamos um passo mais prximo de termos tudo funcionando do que
estvamos quando o teste no funcionava. Agora fazemos o prximo funcionar, e
o prximo, e o prximo. Por analogia, quanto mais rduo o problema de progra-
mao, menor deve ser o terreno que cada teste deve cobrir.
Leitores do meu livro Programao Extrema Explicada notaro a diferena
de tom entre Programao Extrema (XP) e TDD. TDD no absoluta do jeito
que XP . XP diz: Aqui esto coisas que voc deve ser capaz de fazer para estar

* N. de R. T.: Uma descontrao do autor, a frase poor little programmer needs a pacifier est es-
crita no texto conforme o sotaque e modo de falar do Hortelino Troca-Letras (Elmer Fudd, em ingls),
personagem arqui-inimigo do Pernalonga no desenho animado da Looney Tunes. Na verso em ingls
do desenho, Hortelino fala Wabbit em vez de o correto Rabbit.
xii Prefcio

preparado para evoluir. TDD um pouco nebuloso. TDD uma conscincia da


lacuna entre deciso e feedback durante a programao, e tcnicas para controlar
essa lacuna. E se eu fizer um projeto em papel por uma semana, e ento testar o
cdigo? Isso TDD? Claro, TDD. Voc estava ciente da lacuna entre deciso e
feedback e controlou a lacuna deliberadamente.
Dito isso, a maioria das pessoas que aprendem TDD acham que suas prticas
de programao mudaram para melhor. Test Infected* a frase que Erich Gamma
cunhou para descrever essa mudana. Voc pode se encontrar escrevendo testes
mais cedo e trabalhando em passos menores como jamais sonhou ser sensato. Por
outro lado, alguns engenheiros de software aprendem TDD e ento voltam a suas
prticas anteriores, reservando TDD para ocasies especiais, naquelas em que a
programao comum no est fazendo progressos.
Certamente, h tarefas de programao que no podem ser guiadas somente
por testes (ou, ao menos, no ainda). Segurana de software e concorrncia, por
exemplo, so dois tpicos em que TDD insuficiente para demonstrar mecanica-
mente que as metas do software foram alcanadas. Embora seja verdade que segu-
rana depende essencialmente de cdigo livre de defeitos, ela tambm depende do
julgamento humano sobre os mtodos usados para tornar o software mais seguro.
Problemas sutis de concorrncia no podem ser reproduzidos de forma confivel
atravs da execuo do cdigo.
Depois de terminar de ler este livro, voc estar pronto para

Comear de forma simples


Escrever testes automatizados
Refatorar para adicionar uma deciso de projeto por vez

Este livro organizado em trs partes:

Parte I, O Exemplo Financeiro Um exemplo de cdigo modelo tipica-


mente escrito usando TDD. O exemplo um dos que eu obtive tempos
atrs com Ward Cunningham e que tenho usado muitas vezes desde en-
to: aritmtica multi-moeda. Esse exemplo permitir que voc aprenda a
escrever testes antes de cdigo e desenvolver um projeto organicamente.
Parte II, O Exemplo xUnit Um exemplo de teste mais complicado do
ponto de vista lgico, incluindo reflexo e excees, no desenvolvimento
de um framework para testes automatizados. Esse exemplo tambm o in-
troduzir arquitetura xUnit, que est no corao de muitas ferramentas
de teste orientadas ao programador. No segundo exemplo, voc aprender
a trabalhar em passos ainda menores do que no primeiro exemplo, incluin-
do o tipo de perturbao** amado pelos cientistas da computao.

* N. de R. T.: No Brasil, a expresso adequada poderia ser Mordido pelo Bicho do Teste.
** N. de R. T.: No original usada a expresso informal hoo-ha que significa tumulto, desordem,
distrbio e que preferimos traduzir por perturbao.
Prefcio xiii

Parte III, Padres para Desenvolvimento Guiado por Testes Esto inclu-
dos padres para decidir que testes escrever, como escrever testes usando
xUnit, e uma seleo de grandes sucessos dos padres de projeto e refato-
rao usados nos exemplos.

Eu escrevi os exemplos imaginando uma sesso de programao em pares. Se


voc gosta de olhar o mapa antes de sair andando, ento pode querer ir direto aos
padres na Parte III e usar os exemplos como ilustraes. Se voc prefere apenas
sair andando e ento olhar o mapa para ver aonde foi, ento tente ler atravs dos
exemplos, consultando os padres quando quiser mais detalhes sobre uma tcnica
e usando os padres como uma referncia. Muitos revisores deste livro comenta-
ram que obtiveram maior proveito dos exemplos quando abriram um ambiente de
programao, entraram com o cdigo e rodaram os testes como leram.
Uma observao sobre os exemplos. Os exemplos clculo multi-moeda e um
framework de teste parecem simples. Existem (e eu j vi) formas complicadas, feias
e confusas de resolver os mesmos problemas. Eu poderia ter escolhido uma dessas
solues complicadas, feias e confusas para dar ao livro um ar de realidade.
Entretanto, minha meta, e eu espero que a sua tambm, escrever cdigo limpo
que funcione. Antes de rotular os exemplos como sendo simples demais, gaste
15 segundos imaginando um mundo da programao no qual todos os cdigos
fossem limpos e diretos daquele jeito, onde no houvessem solues complicadas,
apenas problemas aparentemente complicados implorando por uma considerao
cuidadosa. TDD pode lev-lo exatamente at essa considerao cuidadosa.
Sumrio

Introduo 17

PARTE I O Exemplo Financeiro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21


Captulo 1 Dinheiro Multi-Moeda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Captulo 2 Degenerar Objetos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Captulo 3 Igualdade para Todos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Captulo 4 Privacidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Captulo 5 Falando Franca-mente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Captulo 6 Igualdade para Todos, Restaurada. . . . . . . . . . . . . . . . . . . . . . . 47
Captulo 7 Mas e Laranjas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Captulo 8 Fazendo Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Captulo 9 Tempos em que Estamos Vivendo . . . . . . . . . . . . . . . . . . . . . . . 59
Captulo 10 Tempos Interessantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Captulo 11 A Raiz de Todo o Mal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Captulo 12 Adio, Finalmente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Captulo 13 Faa-o . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Captulo 14 Mudana . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
16 Sumrio

Captulo 15 Moedas Misturadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93


Captulo 16 Abstrao, Finalmente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Captulo 17 Retrospectiva Financeira . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101

PARTE II O Exemplo xUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .109


Captulo 18 Primeiros Passos para xUnit . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Captulo 19 Iniciando . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Captulo 20 Limpando em Seguida . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Captulo 21 Contagem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Captulo 22 Lidando com Falha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
Captulo 23 Como a Sute?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
Captulo 24 Retrospectiva xUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

PARTE III Padres para Desenvolvimento Guiado por Testes . . . . . . .141


Captulo 25 Padres de Desenvolvimento Guiado por Testes . . . . . . . . . . . 143
Captulo 26 Padres de Barra Vermelha . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Captulo 27 Padres de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
Captulo 28 Padres de Barra Verde . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Captulo 29 Padres xUnit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Captulo 30 Padres de Projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
Captulo 31 Refatorao . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Captulo 32 Dominando TDD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213

Apndice I Diagramas de Influncia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227


Apndice II Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231

Posfcio 235
ndice 237
Introduo

Comeo de uma sexta-feira, o chefe de Ward Cunningham procurou-o para apre-


sentar Peter, um cliente potencial para WyCash, o sistema de gerenciamento de
investimentos de ttulos que a companhia estava vendendo. Peter disse: Estou
muito impressionado com a funcionalidade que eu vejo. Entretanto, percebi que
vocs lidam apenas com ttulos em dlar americano. Estou comeando um novo
fundo de ttulos e minha estratgia requer que eu lide com ttulos em moedas dife-
rentes. O chefe virou para Ward: Bem, ns podemos fazer isso?
Aqui est o cenrio de um pesadelo para qualquer projetista de software. Voc
estava seguindo feliz e bem-sucedido com um conjunto de suposies. De repente,
tudo mudou. E o pesadelo no apenas do Ward. O chefe, uma pessoa experiente
em desenvolvimento de software, no tinha certeza de qual resposta viria.
Um pequeno time havia desenvolvido WyCash ao longo de dois anos. O siste-
ma estava apto a lidar com a maioria dos tipos de ttulos de renda fixa comumente
encontradas no mercado norte-americano e tambm com alguns dos novos instru-
mentos, como Contratos de Investimento Garantido, com o qual os competidores
no lidavam.
WyCash tinha sido desenvolvido usando objetos e um banco de dados de
objetos. No comeo, a abstrao fundamental da computao, Dollar, fora ter-
ceirizada a um grupo qualificado de engenheiros de software. O objeto resultante
combinava responsabilidades de formatao e clculo.
Nos ltimos seis meses, Ward e o resto do time tinham lentamente descaracte-
rizado Dollar de suas responsabilidades. As classes numricas de Smalltalk acabaram
por ser apenas um peso no clculo. Todo o cdigo complicado para arredondamen-
to para trs dgitos decimais comeou como uma forma de produzir respostas preci-
sas. Quando as respostas se tornaram mais precisas, os mecanismos complicados no
framework de teste para comparao dentro de uma certa tolerncia foram substi-
tudos por uma correspondncia exata de resultados esperados e reais.
A responsabilidade por formatao pertencia de fato s classes de interface
com usurio. Como os testes foram escritos no nvel das classes de interface com
18 Introduo

usurio, em particular no framework de relatrio,1 esses testes no tinham de ser


alterados para contemplar tal refinamento. Depois de seis meses de poda cuidado-
sa, no tinham sido deixadas muitas responsabilidades para Dollar.
Um dos algoritmos mais complicados do sistema, mdia ponderada, tam-
bm tinha sofrido uma lenta transformao e havia muitas variaes de cdigo de
mdia ponderada espalhadas por todo o sistema. Como o framework de relatrio
formou-se de um conjunto inicial de objetos, era bvio que haveria um lugar para
o algoritmo em AveragedColumn.
Ento, foi para AveragedColumn que Ward se voltou. Se mdias ponderadas
pudessem ser multi-moeda, ento o resto do sistema deveria ser possvel. No co-
rao do algoritmo era mantida uma contagem de dinheiro na coluna. De fato, o
algoritmo era abstrato o suficiente para calcular a mdia de qualquer objeto que
pudesse trabalhar aritmeticamente. Tornou-se possvel ter mdias ponderadas de
datas, por exemplo.
O final de semana passou com as atividades habituais de finais de semana.
Na segunda-feira pela manh, o chefe estava de volta. Ns podemos fazer isso?
D-me mais um dia, e eu falarei com certeza.
Dollar agiu como um contador na mdia ponderada; portanto, a fim de calcu-
lar em mltiplas moedas, eles precisavam de um objeto com um contador por mo-
eda, como uma espcie de polinmio. Em vez de 3x2 e 4y3, entretanto, os termos
seriam 15 USD e 200 CHF.
Um experimento rpido mostrou que era possvel computar com um objeto
Currency (moeda) genrico em vez de um Dollar, e retornar um PolyCurrency quando
duas moedas diferentes eram adicionadas juntas. O truque, agora, era criar espao
para a nova funcionalidade sem estragar nada que j estivesse funcionando. O que
aconteceria se Ward apenas rodasse os testes?
Depois da adio de algumas operaes no implementadas para Currency,
a maior parte dos testes passou. No final do dia, todos os testes haviam passado.
Ward verificou o cdigo existente e foi at o chefe. Ns podemos fazer, ele disse
confiante.
Vamos pensar um pouco sobre essa histria. Em dois dias, o mercado poten-
cial foi multiplicado muitas vezes, multiplicando o valor de WyCash muitas vezes.
Contudo, a habilidade para criar tanto valor comercial to rpido no foi aciden-
te. Muitos fatores entraram em jogo.

Mtodo Ward e o time WyCash precisaram ter, pouco a pouco, cons-


tante crescimento de experincia no projeto do sistema, de modo que a
mecnica da transformao foi bem feita.
Motivo Ward e o time precisaram entender claramente a importncia do
negcio de fazer WyCash multi-moeda e ter a coragem para comear essa
tarefa aparentemente impossvel.
Oportunidade A combinao de uma gerao de testes amplos e con-
fiveis, um programa bem fatorado e uma linguagem de programao

1
Para mais detalhes sobre o framework de relatrio, consulte c2.com/doc/oopsla91.html.
Introduo 19

que torna possvel isolar decises de projeto significou que houve poucas
fontes de erro e esses erros foram fceis de identificar.

Voc no pode controlar o motivo de ter de multiplicar o valor de seu projeto


tecendo tcnicas mgicas. Mtodo e oportunidade, por outro lado, esto inteira-
mente sob seu controle. Ward e seu time criaram mtodo e oportunidade por meio
da combinao de talento, experincia e disciplina. Isso significa que se voc no
um dos dez melhores engenheiros de software do planeta e no tem um mao de
dinheiro no banco de modo a poder dizer para seu chefe dar uma caminhada para
ter tempo suficiente para fazer as coisas direito, tais momentos esto alm do seu
alcance para sempre?
No. absolutamente possvel colocar seus projetos em uma posio para
que voc trabalhe magicamente, mesmo sendo um engenheiro de software com ha-
bilidades comuns e, s vezes, sendo relutante e pegando atalhos quando a presso
aumenta. Desenvolvimento guiado por testes um conjunto de tcnicas que qual-
quer engenheiro de software pode seguir, que encoraja projetos simples e conjun-
tos de testes que inspiram confiana. Se voc um gnio, no precisa dessas regras.
Se voc um idiota, as regras no vo ajudar. Para a grande maioria de ns entre
esses dois extremos, seguir essas duas regras simples pode nos levar a trabalhar
muito mais perto de nosso potencial.

Escreva um teste automtico que falhe antes de escrever qualquer cdigo.


Remova duplicao.

Como exatamente fazer isso, os estgios sutis na aplicao dessas regras e o


quanto voc pode ampliar os limites da aplicao dessas duas regras simples so o
tpico deste livro. Ns comearemos com o objeto que Ward criou nesse momento
de inspirao dinheiro multi-moeda (multi-currency money).
Parte I
O Exemplo Financeiro

Na Parte I, desenvolveremos um modelo de cdigo tpico e completamente guiado


por testes (exceto quando cometermos alguns deslizes, puramente para fins edu-
cacionais). Minha meta que voc veja o ritmo do Desenvolvimento Guiado por
Testes (TDD), o qual pode ser resumido como segue.

1. Adicionar um teste rapidamente.


2. Rodar todos os testes e ver o mais novo falhando.
3. Fazer uma pequena mudana.
4. Rodar todos os testes e ver todos funcionando.
5. Refatorar para remover duplicaes.

As provveis surpresas incluem:

Como cada teste pode cobrir um pequeno aumento de funcionalidade


Quo pequenas e feias as mudanas podem ser para fazer os novos testes
rodarem
Com frequncia os testes so executados
De quantos pequeninos passos as refatoraes so compostas
Captulo 1
Dinheiro Multi-Moeda

Comearemos com o objeto que Ward criou no WyCash, dinheiro multi-moeda


(mencionado na Introduo). Suponha que tenhamos um relatrio como esse:

Instrumento Aes Preo Total


IBM 1.000 25 25.000
GE 400 100 40.000
Total 65.000

Para fazer um relatrio multi-moeda, precisamos adicionar moedas:

Instrumento Aes Preo Total


IBM 1.000 25 USD 25.000 USD
Novartis 400 150 CHF 60.000 CHF
Total 65.000 USD

Precisamos tambm especificar taxas de cmbio:

De Para Taxa
CHF USD 1,5

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
24 Parte I O Exemplo Financeiro

De que comportamento precisaremos para produzir o relatrio revisado?


Dito de outra forma, qual conjunto de testes, quando passarem, demonstrar a
presena de cdigo que estamos confiantes que ir calcular o relatrio correta-
mente?

Precisamos ser capazes de somar valores em duas moedas diferentes e de


converter o resultado, dado um conjunto de taxas de cmbio.
Precisamos ser capazes de multiplicar um valor (preo por ao) por um
nmero (nmero de aes) e de receber uma quantia.

Faremos uma lista de tarefas para lembrarmos o que precisamos fazer para
mantermos o foco e para dizer quando acabarmos. Quando comearmos a tra-
balhar em um item, ns o marcaremos em negrito, assim. Quando terminarmos
um item, o riscaremos, assim. Quando pensarmos em outro teste para escrever, o
adicionaremos lista.
Como voc pode ver, a partir da lista de tarefas na pgina anterior, trabalha-
remos, primeiro, na multiplicao. Ento, de qual objeto precisamos primeiro?
Uma questo traioeira. Ns no comeamos com objetos, comeamos com testes.
(Continuo tendo de me lembrar disso, ento vou fingir que voc to devagar
quanto eu.)
Tente novamente. De qual teste precisamos primeiro? Observando a lista,
aquele primeiro teste parece complicado. Comece devagar ou no comece. Multi-
plicao, quo difcil isso pode ser? Trabalharemos nisso primeiro.
Quando escrevemos um teste, imaginamos uma interface perfeita para nossa
operao. Estamos contando para ns mesmos como a operao se parecer do
lado de fora. Nossa histria nem sempre se torna verdadeira, mas melhor come-
ar da melhor Interface de Programao de Aplicativos (API) possvel e trabalhar
ao contrrio para fazer coisas complicadas, feias e realistas desde o comeo.
Aqui est um exemplo simples de multiplicao:

public void testMultiplication() {


Dollar five= new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
}

(Eu sei, eu sei, campos pblicos, efeitos colaterais, inteiros para quantias monetrias
e tudo isso. Pequenos passos. Tomaremos nota desse mau-cheiro e vamos em frente.
Temos um teste falhando e queremos a barra no verde to logo quanto possvel.)

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
Captulo 1 Dinheiro Multi-Moeda 25

O teste que acabamos de digitar nem mesmo compila. (Explicarei onde e


como digit-lo mais tarde, quando falarmos sobre o framework de testes, JUnit.)
Isso fcil de corrigir. Qual o mnimo que podemos fazer para t-lo compilando,
mesmo se ele no rodar? Temos quatro erros de compilao:

Sem classe Dollar


Sem construtor
Sem mtodo times(int)
Sem campo amount

Vamos cuidar deles um de cada vez. (Sempre procuro por alguma medida
numrica de progresso.) Podemos eliminar um erro definindo a classe Dollar:

Dollar
class Dollar

Um erro a menos, trs erros sobrando. Agora precisamos de um construtor, mas


ele no tem que fazer coisa alguma; apenas fazer o teste compilar:

Dollar
Dollar(int amount) {
}

Faltam dois erros. Precisamos de uma implementao de um stub* para times().


De novo, faremos o mnimo de trabalho possvel apenas para ter o teste compi-
lando:

Dollar
void times(int multiplier) {
}

Falta um erro. Finalmente, precisamos de um campo amount:

Dollar
int amount;

Bingo! Agora podemos rodar o teste e v-lo falhando, como mostrado na Figura 1.1.
Voc est vendo a temida barra vermelha*. Nosso framework de testes (JU-
nit, nesse caso) rodou o pequeno trecho de cdigo com o qual comeamos e infor-
mou que, embora esperssemos 10 como resultado, ns vimos 0.

* N. de R. T.: Usa-se o termo original, pois no h traduo consensual no Brasil. Um stub tem por
objetivo substituir outra unidade de software envolvida em um caso de teste, eliminando a dependn-
cia entre as unidades. No caso, um stub para times()significa criar um stub que substitua o mtodo
times(). Para mais detalhes sobre teste, ver o livro PEZZ, Mauro; YOUNG, Michal. Teste e Anlise
de software: processos, princpios e tcnicas. Porto Alegre: Bookman, 2008.
26 Parte I O Exemplo Financeiro

Figura 1.1 Progresso! O teste falha.

Tristeza? No, no. Falha progresso. Agora temos uma medida concreta
da falha. Isso melhor que apenas saber vagamente que estamos falhando. Nosso
problema de programao foi transformado de me d multi-moeda para faa
esse teste funcionar e, ento, faa o resto dos testes funcionarem. Muito mais sim-
ples. Muito menos escopo para temer. Ns podemos fazer esse teste funcionar.
Voc provavelmente no vai gostar da soluo, mas a meta agora no obter
a resposta perfeita, mas passar no teste. Ns faremos nosso sacrifcio no altar da
verdade e da beleza mais tarde.
Aqui est a menor mudana que eu pude imaginar que faria nosso teste passar:

Dollar
int amount= 10;

* N. de R. T.: Um resultado da execuo do framework JUnit a barra vermelha significando teste


falhou (fail) em contraposio barra verde teste passou (pass). Aos leitores desta edio: no livro
original em ingls tambm no h uma Figura 1.1 colorida (se isso serve de consolo). Vejam mais detalhes
sobre frameworks de teste na Parte II deste livro.
Captulo 1 Dinheiro Multi-Moeda 27

Figura 1.2 O teste roda.

A Figura 1.2 mostra o resultado quando o teste roda de novo. Agora ns


temos a barra verde, imaginada em cano e histria
, alegria, , encanto! No to rpido, meu caro hacker. O ciclo no est
completo. H pouqussimas entradas no mundo que fariam to limitada, to mal
cheirosa, to ingnua implementao passar. Precisamos generalizar antes de pros-
seguirmos. Lembre-se, o ciclo como segue.

1. Adicione um pequeno teste


2. Rode todos os testes e falhe
3. Faa uma pequena mudana
4. Rode os testes e seja bem-sucedido
5. Refatore para remover duplicao
28 Parte I O Exemplo Financeiro

Dependncia e duplicao

Steve Freeman salientou que o problema com o testar e codificar tal como est colocado
no duplicao (que ainda no apontei para voc, mas eu prometo faz-lo to logo essa
digresso estiver terminada). O problema a dependncia entre o cdigo e o teste voc
no pode mudar um sem mudar o outro. Nossa meta sermos capazes de escrever outro
teste que faa sentido para ns, sem ter que mudar o cdigo (algo que no possvel com
a implementao atual).
Dependncia o problema chave no desenvolvimento de software em todas as es-
calas. Se voc tem detalhes da implementao de SQL de um fornecedor espalhados por
todo o cdigo e voc decide mudar para outro fornecedor, ento descobrir que seu cdigo
dependente do banco de dados do fornecedor. Voc no pode mudar o banco de dados
sem mudar o cdigo.
Se dependncia o problema, duplicao o sintoma. Duplicao na maioria das
vezes toma a forma de lgica duplicada a mesma expresso aparecendo em mltiplos
lugares no cdigo. Objetos so excelentes para abstrair a duplicao de lgica. Diferente da
maioria dos problemas na vida, onde eliminar os sintomas apenas faz o problema brotar em
algum outro lugar de uma forma ainda pior, eliminar duplicao em programas elimina de-
pendncia. por isso que a segunda regra aparece em TDD. Eliminando a duplicao antes
de continuarmos com o prximo teste, maximizamos nossas chances de obtermos o prximo
teste com uma, e apenas uma, mudana.

Ns rodamos os itens de 1 at 4. Agora estamos preparados para remover


duplicao. Mas, onde est a duplicao? Geralmente, voc v duplicao entre
dois pedaos de cdigo, mas aqui a duplicao est entre o dado no teste e o dado
no cdigo. No v? E se escrevssemos o seguinte:

Dollar
int amount= 5 * 2;

Aquele 10 tinha que vir de algum lugar. Fizemos a multiplicao em nossas


cabeas to rpido que nem sequer notamos. O 5 e o 2 esto agora em dois lugares,
e devemos eliminar implacavelmente a duplicao antes de prosseguir. As regras
dizem isso. No h um passo nico que elimine o 5 e o 2. Mas, e se ns movermos
a atribuio de quantidade da inicializao do objeto para o mtodo times()?

Dollar
int amount;

void times(int multiplier) {


amount= 5 * 2;
}
Captulo 1 Dinheiro Multi-Moeda 29

O teste ainda passa; a barra permanece verde. A felicidade ainda nossa.


Esses passos no parecem muito pequenos para voc? Lembre-se, TDD no
sobre darmos passos pequeninos; sobre sermos capazes de dar passos pequeninos.
Eu codificaria no dia a dia com passos desse tamanho? No. Mas, quando as coisas
ficam no mnimo um pouco estranhas, eu fico feliz de poder fazer isso. Tente passos
pequeninos com um exemplo de sua prpria escolha. Se voc puder dar passos desse
tamanho, pode certamente dar passos do tamanho certo. Se voc apenas d passos
maiores, nunca saber se passos menores so apropriados. Deixando a defensiva de
lado, onde estvamos? Ah, sim, estvamos eliminando a duplicao entre o teste e
o cdigo funcionando. Onde podemos obter um 5? Aquele foi o valor passado para
o construtor, ento, se ns o salvamos em uma varivel quantidade,

Dollar
Dollar(int amount) {
this.amount= amount;
}

Podemos us-lo em times():

Dollar
void times(int multiplier) {
amount= amount * 2;
}

O valor do parmetro multiplicador 2, ento podemos substituir o parmetro


pela constante:

Dollar
void times(int multiplier) {
amount= amount * multiplier;
}

Para demonstrar nosso profundo conhecimento da sintaxe de Java, queremos usar


o operador *= (o qual faz, isso deve ser dito, reduzir a duplicao):

Dollar
void times(int multiplier) {
amount *= multiplier;
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
30 Parte I O Exemplo Financeiro

Podemos marcar agora o primeiro teste como feito. Em seguida, cuidaremos


daqueles efeitos colaterais estranhos. Mas, primeiro, vamos revisar. Fizemos o se-
guinte:

Fizemos uma lista dos testes que sabamos que precisvamos ter funcio-
nando.
Contamos uma histria com um trecho de cdigo sobre como queramos
ver uma operao.
Ignoramos os detalhes do JUnit por enquanto.
Fizemos o teste compilar com stubs.
Fizemos o teste rodar, cometendo pecados horrveis.
Gradualmente, generalizamos o cdigo que est funcionando, substituin-
do constantes por variveis.
Adicionamos itens nossa lista de tarefas em vez de resolv-los todos de
uma vez.
Captulo 2
Degenerar Objetos

O ciclo geral de TDD o seguinte:

1. Escreva um teste. Pense em como voc gostaria que a operao em sua


mente aparecesse em seu cdigo. Voc est escrevendo uma histria. In-
vente a interface que deseja ter. Inclua na histria todos os elementos que
voc imagina que sero necessrios para calcular as respostas certas.
2. Faa-o rodar. Fazer rapidamente aquela barra ir para verde domina todo o
resto. Se a soluo limpa e simples bvia, ento codifique-a. Se a soluo
limpa e simples bvia, mas vai levar um minuto, ento tome nota dela e
volte para o problema principal que deixar a barra verde em segundos.
Essa mudana na esttica difcil para alguns engenheiros de software ex-
perientes. Eles apenas sabem como seguir as regras da boa engenharia. Um
verde rpido perdoa todos os pecados. Mas, apenas por um momento.
3. Faa direito. Agora que o sistema est se comportando, ponha os cami-
nhos pecaminosos do passado recente atrs de voc. D um passo para
trs na correta e minuciosa trilha da integridade de software. Remova a
duplicao que voc introduziu e chegue ao verde rapidamente.

O objetivo cdigo limpo que funciona (obrigado a Ron Jeffries por esse
resumo conciso). Cdigo limpo que funciona est fora do alcance mesmo dos
melhores programadores em algum momento, e fora do alcance da maior parte
dos programadores (como eu) a maior parte do tempo. Dividir e conquistar. Pri-
meiro resolveremos a parte do que funciona do problema. Ento resolveremos
a parte do cdigo limpo. Isso o oposto do desenvolvimento guiado por ar-
quitetura, em que, primeiro, voc resolve o cdigo limpo , ento luta tentando
integrar no projeto as coisas que aprende conforme resolve o problema do que
funciona.
32 Parte I O Exemplo Financeiro

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?

Temos um teste para trabalhar, mas percebemos algo estranho no processo:


quando fazemos uma operao em Dollar, o Dollar muda. Eu quero ser capaz de
escrever:

public void testMultiplication() {


Dollar five= new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
five.times(3);
assertEquals(15, five.amount);
}

Eu no consigo imaginar uma maneira limpa de fazer esse teste funcionar.


Depois da primeira chamada de times(), cinco no mais cinco , na realidade,
dez. Se, contudo, retornarmos um novo objeto de times(), ento podemos multipli-
car nossos cinco contos originais para sempre e nunca teremos de mud-lo.
Estamos mudando a interface de Dollar quando fazemos essa mudana,
ento temos que mudar o teste. Tudo bem. Nossos palpites sobre a interface
certa provavelmente no so mais perfeitos que nossos palpites sobre a imple-
mentao certa.

public void testMultiplication() {


Dollar five= new Dollar(5);
Dollar product= five.times(2);
assertEquals(10, product.amount);
product= five.times(3);
assertEquals(15, product.amount);
}

O novo teste no compila at que mudemos a declarao de Dollar.times():

Dollar
Dollar times(int multiplier) {
amount *= multiplier;
return null;
}
Captulo 2 Degenerar Objetos 33

Agora o teste compila, mas no roda. Progresso! Faz-lo rodar requer que
retornemos um novo Dollar com a quantia correta:

Dollar
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?

No Captulo 1, quando fizemos um teste funcionar, comeamos com uma


implementao fajuta e gradativamente a fizemos real. Aqui, codificamos o que
pensvamos estar correto e rezamos enquanto os testes rodavam (preces pequenas,
para ser exato, pois rodar os testes levava apenas alguns milissegundos). Porque
fomos sortudos e o teste rodou, conseguimos riscar outro item.
A seguir, esto duas das trs estratgias que conheo para chegar rapidamente
ao verde:

Engane-o Retorne uma constante e gradualmente substitua constantes


por variveis at ter o cdigo real.
Use Implementao bvia Codifique a implementao real.

Quando uso TDD na prtica, comumente troco entre esses dois modos de
implementao. Quando tudo est indo bem e eu sei o que digitar, ponho Imple-
mentao bvia atrs de Implementao bvia (rodando os testes a cada vez para
garantir que o bvio para mim tambm bvio para o computador). Assim que
eu recebo uma barra vermelha inesperada, dou marcha r, mudo para implemen-
taes falsas e refatoro para o cdigo certo. Quando minha confiana retorna, eu
volto para Implementaes bvias.
H um terceiro estilo de TDD, Triangulao, que demonstraremos no Cap-
tulo 3. Contudo, para revisar:

Traduzimos um obstculo de projeto (efeitos colaterais) em um caso de


testes que falhou por causa desse obstculo
Obtivemos o cdigo compilando rapidamente com uma implementao
de stub
Fizemos o teste funcionar codificando o que parecia ser o cdigo certo
34 Parte I O Exemplo Financeiro

A traduo de um sentimento (por exemplo, averso a efeitos colaterais) para um


teste (por exemplo, multiplicar o mesmo Dollar duas vezes) um tema comum de
TDD. Quanto mais eu fizer isso, mais apto estarei para traduzir meus julgamen-
tos estticos em testes. Quando consigo fazer isso, minhas discusses de projeto
ficam muito mais interessantes. Primeiro, conseguimos falar sobre se o sistema
deve funcionar assim ou assado. Uma vez que decidamos o comportamento
correto, podemos falar sobre a melhor forma de alcanar tal comportamento.
Podemos especular sobre toda a verdade e a beleza que quisermos tomando umas
cervejas, mas, enquanto estamos programando, podemos deixar divagaes para
trs e falar de casos.
Captulo 3
Igualdade para Todos

Se eu tenho um inteiro e somo 1 a ele, no espero o inteiro original mudar; espero


usar o novo valor. Objetos geralmente no se comportam desse jeito. Se eu tenho
um contrato e adiciono um a sua cobertura, ento a cobertura do contrato deveria
mudar (sim, sim, sujeita a todo tipo de regras de negcio interessantes que no nos
interessam aqui).
Podemos usar objetos como valores, como estamos usando nosso Dollar ago-
ra. O padro para isso Value Object*. Uma das restries em usar Value Object
que os valores das variveis de instncia do objeto nunca mudem uma vez que
foram criados no construtor.
H uma grande vantagem em usar Value Object: voc no tem que se preocu-
par com problemas de sinnimos. Digamos que eu tenho um cheque e coloque sua
quantia em $5, e ento eu crio outra quantia de cheque com os mesmos $5. Alguns
dos piores bugs da minha carreira ocorreram quando mudar o valor do primeiro
cheque mudou, inadvertidamente, o valor do segundo cheque. Isso so sinnimos.
Quando voc tem Value Object, no precisa se preocupar com sinnimos. Se
eu tenho $5, ento tenho certeza que ele vai ser $5 para todo o sempre. Se algum
quer $7, ento ter que criar um objeto totalmente novo.

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()

* N. de R. T.: Value Object: padro de projeto (design pattern) que permite a criao de objeto simples
com cpias dos atributos de um objeto sem permitir sua alterao. No um padro GoF. Ver mais
detalhes em http://java.sun.com/j2ee/patterns/ValueObject.html.
36 Parte I O Exemplo Financeiro

Uma implicao do Value Object que todas as operaes devem retornar


um novo objeto, como vimos no Captulo 2. Outra implicao que Value Object
deve implementar equals(), pois um $5 to bom quanto qualquer outro.

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()

Se voc usar Dollars como a chave para uma tabela hash, ento tem que im-
plementar hashCode() se voc implementar equals(). Colocaremos isso na lista de
tarefas tambm e chegaremos a ela quando for um problema.
Voc no est pensando sobre a implementao de equals(), est? Bom. Nem
eu. Depois de bater atrs da minha mo com uma rgua, estou pensando em como
testar igualdade. Primeiro, $5 deveria ser igual a $5:
public void testEquality() {
assertTrue(new Dollar(5).equals(new Dollar(5)));
}

A barra fica adoravelmente vermelha. A implementao falsa para retornar


apenas true:

Dollar
public boolean equals(Object object) {
return true;
}

Voc e eu sabemos que true realmente 5 == 5, que , na realidade, quan-


tidade == 5, que , na realidade, quantidade == dollar.amount. Sem passar
por essas etapas, eu no seria capaz de demonstrar a terceira e mais conservadora
estratgia de implementao: Triangulao.
Se duas estaes receptoras a uma distncia conhecida uma da outra podem
medir a direo de um sinal de rdio, ento h informao suficiente para calcular
a distncia e a direo do sinal (se voc lembra mais de trigonometria que eu, pelo
menos). Esse clculo chamado Triangulao.
Por analogia, quando triangulamos, apenas generalizamos cdigo quando
temos dois exemplos ou mais. Vamos ignorar brevemente a duplicao entre teste
e cdigo do modelo. Quando o segundo exemplo exige uma soluo mais geral,
ento, e s ento, generalizamos.
Assim, para triangula-mos precisamos de um segundo exemplo. Que tal $5 !=
$6?
Captulo 3 Igualdade para Todos 37

public void testEquality() {


assertTrue(new Dollar(5).equals(new Dollar(5)));
assertFalse(new Dollar(5).equals(new Dollar(6)));
}

Agora podemos generalizar a igualdade:

Dollar
public boolean equals(Object object) {
Dollar dollar= (Dollar) object;
return amount == dollar.amount;
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()

Poderamos ter usado Triangulao para guiar a generalizao de times() tam-


bm. Se tivssemos $5 x 2 = $10 e $5 x 3 = $15, ento no estaramos mais aptos
a retornar uma constante.
Triangulao parece engraada para mim. Eu a uso apenas quando estou
completamente inseguro sobre como refatorar. Se consigo ver como eliminar du-
plicao entre cdigo e testes e criar a soluo geral, ento fao apenas isso. Por
que eu precisaria escrever outro teste para me permitir escrever o que, provavel-
mente, teria escrito em primeiro lugar?
Contudo, quando as ideias de projeto simplesmente no esto surgindo,
Triangulao d a chance para pensarmos sobre o problema de uma direo ligei-
ramente diferente. Quais eixos de variabilidade voc est tentando prover em seu
projeto? Faa alguns deles variarem, e a resposta pode se tornar mais clara.

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
38 Parte I O Exemplo Financeiro

Ento, igualdade est feita por enquanto. Mas, e a comparao com null e a
comparao com outros objetos? Essas so operaes comumente usadas, mas no
necessrias no momento, ento vamos adicion-las lista de tarefas.
Agora que temos igualdade, podemos comparar diretamente Dollars com
Dollars. O que nos permite tornar quantidade privada (private), como todas as
boas variveis de instncia deveriam ser. Para revisar o que vimos:

Percebemos que nosso padro de projeto (Value Object) sugeria uma


operao
Testamos para aquela operao
Implementamos de uma forma simples
No refatoramos imediatamente, mas, em vez disso, testamos mais
Refatoramos para capturar os dois casos de uma s vez
Captulo 4
Privacidade

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto

Agora que definimos igualdade, podemos us-la para fazer nossos testes mais
expressivos. Conceitualmente, a operao Dollar.times() deveria retornar um
Dollar cujo valor o valor recebido vezes o multiplicador. Nosso teste no diz
exatamente isso:

public void testMultiplication() {


Dollar five= new Dollar(5);
Dollar product= five.times(2);
assertEquals(10, product.amount);
product= five.times(3);
assertEquals(15, product.amount);
}

Ns podemos reescrever a primeira assero para comparar Dollars com


Dollars:

public void testMultiplication() {


Dollar five= new Dollar(5);
Dollar product= five.times(2);
assertEquals(new Dollar(10), product);
40 Parte I O Exemplo Financeiro

product= five.times(3);
assertEquals(15, product.amount);
}

Isso parece melhor, ento reescrevemos a segunda assero tambm:

public void testMultiplication() {


Dollar five= new Dollar(5);
Dollar product= five.times(2);
assertEquals(new Dollar(10), product);
product= five.times(3);
assertEquals(new Dollar(15), product);
}

Agora a varivel temporria product no est ajudando muito, ento pode-


mos otimiz-la:

public void testMultiplication() {


Dollar five= new Dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}

Esse teste nos fala mais claramente, como se fosse uma afirmao de verdade e no
uma sequncia de operaes.
Com essas mudanas para o teste, Dollar agora a nica classe usando sua
varivel de instncia amount, ento podemos faz-la privada:

Dollar
private int amount;

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto

E podemos riscar outro item da lista de tarefas. Perceba que nos abrimos ao risco.
Se o teste de igualdade falha em checar cuidadosamente se a igualdade est funcio-
nando, ento o teste para multiplicao poderia falhar tambm em checar cuida-
dosamente se a multiplicao est funcionando. Esse um risco que gerenciamos
ativamente em TDD. No estamos lutando pela perfeio. Ao dizer tudo de duas
formas como cdigo e como teste esperamos reduzir nossos defeitos o suficien-
te para seguir em frente com confiana. De tempos em tempos nosso raciocnio
Captulo 4 Privacidade 41

falhar e um defeito vai passar. Quando isso acontecer, aprenderemos nossa lio
de como o teste deveria ter sido escrito e seguiremos em frente. No resto do tempo,
continuaremos em frente audaciosamente sob nossa barra verde bravamente tre-
mulante (minha barra realmente no tremula, mas a gente pode sonhar).
Para revisar:

Usamos uma funcionalidade recm-desenvolvida para melhorar um teste


Percebemos que, se dois testes falham de uma vez, estamos arruinados
Prosseguimos a despeito do risco
Usamos uma nova funcionalidade em um objeto sob teste para reduzir o
acoplamento entre os testes e o cdigo
Captulo 5
Falando Franca-mente*

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF

Como vamos abordar o primeiro teste, o teste mais interessante da lista? Ainda
parece ser um grande salto. No estou certo de poder escrever um teste que eu
possa implementar em uma pequena etapa. Um pr-requisito parece ser termos um
objeto como Dollar, mas para representar francos. Se conseguirmos que o objeto
Franc funcione da forma que o objeto Dollar funciona agora, estaremos mais prxi-
mos de escrever e rodar um teste misto de adio.
Podemos copiar e editar o teste de Dollar:

public void testFrancMultiplication() {


Franc five= new Franc(5);
assertEquals(new Franc(10), five.times(2));
assertEquals(new Franc(15), five.times(3));
}

* N. de R. T.: No ttulo original deste captulo, Franc-ly speaking, o autor faz um jogo de palavras
com o termo franc que aparece na palavra francly (francamente, sinceramente) mas que refere-se tam-
bm moeda usada na Sua (francos suos). O jogo irnico pois no exemplo monetrio nesta Parte
I do livro, moedas como dlar e franco so comumente usadas.
44 Parte I O Exemplo Financeiro

(Voc no est feliz por simplificarmos o teste do Captulo 4? Foi o que fez nosso
teste aqui ser mais fcil. No maravilhoso como muitas vezes as coisas funcio-
nam assim como nos livros? Eu realmente no planejei desse jeito, mas no farei
promessas para o futuro.)
Qual pequeno passo nos levar barra verde? Copiar o cdigo de Dollar e
substituir Dollar por Franc.
Pare. Espere. Eu posso ouvir a inclinao esttica em voc, zombando e cus-
pindo. Reuso do tipo copiar-e-colar? A morte da abstrao? O assassino da clareza
de projeto?
Se voc est chateado, respire fundo. Inspire pelo nariz... segure, 1, 2, 3...
expire pela boca. Pronto. Lembre-se, nosso ciclo tem fases diferentes (elas passam
rapidamente, em segundos, mas so fases):

1. Escreva um teste
2. Faa-o compilar
3. Rode-o e veja-o falhar
4. Faa-o rodar
5. Remova duplicao

As diferentes fases tm diferentes propsitos. Elas invocam estilos diferentes de


soluo, diferentes perspectivas estticas. As primeiras trs fases precisam passar
rapidamente, assim chegamos a um estado conhecido com a nova funcionalidade.
Podemos cometer qualquer nmero de pecados para chegar l, pois a velocidade
agora melhor que o projeto, ao menos por um breve momento.
Agora estou preocupado. Dei a voc permisso para abandonar todos os
princpios de bom projeto. L vai voc para sua equipe Kent disse que toda
aquela coisa de projeto no importa. Pare. O ciclo no est completo. Uma cadei-
ra Aeron de quatro ps cai. As quatro primeiras etapas do ciclo no funcionaro
sem a quinta. Bom projeto no bom momento. Faa-o rodar, faa-o direito.
Pronto, me sinto melhor. Agora estou certo de que voc no mostrar a nin-
gum, exceto para seu colega, seu cdigo, at que tenha removido a duplicao.
Onde estvamos? Ah, sim. Violando todos os dogmas do bom projeto em prol da
velocidade (a penitncia por nossos pecados ocupar os prximos captulos).

Franc
class Franc {
private int amount;
Franc(int amount) {
this.amount= amount;
}
Franc times(int multiplier) {
return new Franc(amount * multiplier);
}
Captulo 5 Falando Franca-mente 45

public boolean equals(Object object) {


Franc franc= (Franc) object;
return amount == franc.amount;
}
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum

Devido ao passo para rodar o cdigo ser to curto, fomos capazes de pular a etapa
de faa-o compilar.
Agora temos duplicao abundante e temos que elimin-la antes de escrever
nosso prximo teste. Comearemos generalizando equals(). Entretanto, podemos
riscar um item de nossa lista de tarefas, embora tenhamos acrescentado mais dois.
Revisando:

No pudemos armar um grande teste, ento inventamos um pequeno teste


que representa progresso
Escrevemos o teste por meio de uma duplicao e uma edio descaradas
Ainda pior, fizemos o teste funcionar pela cpia e edio indiscriminada
do cdigo modelo
Prometemos a ns mesmos que no iramos para casa at a duplicao ter
sumido
Captulo 6
Igualdade para
Todos, Restaurada

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum

H uma fabulosa sequncia em Crossing to Safety na qual o autor, Wallace Steg-


ner, descreve a oficina de um personagem. Cada item est perfeitamente no lugar, o
cho impecvel, tudo est em ordem e limpo. O personagem, contudo, nunca fez
nada. Preparar tem sido o trabalho de sua vida. Ele prepara, ento limpa. (Esse
tambm o livro cujo final me fez debulhar-me em lgrimas na classe executiva em
um transatlntico 747. Leia com cuidado.)
Ns evitamos essa armadilha no Captulo 5. Realmente conseguimos um
novo caso de teste funcionando. Mas, pecamos enormemente ao copiar e colar
toneladas de cdigo de forma a faz-lo rapidamente. Agora hora da limpeza.
Uma possibilidade fazer uma de nossas classes estender a outra. Eu tentei
isso, e dificilmente salva qualquer cdigo. Em vez disso, encontraremos uma su-
perclasse comum para as duas classes, como mostra a Figura 6.1. (Eu j tentei isso
tambm, e funciona muito bem, embora leve um pouco mais de tempo.)
48 Parte I O Exemplo Financeiro

Dollar Money

Franc Dollar Franc

Figura 6.1 Uma superclasse comum para as duas classes.

E se tivssemos uma classe Money para capturar o cdigo de igualdade co-


mum? Podemos comear pequeno:

Money
class Money

Todos os testes ainda rodam no que pudssemos ter estragado algo, mas uma
boa hora para rodar testes de qualquer forma. Se Dollar estende Money, isso no
poderia estragar nada.

Dollar
class Dollar extends Money {
private int amount;
}

Poderia? No, todos os testes ainda rodam. Agora podemos mover a varivel
de instncia amount para Money:

Money
class Money {
protected int amount;
}

Dollar
class Dollar extends Money {
}

A visibilidade tem que mudar de particular para protegida para que a subclasse
possa tambm v-la. (Quisssemos ir mais lentamente, poderamos ter declarado o
campo Money em uma etapa e ento removido ele de Dollar em uma segunda etapa.
Estou me sentindo arrojado.)
Agora podemos comear a trabalhar no cdigo de equals() prontos para mo-
v-lo para cima. Primeiro mudamos a declarao da varivel temporria:

Dollar
public boolean equals(Object object) {
Money dollar= (Dollar) object;
return amount == dollar.amount;
}
Captulo 6 Igualdade para Todos, Restaurada 49

Todos os testes ainda rodam. Agora mudamos a converso:

Dollar
public boolean equals(Object object) {
Money dollar= (Money) object;
return amount == dollar.amount;
}

Para comunicar melhor, deveramos tambm mudar o nome da varivel temporria:

Dollar
public boolean equals(Object object) {
Money money= (Money) object;
return amount == money.amount;
}

Agora podemos mov-lo de Dollar para Money:

Money
public boolean equals(Object object) {
Money money= (Money) object;
return amount == money.amount;
}

Agora, precisamos eliminar Franc.equals(). Primeiro, percebemos que os testes


de igualdade no cobrem a comparao de Francs com Francs. Nossos pecados na
cpia de cdigo esto nos alcanando. Antes de mudarmos o cdigo, escreveremos
os testes que deveriam em primeiro lugar estar l.
Voc frequentemente estar implementando TDD em cdigo que no tem
testes adequados (ao menos para a prxima dcada ou coisa assim). Quando voc
no tem testes suficientes, obrigado a encarar refatoraes que no so supor-
tadas por testes. Voc poderia cometer um erro de refatorao, e os testes ainda
assim rodariam. O que voc faz?
Escreva testes que gostaria de ter. Se no o fizer, estragar algo enquanto re-
fatora. Ento ter maus pressentimentos sobre refatorao e vai parar de faz-lo
tanto. Logo, seu projeto vai deteriorar. Voc ser despedido. Seu co vai deix-lo.
Voc vai parar de prestar ateno na sua alimentao. Seus dentes vo ficar ruins.
Ento, para manter seus dentes saudveis, teste retroativamente antes de refatorar.
Felizmente, aqui os testes so fceis de escrever. Ns apenas copiamos os
testes de Dollar:

public void testEquality() {


assertTrue(new Dollar(5).equals(new Dollar(5)));
assertFalse(new Dollar(5).equals(new Dollar(6)));
assertTrue(new Franc(5).equals(new Franc(5)));
assertFalse(new Franc(5).equals(new Franc(6)));
}
50 Parte I O Exemplo Financeiro

Mais duplicao, duas linhas a mais! Vamos expiar os pecados, tambm.


Testes no lugar, podemos ter Franc estendendo Money:

Franc
class Franc extends Money {
private int amount;
}

Podemos deletar o campo amount de Franc em favor daquele em Money:

Franc
class Franc extends Money {
}

Franc.equals() quase o mesmo que Money.equals(). Se ns os fizermos preci-


samente iguais, ento podemos deletar a implementao em Franc sem mudar o
sentido do programa. Primeiro, mudamos a declarao da varivel temporria:

Franc
public boolean equals(Object object) {
Money franc= (Franc) object;
return amount == franc.amount;
}

Ento mudamos a converso:

Franc
public boolean equals(Object object) {
Money franc= (Money) object;
return amount == franc.amount;
}

Realmente temos que mudar o nome da varivel temporria para combi-


nar com a superclasse? Eu deixarei isso para sua conscincia.... certo, faremos
isso:

Franc
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount;
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
Captulo 6 Igualdade para Todos, Restaurada 51

hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares

Agora, no h diferena entre Franc.equals() e Money.equals(), ento deletaremos


a implementao redundante em Franc. E rodamos os testes. Eles rodam. O que
acontece quando comparamos Francs com Dollars? Chegaremos a isso no Captulo
7. Revisando o que fizemos aqui:

Movemos passo a passo cdigo comum de uma classe (Dollar) para uma
superclasse (Money)
Fizemos de uma segunda classe (Franc) uma subclasse
Reconciliamos duas implementaes equals() antes de eliminar aquela
redundante
Captulo 7
Mas e Laranjas

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares

Um pensamento nos surpreendeu no final do Captulo 6: o que acontece quando


comparamos Francs com Dollars? Ns respeitosamente transformamos nosso pavo-
roso pensamento em um item na lista de tarefas. Mas no conseguimos tir-lo da
cabea. O que acontece?

public void testEquality() {


assertTrue(new Dollar(5).equals(new Dollar(5)));
assertFalse(new Dollar(5).equals(new Dollar(6)));
assertTrue(new Franc(5).equals(new Franc(5)));
assertFalse(new Franc(5).equals(new Franc(6)));
assertFalse(new Franc(5).equals(new Dollar(5)));
}

Ele falha. Dollars so Francs. Antes que os compradores suos fiquem entu-
siasmados, vamos tentar corrigir o cdigo. O cdigo de igualdade precisa verificar
54 Parte I O Exemplo Financeiro

que no estamos comparando Dollars com Francs. Podemos fazer isso agora compa-
rando a classes dos dois objetos dois Moneys so iguais apenas se suas quantidades
e classes so iguais.

Money
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount
&& getClass().equals(money.getClass());
}

Usar as classes dessa forma no cdigo modelo um pouco mau cheiroso*.


Gostaramos de usar um critrio que fizesse sentido no domnio das finanas, no
no domnio de objetos Java. Mas, no temos atualmente nada como uma moeda,
e isso no parece ser razo suficiente para introduzir uma, ento isso ter que ficar
assim por enquanto.

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?

Agora, realmente precisamos nos livrar do cdigo comum de times() para que
possamos obter uma aritmtica de moeda mista. Antes de faz-lo, contudo, pode-
mos revisar nossas grandes realizaes desse captulo:

Pegamos uma dvida que nos incomodava e a transformamos em um teste.


Fizemos o teste rodar de uma forma razovel, mas no perfeita
getClass().
Decidimos no introduzir mais projeto at termos uma motivao melhor.

* N. de R. T.: Referncia a bad smells (mau cheiro), termo que a comunidade OO adotou para expressar
que um trecho de cdigo potencialmente fonte de problemas, suspeito e forte candidato a refatorao.
Captulo 8
Fazendo Objetos

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?

As duas implementaes de times() so extraordinariamente similares:

Franc
Franc times(int multiplier) {
return new Franc(amount * multiplier);
}

Dollar
Dollar times(int multiplier) {
return new Dollar(amount * multiplier);
}
56 Parte I O Exemplo Financeiro

Podemos dar um passo na direo de concili-los, fazendo ambos retornarem um


Money:

Franc
Money times(int multiplier) {
return new Franc(amount * multiplier);
}

Dollar
Money times(int multiplier) {
return new Dollar(amount * multiplier);
}

O prximo passo no to bvio. As duas subclasses de Money no esto


fazendo o suficiente para justificarem sua existncia, logo, gostaramos de elimin-
-las. Mas, no podemos fazer isso em um grande passo, pois no seria uma de-
monstrao muito eficaz de TDD.
Certo, estaremos um passo mais prximos de eliminarmos as subclasses se
houver menos referncias diretas s subclasses. Podemos introduzir um mtodo
fbrica em Money que retorne um Dollar. Usaremos algo assim:

public void testMultiplication() {


Dollar five = Money.dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}

A implementao cria e retorna um Dollar:

Money
static Dollar dollar(int amount) {
return new Dollar(amount);
}

Mas, queremos que as referncias a Dollars desapaream, logo precisamos mudar


a declarao no teste:

public void testMultiplication() {


Money five = Money.dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}

Nosso compilador educadamente nos informa que times() no est defini-


do para Money. Ainda no estamos prontos para implement-lo, ento fazemos
Money abstrato (suponho que ns deveramos ter comeado fazendo isso, no?) e
declaramos Money.times():

Money
abstract class Money
abstract Money times(int multiplier);
Captulo 8 Fazendo Objetos 57

Agora podemos mudar a declarao do mtodo fbrica:

Money
static Money dollar(int amount) {
return new Dollar(amount);
}

Todos os testes rodam, ento, pelo menos, no estragamos nada. Ns pode-


mos, agora, usar nosso mtodo fbrica em qualquer lugar dos testes:

public void testMultiplication() {


Money five = Money.dollar(5);
assertEquals(Money.dollar(10), five.times(2));
assertEquals(Money.dollar(15), five.times(3));
}
public void testEquality() {
assertTrue(Money.dollar(5).equals(Money.dollar(5)));
assertFalse(Money.dollar(5).equals(Money.dollar(6)));
assertTrue(new Franc(5).equals(new Franc(5)));
assertFalse(new Franc(5).equals(new Franc(6)));
assertFalse(new Franc(5).equals(Money.dollar(5)));
}

Agora estamos em uma posio ligeiramente melhor que antes. Nenhum c-


digo cliente sabe que existe uma subclasse chamada Dollar. Desacoplando os testes
da existncia das subclasses, nos demos liberdade para mudar herana sem afetar
qualquer cdigo modelo.
Antes de irmos mudar cegamente o testFrancMultiplication, percebemos que ele
no est testando qualquer lgica que no testada pela multiplicao de Dollar. Se
deletarmos o teste, perderemos qualquer confiana no cdigo? Um pouco, sim, por
isso o deixamos l. Mas suspeito.

public void testEquality() {


assertTrue(Money.dollar(5).equals(Money.dollar(5)));
assertFalse(Money.dollar(5).equals(Money.dollar(6)));
assertTrue(Money.franc(5).equals(Money.franc(5)));
assertFalse(Money.franc(5).equals(Money.franc(6)));
assertFalse(Money.franc(5).equals(Money.dollar(5)));
}
public void testFrancMultiplication() {
Money five = Money.franc(5);
assertEquals(Money.franc(10), five.times(2));
assertEquals(Money.franc(15), five.times(3));
}

A implementao como Money.dollar():

Money
static Money franc(int amount) {
return new Franc(amount);
}
58 Parte I O Exemplo Financeiro

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?

Em seguida, vamos nos livrar da duplicao de times(). Por agora, para revi-
sar:

Avanamos um passo eliminando a duplicao pela conciliao das assi-


naturas de duas variantes do mesmo mtodo times().
Movemos ao menos uma declarao dos mtodos para uma superclasse
comum.
Desacoplamos cdigo de teste da existncia de subclasses concretas pela
introduo de mtodos fbrica.
Percebemos que, quando as subclasses desaparecerem, alguns testes sero
redundantes, mas no tomamos providncias.
Captulo 9
Tempos em que
Estamos Vivendo*

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?

O que h em nossa lista de tarefas que pode nos ajudar a eliminar essas malditas
subclasses inteis? O que aconteceria se introduzssemos a noo de moeda?
Como queremos implementar moedas no momento? Estraguei tudo, de novo.
Antes do fazedor-de-regras sair, vou reformular: Como queremos testar moedas
no momento? Pronto. Dedos salvos, por enquanto.

* N. de R. T.: O ttulo deste captulo no original Times Were Living In outro jogo de palavras do
autor: a palavra times pode se referir operao de multiplicao (traduzida como vezes) muito
usada no captulo mas tambm poderia ser traduzida como tempos. O ttulo assim seria Tempos
em que Estamos Vivendo ou Vezes em que estamos Vivendo, mas optamos pelo primeiro...
60 Parte I O Exemplo Financeiro

Podemos querer ter objetos complicados representando moedas com fbricas


flyweight* para garantir que no criamos mais objetos do que realmente precisa-
mos. Mas, por enquanto, strings serviro:

public void testCurrency() {


assertEquals("USD", Money.dollar(1).currency());
assertEquals("CHF", Money.franc(1).currency());
}

Primeiro, declaramos currency() em Money:

Money
abstract String currency();

Ento o implementamos em ambas as subclasses:

Franc
String currency() {
return "CHF";
}

Dollar
String currency() {
return "USD";
}

Queremos que a mesma implementao seja suficiente para ambas as classes.


Poderamos armazenar a moeda em uma varivel de instncia e apenas retornar a
varivel. (Vou comear a ir um pouco mais rpido com as refatoraes devido ao
tempo. Se eu for muito rpido, por favor, me avise para ir mais devagar. Ei, espere,
isso um livro talvez eu apenas no deva acelerar demais.)

Franc
private String currency;
Franc(int amount) {
this.amount = amount;
currency = "CHF";
}
String currency() {
return currency;
}

Podemos fazer o mesmo com Dollar:

Dollar
private String currency;

* N. de R. T.: Referncia ao padro GoF Flyweight. Flyweight usado para criar compartilhamento
entre dados de objetos e limitar a proliferao de muitas classes similares pequenas. Ver livro GAMMA,
Erich et al. Padres de projeto: solues reutilizveis de software orientado a objetos. Porto Alegre:
Bookman, 2000.
Captulo 9 Tempos em que Estamos Vivendo 61

Dollar(int amount) {
this.amount = amount;
currency = "USD";
}
String currency() {
return currency;
}

Agora, podemos subir a declarao da varivel e a implementao de


currency(), pois elas so idnticas:

Money
protected String currency;
String currency() {
return currency;
}

Se movermos as strings constantes USD e CHF para os mtodos fbrica (factory)


estticos, ento os dois construtores sero idnticos e poderemos criar uma imple-
mentao comum.
Primeiro, adicionaremos um parmetro ao construtor:

Franc
Franc(int amount, String currency) {
this.amount = amount;
this.currency = "CHF";
}

Isso estraga os dois chamadores do construtor:

Money
static Money franc(int amount) {
return new Franc(amount, null);
}

Franc
Money times(int multiplier) {
return new Franc(amount * multiplier, null);
}

Espere um minuto! Por que Franc.times() est chamando o construtor em vez


do mtodo fbrica? Queremos fazer essa mudana agora ou vamos esperar? A
resposta dogmtica que esperaremos; no interromper o que estamos fazendo. A
resposta, na minha prtica, que vou fazer uma breve interrupo, mas bem breve,
e que nunca interromperei uma interrupo (Jim Coplien me ensinou essa regra).
Para ser realista, vamos limpar times() antes de prosseguir:

Franc
Money times(int multiplier) {
return Money.franc(amount * multiplier);
}
62 Parte I O Exemplo Financeiro

Agora, o mtodo fbrica pode passar CHF:

Money
static Money franc(int amount) {
return new Franc(amount, "CHF");
}

E, finalmente, podemos atribuir o parmetro para a varivel de instncia:

Franc
Franc(int amount, String currency) {
this.amount = amount;
this.currency = currency ;
}

Estou me sentindo na defensiva novamente sobre dar passos muito pequeni-


nos. Estou recomendando que voc realmente trabalhe dessa maneira? No. Estou
recomendando que voc seja capaz de trabalhar dessa forma. O que eu acabei de
fazer foi trabalhar em passos maiores e cometer um erro meio estpido. Eu tirei
um valioso minuto de mudanas, troquei para uma marcha menor, e fiz isso com
passos pequenos. Estou me sentindo melhor agora, ento veremos se conseguimos
fazer a mudana anloga de Dollar de uma vez s:

Money
static Money dollar(int amount) {
return new Dollar(amount, "USD");
}

Dollar
Dollar(int amount, String currency) {
this.amount = amount;
this.currency = currency;
}
Money times(int multiplier) {
return Money.dollar(amount * multiplier);
}

E funcionou de primeira! Ipi!


Esse o tipo de sintonia que voc far constantemente com TDD. Os passos
pequeninos esto parecendo restritivos? D passos maiores. Est se sentindo um
pouco inseguro? D passos menores. TDD um processo conduzido um pouco
desse jeito, um pouco de outro. No h tamanho certo de passo, agora e eterna-
mente.
Os dois construtores agora so idnticos, ento conseguimos subir a imple-
mentao:

Money
Money(int amount, String currency) {
this.amount = amount;
Captulo 9 Tempos em que Estamos Vivendo 63

this.currency = currency;
}

Franc
Franc(int amount, String currency) {
super(amount, currency);
}

Dollar
Dollar(int amount, String currency) {
super(amount, currency);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?

Estamos quase prontos para subir a implementao de times() e eliminar as


subclasses, mas, primeiro, para revisar:

Fomos um pouco travados em grandes ideias de projeto, assim, trabalha-


mos em algo menor que percebemos antes.
Conciliamos os dois construtores, movendo a variao para o chamador
(o mtodo fbrica).
Interrompemos a refatorao para uma pequena volta, usando o mtodo
fbrica em times().
Repetimos uma refatorao anloga (fazendo em Dollar o que j fizemos
em Franc) em um grande passo.
Subimos dois construtores idnticos.
Captulo 10
Tempos Interessantes*

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?

Quando terminarmos este captulo, teremos uma nica classe para representar
Money. As duas implementaes de times() so parecidas, mas no idnticas:

Franc
Money times(int multiplier) {
return Money.franc(amount * multiplier);
}

* N. de R. T.: O ttulo deste captulo no original Interesting Times outro jogo de palavras do autor:
a palavra times pode se referir operao de multiplicao (traduzida como vezes) muito usada no
captulo mas tambm poderia ser traduzida como tempos. O ttulo assim seria Tempos Interessan-
tes ou Vezes Interessantes, ambos interessantes, mas optamos pelo primeiro...
66 Parte I O Exemplo Financeiro

Dollar
Money times(int multiplier) {
return Money.dollar(amount * multiplier);
}

No h um jeito bvio de faz-las idnticas. s vezes voc tem que voltar


atrs para seguir em frente, como quando resolvemos um Cubo de Rubik. O que
acontece se ns otimizarmos os mtodos fbrica? (Eu sei, eu sei, ns chamamos o
mtodo fbrica pela primeira vez apenas um captulo atrs. Frustrante, no ?)

Franc
Money times(int multiplier) {
return new Franc(amount * multiplier, "CHF");
}

Dollar
Money times(int multiplier) {
return new Dollar(amount * multiplier, "USD");
}

Em Franc, entretanto, sabemos que a varivel de instncia de moeda sempre


CHF, ento podemos escrever:

Franc
Money times(int multiplier) {
return new Franc(amount * multiplier, currency);
}

Isso funciona. O mesmo truque funciona em Dollar:

Dollar
Money times(int multiplier) {
return new Dollar(amount * multiplier, currency);
}

Estamos quase l. Realmente importa se temos um Franc ou um Money? Po-


deramos pensar sobre isso cuidadosamente, dado nosso conhecimento sobre o
sistema, mas temos cdigo limpo e temos testes que nos do confiana de que o
cdigo limpo funciona. Em vez de usar minutos de raciocnio suspeito, podemos
apenas perguntar para o computador fazendo as mudanas e rodando os testes.
No ensino de TDD, eu vejo essa situao o tempo todo excelentes engenheiros
de software pensando em uma questo durante 5 ou 10 minutos que o computa-
dor poderia responder em 15 segundos. Sem os testes, voc no tem escolha, voc
tem de pensar. Com os testes, voc pode decidir se um experimento responderia
a questo mais rpido. s vezes, voc deveria apenas perguntar para o compu-
tador.
Captulo 10 Tempos Interessantes 67

Para rodar nosso experimento, mudamos Franc.times() para retornar um


Money:

Franc
Money times(int multiplier) {
return new Money(amount * multiplier, currency);
}

O compilador nos diz que Money deve ser uma classe concreta:

Money
class Money
Money times(int amount) {
return null;
}

E ns temos uma barra vermelha. A mensagem de erro diz, expected:<Money.


Franc@31aebf> but was: <Money.Money@478a43>. No to til como talvez gos-
taramos. Podemos definir toString() para nos dar uma mensagem de erro melhor:

Money
public String toString() {
return amount + " " + currency;
}

Uau! Cdigo sem um teste? Voc pode fazer isso? Poderamos certamente ter
escrito um teste para toString() antes de codific-lo. Contudo:

Estamos prestes a ver os resultados na tela.


Devido a toString() ser usado apenas para sada de debug, o risco de falha
baixo.
J temos uma barra vermelha e preferimos no escrever um teste quando
temos uma barra vermelha.

Exceo anotada.
Agora, a mensagem de erro diz: expected:<10 CHF> but was:<10 CHF>. Est um
pouco melhor, mas ainda confuso. Ns temos o dado certo na resposta, mas a clas-
se est errada Money em vez de Franc. O problema est na nossa implementao
de equals():

Money
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount
&& getClass().equals(money.getClass());
}
68 Parte I O Exemplo Financeiro

Realmente deveramos verificar se as moedas so as mesmas, no se as classes so


as mesmas.
Preferimos no escrever um teste quando temos uma barra vermelha. Mas,
estamos prestes a mudar o cdigo modelo real e no podemos mudar o cdigo mo-
delo sem um teste. O curso conservador reverter a mudana que causou a barra
vermelha, e assim estamos de volta ao verde. Ento, podemos mudar o teste para
equals(), corrigir a implementao e repetir a mudana original.
Dessa vez, seremos conservadores. (s vezes, sigo adiante e escrevo um teste
no vermelho, mas no enquanto as crianas esto acordadas.)

Franc
Money times(int multiplier) {
return new Franc(amount * multiplier, currency);
}

Isso nos leva de volta ao verde. A situao que tnhamos era um Franc(10, CHF) e
um Money(10, CHF) que foram relatados no serem iguais. Podemos usar exata-
mente isso para nosso teste:

public void testDifferentClassEquality() {


assertTrue(new Money(10, "CHF").equals(new Franc(10, "CHF")));
}

Falha, como esperado. O cdigo de equals() deveria comparar moedas, no clas-


ses:

Money
public boolean equals(Object object) {
Money money = (Money) object;
return amount == money.amount
&& currency().equals(money.currency());
}

Agora podemos retornar um Money de um Franc.times() e ainda passar nos


testes:

Franc
Money times(int multiplier) {
return new Money(amount * multiplier, currency);
}

O mesmo funcionar para Dollar.times()?

Dollar
Money times(int multiplier) {
return new Money (amount * multiplier, currency);
}
Captulo 10 Tempos Interessantes 69

Sim! Agora que as duas implementaes so idnticas, podemos mov-las para


cima.

Money
Money times(int multiplier) {
return new Money(amount * multiplier, currency);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?

Com a multiplicao no lugar, estamos preparados para eliminar as subclas-


ses estpidas. Para revisar:

Conciliamos dois mtodos times() pela otimizao dos mtodos que


eles chamavam e pela substituio de constantes por variveis.
Escrevemos um toString() sem um teste apenas para nos ajudar a depurar.
Tentamos uma mudana (retornar Money em vez de Franc) e deixamos os
testes nos dizerem se funcionou.
Retrocedemos um experimento e escrevemos outro teste. Fazer o teste
funcionar fez o experimento funcionar.
Captulo 11
A Raiz de Todo o Mal

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?

As duas subclasses, Dollar e Franc, tm somente seus construtores. Mas, como um


construtor no motivo suficiente para termos uma subclasse, queremos deletar
as subclasses.
Podemos substituir referncias para as subclasses por referncias pela super-
classe sem mudar o sentido do cdigo. Primeiro, Franc:

Franc
static Money franc(int amount) {
return new Money(amount, "CHF");
}
72 Parte I O Exemplo Financeiro

Ento, Dollar:

Dollar
static Money dollar(int amount) {
return new Money(amount, "USD");
}

Agora, no h referncias para Dollar, ento podemos delet-lo. Franc, por outro
lado, ainda tem uma referncia no teste que acabamos de escrever.
public void testDifferentClassEquality() {
assertTrue(new Money(10, "CHF").equals(new Franc(10, "CHF")));
}

A igualdade est suficientemente coberta em outros lugares para podermos


deletar esse teste? Olhando para o outro teste de igualdade,
public void testEquality() {
assertTrue(Money.dollar(5).equals(Money.dollar(5)));
assertFalse(Money.dollar(5).equals(Money.dollar(6)));
assertTrue(Money.franc(5).equals(Money.franc(5)));
assertFalse(Money.franc(5).equals(Money.franc(6)));
assertFalse(Money.franc(5).equals(Money.dollar(5)));
}

parece que temos os casos para igualdade bem cobertos muito bem cobertos,
realmente. Podemos deletar as terceira e quarta declaraes, pois elas duplicam o
exerccio das primeira e segunda declaraes:

public void testEquality() {


assertTrue(Money.dollar(5).equals(Money.dollar(5)));
assertFalse(Money.dollar(5).equals(Money.dollar(6)));
assertFalse(Money.franc(5).equals(Money.dollar(5)));
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 * 2 = $10
Tornar quantidade privada
Efeitos colaterais em Dollar?
Arredondamento de dinheiro?
equals()
hashCode()
Igualdade de null
Igualdade de objeto
5 CHF * 2 = 10 CHF
Duplicao de Dlar/Franco
Igualdade comum
Multiplicao comum
Comparar Francos com Dlares
Moeda?
Deletar testFrancMultiplication?
Captulo 11 A Raiz de Todo o Mal 73

O teste que escrevemos, que nos fora a comparar moedas em vez de classes,
faz sentido apenas se h mltiplas classes. Devido a estarmos tentando eliminar a
classe Franc, um teste para garantir que o sistema funciona se h uma classe Franc
um fardo e no ajuda. testDifferentClassEquality() vai embora, e Franc vai com
ele. Similarmente, h testes separados para multiplicao de dlares e francos.
Olhando o cdigo, podemos ver que no h diferena na lgica, no momento,
baseados na moeda (havia uma diferena quando havia duas classes). Podemos
deletar testFrancMultiplication() sem perder qualquer confiana no comportamento
do sistema.
Com a classe nica no lugar, estamos prontos para enfrentar adio. Primei-
ro, para revisar:

Terminamos eviscerando subclasses e deletando-as.


Eliminamos testes que faziam sentido com a estrutura do cdigo antigo,
mas que eram redundantes com a nova estrutura de cdigo.
Captulo 12
Adio, Finalmente

$5 + 10 CHF = $10 se a taxa 2:1

um novo dia, e nossa lista de tarefas tornou-se um pouco bagunada, ento co-
piaremos os itens pendentes para uma nova lista. (Eu gosto de copiar fisicamente
itens por fazer para uma nova lista. Se h muitos itenzinhos, eu tendo a faz-los em
vez de copi-los. Coisinhas que de outra forma poderiam se acumular so resolvi-
das s porque sou preguioso. Jogue com seus pontos fortes.)

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10

No estou certo de como escrever a histria de toda a adio, ento comea-


remos com um exemplo mais simples: $5 + $5 = $10.

public void testSimpleAddition() {


Money sum= Money.dollar(5).plus(Money.dollar(5));
assertEquals(Money.dollar(10), sum);
}

Poderamos fazer de conta a implementao apenas retornando Money.dollar(10),


mas a implementao parece bvia. Tentaremos:

Money
Money plus(Money addend) {
return new Money(amount + addend.amount, currency);
}
76 Parte I O Exemplo Financeiro

(Em geral, comearei acelerando as implementaes para salvar rvores e manter


seu interesse. Onde o projeto no bvio, vou utilizar fazer de conta a implemen-
tao e refatorarei. Com isso espero que voc veja como TDD d a voc controle
sobre o tamanho dos passos.)
Tendo dito que estaria indo mais rpido, eu imediatamente irei mais devagar
no em obter os testes funcionando, mas em escrever o teste em si. H vezes e tes-
tes que clamam por uma reflexo cuidadosa. Como vamos representar aritmtica
multi-moeda? Essa uma dessas vezes que requerem reflexo cuidadosa.
A mais difcil restrio de projeto querermos que a maior parte do cdigo
no sistema esteja inconsciente de que est, potencialmente, lidando com mltiplas
moedas. Uma estratgia possvel converter imediatamente todos os valores de
dinheiro para uma moeda de referncia. (Deixarei voc imaginar qual moeda ame-
ricana imperialista de referncia os programadores geralmente escolhem.) Entre-
tanto, isso no permite taxa de cmbio variar facilmente.
Em vez disso, gostaramos de uma soluo que nos deixasse representar
convenientemente mltiplas taxas de cmbio e ainda permitir que a maioria das
expresses similares a expresses aritmticas parecesse com, bem, expresses
aritmticas.
Objetos para o resgate. Quando o objeto que temos no se comporta da
forma que queremos, fazemos outro objeto com o mesmo protocolo externo (um
impostor), mas com uma implementao diferente.
Isso, provavelmente, soa um pouco como mgica. Como sabemos que temos
de pensar em criar um impostor aqui? No vou enganar voc no h frmula para
isso. Ward Cunningham veio com o truque h uma dcada, e eu no o vi ainda in-
dependentemente duplicado, logo, deve ser um belo e complicado truque. TDD no
pode garantir que teremos flashes de ideias no momento certo. Contudo, testes que
do confiana e cdigo fatorado cuidadosamente nos do preparao para ideias e
preparao para aplic-las quando surgirem.
A soluo criar um objeto que age como um Money, mas representa a soma
de dois Moneys. Tentei vrias metforas diferentes para explicar essa ideia. Uma
tratar a soma como uma carteira: voc pode ter vrias notas diferentes de diferen-
tes denominaes e moedas na mesma carteira.
Outra metfora expresso, como em (2 + 3) * 5, ou, no nosso caso, ($2
+ 3CHF) * 5. Um Money a forma atmica de uma expresso. Operaes resultam
em Expressions, uma das quais ser uma Sum. Uma vez que a operao(como a soma
do valor de uma carteira) est completa, a Expression resultante pode ser reduzida
de volta a uma moeda simples dado um conjunto de taxas de cmbio.
Aplicando essa metfora em nosso teste, sabemos que terminamos com:

public void testSimpleAddition() {



assertEquals(Money.dollar(10), reduced);
}

A reduced Expression criada pela aplicao de taxas de cmbio a uma


Expression. O que, no mundo real, aplica taxas cambiais? Um banco. Gostaramos
de estar aptos a escrever:
Captulo 12 Adio, Finalmente 77

public void testSimpleAddition() {



Money reduced= bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}

( um pouco estranho misturar as metforas banco e expresso. Teremos, primei-


ro, toda a histria contada e ento veremos o que podemos fazer sobre seu valor
literrio.)
Fizemos uma importante deciso de projeto aqui. Poderamos facilmente
ter escrito reduce= sum.reduce(USD, bank). Por que fazer o banco responsvel?
Uma resposta : Essa a primeira coisa que me veio cabea, mas isso no
muito informativo. Por que me veio cabea que a reduo deveria ser respon-
sabilidade do banco em vez da expresso? No momento, estou ciente disto:

Expressions parecem estar no cerne do que estamos fazendo. Eu tento man-


ter os objetos do cerne to ignorantes do resto do mundo quanto possvel,
assim eles permanecem flexveis o maior tempo possvel (e continuam f-
ceis de testar, de reusar e de entender).
Posso imaginar que haver muitas operaes envolvendo Expressions. Se
adicionarmos todas as operaes em Expression, ento Expression crescer
sem limite.

Essas no parecem ser razes suficientes para pender a balana permanente-


mente, mas so suficientes para eu comear nessa direo. Tambm estou perfei-
tamente disposto a mover responsabilidades para a reduo de Expression, se isso
fizer com que os Banks no precisem estar envolvidos.
O Bank, em nosso exemplo simples, no precisa fazer realmente nada. En-
quanto temos um objeto, estamos bem:

public void testSimpleAddition() {



Bank bank= new Bank();
Money reduced= bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}

A soma de dois Moneys deveria ser uma Expression:

public void testSimpleAddition() {



Expression sum= five.plus(five);
Bank bank= new Bank();
Money reduced= bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}
78 Parte I O Exemplo Financeiro

Ao menos sabemos com certeza como obter cinco dlares:

public void testSimpleAddition() {


Money five= Money.dollar(5);
Expression sum= five.plus(five);
Bank bank= new Bank();
Money reduced= bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}

Como fazemos para isso compilar? Precisamos de uma interface Expression


(poderamos ter uma classe, mas uma interface ainda mais leve):

Expression
interface Expression

Money.plus() precisa retornar uma Expression,

Money
Expression plus(Money addend) {
return new Money(amount + addend.amount, currency);
}

o que significa que Money tem que implementar Expression (o que fcil, pois no
h operaes ainda):

Money
class Money implements Expression

Precisamos de uma classe Bank vazia,

Bank
class Bank

a qual precisa de um stub reduce():

Bank
Money reduce(Expression source, String to) {
return null;
}

Agora compila, e falha miseravelmente. Ipi! Progresso! Podemos facilmente fazer


de conta a implementao:

Bank
Money reduce(Expression source, String to) {
return Money.dollar(10);
}
Captulo 12 Adio, Finalmente 79

Estamos de volta barra verde e preparados para refatorar. Primeiro, para


revisar:

Reduzimos um grande teste para um menor que representava progresso


($5 + 10 CHF para $5 + $5).
Refletimos cuidadosamente sobre as possveis metforas para nossa com-
putao.
Reescrevemos nossos testes anteriores baseados em nossa nova metfora.
Conseguimos fazer o teste compilar rapidamente.
Fizemos ele rodar.
Esperamos com um pouco de agitao a refatorao necessria para fazer
a implementao real.
Captulo 13
Faa-o

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10

Ns no podemos marcar nosso teste $5 + $5 como feito at que tenhamos remo-


vido toda a duplicao. No temos cdigo duplicado, mas temos, sim, duplicao
de dados o $10 na implementao faz de conta:

Bank
Money reduce(Expression source, String to) {
return Money.dollar(10);
}

realmente o mesmo $5 + $5 no teste:

public void testSimpleAddition() {


Money five= Money.dollar(5);
Expression sum= five.plus(five);
Bank bank= new Bank();
Money reduced= bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}

Antes, quando tnhamos uma implementao faz de conta, era bvio como
voltar atrs e fazer a implementao real. Simplesmente, era uma questo de subs-
tituir constantes por variveis. Dessa vez, entretanto, no bvio para mim como
voltar atrs. Ento, mesmo que parea meio especulativo, seguiremos em frente.
82 Parte I O Exemplo Financeiro

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5

Primeiro, Money.plus() precisa retornar uma Expression real uma Sum, no ape-
nas um Money. (Talvez depois otimizaremos o caso especial de somar duas moedas
idnticas, mas isso mais tarde.)
A soma de dois Moneys deveria ser uma Sum:
public void testPlusReturnsSum() {
Money five= Money.dollar(5);
Expression result= five.plus(five);
Sum sum= (Sum) result;
assertEquals(five, sum.augend);
assertEquals(five, sum.addend);
}

(Voc sabia que o primeiro argumento de adies, em ingls, chamado augend?


Eu no sabia at escrever isso. Alegria nerd.)
O teste acima no o que eu esperaria que vivesse muito. Est profunda-
mente preocupado com a implementao para nossa operao em vez de seu com-
portamento externo visvel. Entretanto, se fizermos ele funcionar, esperamos ter
avanado um passo em direo nossa meta. Para t-lo compilando, tudo o que
precisamos uma classe Sum com dois campos, augend and addend*:

Sum
class Sum {
Money augend;
Money addend;
}

Isso nos d uma ClassCastException, pois Money.plus() est retornando um Money,


no uma Sum:

Money
Expression plus(Money addend) {
return new Sum(this, addend);
}

Sum precisa de um construtor:

Sum
Sum(Money augend, Money addend) {
}

* N. de R. T.: Augend o nome em ingls da primeira parcela da adio; Addend o nome em ingls
da segunda parcela da adio. Ambos referem-se a um nmero ou quantidade qual outro nmero ou
quantidade somado para produzir o resultado (soma).
Captulo 13 Faa-o 83

E Sum precisa ser um tipo de Expression:

Sum
class Sum implements Expression

Agora, o sistema compila de novo, mas o teste ainda falha: dessa vez porque
o construtor Sum no est iniciando os campos. (Poderamos fazer de conta a imple-
mentao pela inicializao dos campos, mas eu disse que andaria mais rpido.)

Sum
Sum(Money augend, Money addend) {
this.augend= augend;
this.addend= addend;
}

Agora, Bank.reduce() est recebendo uma Sum. Se as moedas na Sum so as mes-


mas e a moeda alvo tambm a mesma, ento o resultado deveria ser um Money cuja
quantidade a soma das quantidades:

public void testReduceSum() {


Expression sum= new Sum(Money.dollar(3), Money.dollar(4));
Bank bank= new Bank();
Money result= bank.reduce(sum, "USD");
assertEquals(Money.dollar(7), result);
}

Eu escolhi cuidadosamente parmetros que estragariam o teste existente.


Quando reduzimos uma Sum, o resultado (sob essas circunstncias simplificadas)
deveria ser um Money cuja quantidade a soma das quantidades dos dois Moneys e
cuja moeda a moeda que estamos reduzindo.

Bank
Money reduce(Expression source, String to) {
Sum sum= (Sum) source;
int amount= sum.augend.amount + sum.addend.amount;
return new Money(amount, to);
}

Isso imediatamente feio sob dois aspectos:

A converso. Esse cdigo no deveria trabalhar com qualquer Expression.


Os campos pblicos e os dois nveis de referncias para isso.
Bastante fcil de corrigir. Primeiro, podemos mover o corpo do mtodo para
Sum e nos livrarmos de alguns campos visveis. Estamos certos de que precisare-
mos do Bank como parmetro no futuro, mas isso refatorao pura e simples, en-
to vamos deix-lo de fora. (Na verdade, s agora o coloquei, porque eu sabia
que precisaria dele vergonha, devia me envergonhar.)
84 Parte I O Exemplo Financeiro

Bank
Money reduce(Expression source, String to) {
Sum sum= (Sum) source;
return sum.reduce(to);
}

Sum
public Money reduce(String to) {
int amount= augend.amount + addend.amount;
return new Money(amount, to);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )

(O que levanta a questo de como iremos implementar, hum... testar Bank.reduce()


quando o argumento um Money.)
J que a barra est verde e nada h mais bvio para fazer com o cdigo aci-
ma, vamos escrever um teste:

public void testReduceMoney() {


Bank bank= new Bank();
Money result= bank.reduce(Money.dollar(1), "USD");
assertEquals(Money.dollar(1), result);
}

Bank
Money reduce(Expression source, String to) {
if (source instanceof Money) return (Money) source;
Sum sum= (Sum) source;
return sum.reduce(to);
}

Feio, feio, feio. Entretanto, agora temos uma barra verde e a possibilidade
de refatorao. Deveramos estar usando polimorfismo em vez de verificarmos
classes explicitamente a qualquer hora. Devido a Sum implementar reduce(String),
se Money a implementou tambm, poderamos ento adicion-la interface de
Expression.

Bank
Money reduce(Expression source, String to) {
if (source instanceof Money)
return (Money) source.reduce(to);
Sum sum= (Sum) source;
return sum.reduce(to);
}
Captulo 13 Faa-o 85

Money
public Money reduce(String to) {
return this;
}

Se adicionarmos reduce(String) interface de Expression,

Expression
Money reduce(String to);

ento podemos eliminar todas aquelas converses feias e verificaes de classe:

Bank
Money reduce(Expression source, String to) {
return source.reduce(to);
}

No estou inteiramente satisfeito pelo nome do mtodo ser o mesmo em


Expression e em Bank, mas tendo diferentes parmetros em cada um. Eu nun-
ca encontrei uma soluo geral satisfatria para esse problema em Java. Em
linguagens com parmetros com palavras-chave, comunicar a diferena entre
Bank.reduce(Expression, String) e Expression.reduce(String) bem suportado pela sin-
taxe da linguagem. Com parmetros posicionais, no to fcil fazer o cdigo
falar por ns sobre como so diferentes.

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)

Em seguida, trocaremos realmente de uma moeda para outra. Primeiro, para


revisar:

No marcamos um teste como feito, pois a duplicao no tinha sido


eliminada.
Seguimos em frente em vez de voltarmos atrs para tornarmos real a im-
plementao.
Escrevemos um teste para forar a criao de um objeto que ns esperva-
mos precisar depois (Sum).
Comeamos a implementar mais rpido (o construtor de Sum).
Implementamos cdigo com converses em um lugar, e ento movemos o
cdigo para onde pertencia uma vez que os testes estavam rodando.
Introduzimos polimorfismo para eliminar verificao explcita de classes.
Captulo 14
Mudana

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)

Vale a pena acolher a mudana (especialmente se voc tem um livro com aco-
lha as mudanas no ttulo*). Aqui, entretanto, estamos pensando em uma forma
muito mais simples de mudana temos dois francos e queremos um dlar. Isso j
soa como um caso de teste:

public void testReduceMoneyDifferentCurrency() {


Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Money result= bank.reduce(Money.franc(2), "USD");
assertEquals(Money.dollar(1), result);
}

Quando converto francos para dlares, eu divido por dois. (Ainda estamos
ignorando todos aqueles desagradveis problemas numricos.) Podemos fazer a
barra ficar verde fazendo algo feio:

Money
public Money reduce(String to) {
int rate = (currency.equals("CHF") && to.equals("USD"))

* N. de R. T.: Aluso ao (sub)ttulo do livro de Kent Beck sobre XP: BECK, Kent. Programao Ex-
trema explicada: acolha as mudanas. Porto Alegre: Bookman, 2004.
88 Parte I O Exemplo Financeiro

? 2
: 1;
return new Money(amount / rate, to);
}

Agora, de repente, Money conhece taxas de cmbio. Eca! O Bank deveria ser o
nico lugar em que nos preocupamos com taxas de cmbio. Teremos que passar o
Bank como um parmetro para Expression.reduce(). (V? Ns sabamos que preci-
saramos disso e estvamos certos. Nas palavras do av em A Princesa Prometida,
Voc muito esperto...) Primeiro, o chamador:

Bank
Money reduce(Expression source, String to) {
return source.reduce(this, to);
}

Ento os implementadores:

Expression
Money reduce(Bank bank, String to);

Sum
public Money reduce(Bank bank, String to) {
int amount= augend.amount + addend.amount;
return new Money(amount, to);
}

Money
public Money reduce(Bank bank, String to) {
int rate = (currency.equals("CHF") && to.equals("USD"))
? 2
: 1;
return new Money(amount / rate, to);
}

Os mtodos tm que ser pblicos, pois mtodos na interface tm que ser pblicos
(por alguma razo excelente, estou certo).
Agora conseguimos calcular a taxa no Bank:

Bank
int rate(String from, String to) {
return (from.equals("CHF") && to.equals("USD"))
? 2
: 1;
}

E pedir ao bank a taxa correta:

Money
public Money reduce(Bank bank, String to) {
int rate = bank.rate(currency, to);
Captulo 14 Mudana 89

return new Money(amount / rate, to);


}

Aquele maldito 2 ainda aparece no teste e no cdigo. Para nos livrarmos dele,
precisamos manter uma tabela de taxas no Bank e procurar uma taxa quando pre-
cisarmos dela. Poderamos usar uma tabela hash que mapeia pares de moedas em
taxas. Podemos usar um array de elementos duplos contendo as duas moedas como
chaves? Uma verificao com Array.equals() consegue ver se os elementos so iguais?

public void testArrayEquals() {


assertEquals(new Object[] {"abc"}, new Object[] {"abc"});
}

No. O teste falha, e, assim, temos que criar um objeto real para a chave:

Pair
private class Pair {
private String from;
private String to;
Pair(String from, String to) {
this.from= from;
this.to= to;
}
}

Devido a estarmos usando Pairs como chaves, teremos que implementar


equals() e hash-Code(). No vou escrever testes para eles, pois estamos escrevendo
esse cdigo no contexto de uma refatorao. Se tivermos o xito da refatorao e
todos os testes rodarem, ento esperamos que o cdigo tenha sido exercitado. Se
estivesse programando com algum que no visse exatamente aonde estvamos
indo com isso, ou se a lgica se tornasse um pouco menos complexa, eu comearia
a escrever testes separados.

Pair
public boolean equals(Object object) {
Pair pair= (Pair) object;
return from.equals(pair.from) && to.equals(pair.to);
}
public int hashCode() {
return 0;
}

0 um valor de hash terrvel, mas tem a vantagem de ser fcil de implementar


e de nos deixar fazer funcionar rapidamente. A procura por moeda parecer uma
busca linear. Depois, quando tivermos balaios de moedas, podemos fazer um tra-
balho mais profundo com dados de uso real.
Precisamos de algum lugar para armazenar as taxas:

Bank
private Hashtable rates= new Hashtable();
90 Parte I O Exemplo Financeiro

Precisamos definir a taxa quando dito:

Bank
void addRate(String from, String to, int rate) {
rates.put(new Pair(from, to), new Integer(rate));
}

E ento podemos procurar a taxa quando pedido:

Bank
int rate(String from, String to) {
Integer rate= (Integer) rates.get(new Pair(from, to));
return rate.intValue();
}

Espere um minuto! Temos uma barra vermelha. O que aconteceu? Uma breve
bisbilhotada nos diz que se pedirmos a taxa de USD para USD, esperamos que o
valor seja 1. Como isso foi uma surpresa, vamos escrever um teste para comunicar
o que descobrimos:

public void testIdentityRate() {


assertEquals(1, new Bank().rate("USD", "USD"));
}

Agora temos trs erros, mas esperamos que todos eles sejam corrigidos com uma
mudana:

Bank
int rate(String from, String to) {
if (from.equals(to)) return 1;
Integer rate= (Integer) rates.get(new Pair(from, to));
return rate.intValue();
}

Barra verde!

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Captulo 14 Mudana 91

Em seguida, implementaremos nosso ltimo grande teste, $5 + 10 CHF. V-


rias tcnicas significativas escorregaram nesse captulo. Por agora, para revisar:

Adicionamos um parmetro, em segundos, que espervamos ser neces-


srio.
Refatoramos a duplicao de dados entre cdigo e testes.
Escrevemos um teste (testArrayEquals) para verificar uma suposio sobre
a operao de Java.
Introduzimos uma classe privada auxiliar sem distinguir testes dela mesma.
Cometemos um erro em uma refatorao e escolhemos seguir em frente,
escrevendo outro teste para isolar o problema.
Captulo 15
Moedas Misturadas

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)

Agora ns finalmente estamos preparados para adicionar o teste que comeou isso
tudo, $5 + 10 CHF:

public void testMixedAddition() {


Expression fiveBucks= Money.dollar(5);
Expression tenFrancs= Money.franc(10);
Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}

Isso o que gostaramos de escrever. Infelizmente, h uma srie de erros de com-


pilao. Quando estvamos generalizando de Money para Expression, deixamos um
monte de pontas soltas largadas em volta. Eu estava preocupado com elas, mas
no quis perturbar voc. Chegou a hora.
No seremos capazes de fazer o teste anterior compilar rapidamente. Ns
faremos a primeira mudana, que provocar a prxima e a prxima. Temos dois
caminhos a seguir. Podemos faz-lo funcionar rapidamente escrevendo um teste
mais especfico e, ento, generalizando, ou podemos confiar que nosso compilador
no nos deixar cometer erros. Estou com voc vamos devagar (na prtica, eu
provavelmente apenas arrumaria as mudanas em cascata uma de cada vez).
94 Parte I O Exemplo Financeiro

public void testMixedAddition() {


Money fiveBucks= Money.dollar(5);
Money tenFrancs= Money.franc(10);
Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}

O teste no funciona. Temos 15 USD em vez de 10 USD. como se


Sum.reduce() no estivesse reduzindo os argumentos. No :

Sum
public Money reduce(Bank bank, String to) {
int amount= augend.amount + addend.amount;
return new Money(amount, to);
}

Se reduzirmos ambos os argumentos, o teste dever passar:

Sum
public Money reduce(Bank bank, String to) {
int amount= augend. reduce(bank, to).amount
+ addend.reduce(bank, to).amount;
return new Money(amount, to);
}

E ele passa. Agora podemos comear a catar em Moneys o que deveria ser Expressions.
Para evitar o efeito cascata, comearemos pelas bordas e trabalharemos nosso ca-
minho de volta ao caso de teste. Por exemplo, o augend e o addend podem agora
ser Expressions:

Sum
Expression augend;
Expression addend;

Os argumentos para o construtor de Sum tambm podem ser Expressions:

Sum
Sum(Expression augend, Expression addend) {
this.augend= augend;
this.addend= addend;
}

(Sum est comeando a me lembrar de Composite*, mas no tanto que eu queira


generalizar. No momento, queremos uma Sum com dois parmetros diferentes, em-

* N. de R. T.: Padro GoF Composite para representao e tratamento uniforme de estruturas hie-
rrquicas recursivas; ver mais detalhes em GAMMA, Erich et al. Padres de projeto: solues reutilizveis
de software orientado a objetos. Porto Alegre: Bookman, 2000.
Captulo 15 Moedas Misturadas 95

bora eu esteja pronto para transform-la.) Por ora, isso tudo para Sum e o que
temos para o Money?
O argumento para plus() pode ser uma Expression:

Money
Expression plus(Expression addend) {
return new Sum(this, addend);
}

Times() pode retornar uma Expression:

Money
Expression times(int multiplier) {
return new Money(amount * multiplier, currency);
}

Isso sugere que Expression deveria incluir as operaes plus() e times(). Por ora, isso
o suficiente para Money. Agora podemos mudar o argumento para plus() em nosso
caso de teste:
public void testMixedAddition() {
Money fiveBucks= Money.dollar(5);
Expression tenFrancs= Money.franc(10);
Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}

Quando mudamos fiveBucks para uma Expression, temos que fazer muitas mu-
danas. Felizmente, temos a lista de tarefas do compilador para manter-nos foca-
dos. Primeiro, fazemos a mudana:
public void testMixedAddition() {
Expression fiveBucks= Money.dollar(5);
Expression tenFrancs= Money.franc(10);
Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Money result= bank.reduce(fiveBucks.plus(tenFrancs), "USD");
assertEquals(Money.dollar(10), result);
}

Estamos dizendo, educadamente, que plus() no est definido para Expressions.


Ns o definimos:

Expression
Expression plus(Expression addend);

E, ento, temos que adicion-lo a Money e a Sum. Money? Sim, ele tem que ser pblico
em Money:

Money
public Expression plus(Expression addend) {
96 Parte I O Exemplo Financeiro

return new Sum(this, addend);


}

Vamos apenas fazer um stub da implementao em Sum e adicion-la em nossa


lista de tarefas:

Sum
public Expression plus(Expression addend) {
return null;
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Sum.plus
Expression.times

Agora que o programa compila, todos os testes rodam.


Estamos preparados para terminar a generalizao de Money para Expression.
Mas, primeiro, para revisar:

Escrevemos o teste que queramos, ento recuamos para faz-lo possvel


em um passo.
Generalizamos (usando uma declarao mais abstrata) das folhas de volta
raiz (o caso de teste).
Seguimos o compilador quando fizemos uma mudana (Expression fiveBucks),
o que causou mudanas em cascata (added plus() em Expression, e assim
por diante).
Captulo 16
Abstrao, Finalmente

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Sum.plus
Expression.times

Precisamos implementar Sum.plus() para terminar Expression.plus, e, ento, precisa-


mos de Expression.times(), e, ento, teremos terminado todo o exemplo. Aqui est
o teste para Sum.plus():
public void testSumPlusMoney() {
Expression fiveBucks= Money.dollar(5);
Expression tenFrancs= Money.franc(10);
Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum= new Sum(fiveBucks, tenFrancs).plus(fiveBucks);
Money result= bank.reduce(sum, "USD");
assertEquals(Money.dollar(15), result);
}

Poderamos ter criado uma Sum adicionando fiveBucks e tenFrancs, mas a


forma acima, na qual explicitamente criamos a Sum, comunica mais diretamente.
Estamos escrevendo esses testes no apenas para tornar nossa experincia de
programao mais divertida e gratificante, mas tambm como uma Pedra de
98 Parte I O Exemplo Financeiro

Roseta* para geraes futuras apreciarem nossa genialidade. Pense, pense, em


nossos leitores.
O teste, nesse caso, mais longo que o cdigo. O cdigo o mesmo cdigo
em Money. (Ser que eu ouvi uma classe abstrata ao longe?)

Sum
public Expression plus(Expression addend) {
return new Sum(this, addend);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Sum.plus
Expression.times

Voc terminar, aproximadamente, com o mesmo nmero de linhas no c-


digo do teste e no cdigo modelo quando implementar TDD. Para TDD fazer
sentido em termos de economia, voc precisar ser capaz de escrever o dobro de
linhas por dia do que escrevia antes, ou escrever metade daquela quantidade de li-
nhas para a mesma funcionalidade. Ter que medir e ver que efeitos TDD tem por
sua prpria prtica. Certifique-se, no entanto, de considerar tempos de depurao,
integrao e explicao em suas mtricas.

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Sum.plus
Expression.times

Se conseguirmos fazer Sum.times() funcionar, ento declarar Expression.times() ser


um passo s. O teste :

public void testSumTimes() {


Expression fiveBucks= Money.dollar(5);

* N. de R. T: Pedra de Roseta o nome da pedra encontrada em Roseta, no Egito, contendo o mesmo


texto escrito em 3 lnguas (grego clssico, demtico egpcio e hierglifos) e que foi fundamental para a
decifrao da escrita hieroglfica por J-F. Champollion em 1822.
Captulo 16 Abstrao, Finalmente 99

Expression tenFrancs= Money.franc(10);


Bank bank= new Bank();
bank.addRate("CHF", "USD", 2);
Expression sum= new Sum(fiveBucks, tenFrancs).times(2);
Money result= bank.reduce(sum, "USD");
assertEquals(Money.dollar(20), result);
}

Novamente, o teste mais longo que o cdigo. (Vocs, nerds do JUnit, sabero
como corrigir isso o resto de vocs ter que ler Fixture*.)

Sum
Expression times(int multiplier) {
return new Sum(augend.times(multiplier),addend.times(multiplier));
}

Devido a abstrairmos augend e addend para Expressions no ltimo captulo,


temos agora que declarar times() em Expression para o cdigo compilar:

Expression
Expression times(int multiplier);

Isso nos fora a aumentar a visibilidade de Money.times() e Sum.times():

Sum
public Expression times(int multiplier) {
return new Sum(augend.times(multiplier),addend.times(multiplier));
}

Money
public Expression times(int multiplier) {
return new Money(amount * multiplier, currency);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Sum.plus
Expression.times

E isso funciona.

* N. de R. T.: Fixture o conjunto de recursos ou dados comuns necessrios para a execuo dos
testes. Em geral, as classes so instanciadas ANTES da execuo de cada mtodo.
100 Parte I O Exemplo Financeiro

A nica ponta solta para amarrar experimentar o retorno de um Money quan-


do somamos $5 + $5. O teste seria:

public void testPlusSameCurrencyReturnsMoney() {


Expression sum= Money.dollar(1).plus(Money.dollar(1));
assertTrue(sum instanceof Money);
}

Esse teste um pouco feio, pois est testando as entranhas da implementao e no


o comportamento externamente visvel dos objetos. Contudo, nos guiar a fazer as
mudanas que precisamos fazer, e isso, afinal de contas, somente um experimento.
Aqui est o cdigo que teramos que modificar para faz-lo funcionar:

Money
public Expression plus(Expression addend) {
return new Sum(this, addend);
}

$5 + 10 CHF = $10 se a taxa 2:1


$5 + $5 = $10
Retornar Money de $5 + $5
Bank.reduce( Money )
Reduzir Money com converses
Reduce(Bank, String)
Sum.plus
Expression.times

No h maneira bvia e limpa (no para mim; em todo o caso, estou certo de
que voc poderia pensar em alguma coisa) de checar a moeda do argumento se, e
somente se, for um Money. O experimento falha, ns deletamos o teste (o qual no
gostamos muito de qualquer jeito), e partimos.
Para revisar:

Escrevemos um teste com os futuros leitores em mente.


Sugerimos um experimento que compare TDD com seu estilo de progra-
mao atual.
Mais uma vez tivemos mudanas de declaraes ondulando pelo sistema,
e mais uma vez seguimos os conselhos do compilador para corrigi-las.
Tentamos um breve experimento, ento o descartamos quando no deu
certo.
Captulo 17
Retrospectiva Financeira

Vamos dar uma olhada para trs tanto no processo que usamos quanto nos resul-
tados do exemplo financeiro. Vamos olhar:

Qual o prximo passo?


Metfora O efeito dramtico que a metfora tem sobre a estrutura do
projeto.
Uso de JUnit Quando rodamos os testes e como usamos o JUnit.
Mtricas de Cdigo Um resumo numrico do cdigo resultante.
Processo Dizemos vermelho/verde/refatore, mas quanto trabalho h em
cada passo?
Qualidade de Teste Como testes TDD empilham-se em comparao a
mtricas de teste convencionais?

Qual o prximo passo?


O cdigo est terminado? No. H aquela duplicao desagradvel en-
tre Sum.plus() e Money.plus(). Se fizemos de Expression uma classe em vez de uma
interface (no sendo a direo usual, visto que as classes, mais frequentemente,
tornam-se interfaces), teramos um hbitat natural para o cdigo comum.
Eu no acredito em terminado. TDD pode ser usado como uma maneira
de lutar pela perfeio, mas esse no seu uso mais eficaz. Se voc tem um sistema
grande, ento as partes que voc mexe o tempo todo deveriam ser rocha absoluta-
mente slida, assim voc pode fazer mudanas dirias com confiana. Conforme
voc navega para a periferia do sistema, para partes que no mudam com frequn-
102 Parte I O Exemplo Financeiro

cia, os testes podem ser mais irregulares e o projeto mais feio sem interferir em sua
confiabilidade.
Quando fao todas as tarefas bvias, gosto de rodar um analisador de cdigo*
como Small-Lint para Smalltalk. Muitas das sugestes que surgem eu j conheo ou
no concordo. Todavia, analisadores automticos no esquecem, logo, se no dele-
to uma implementao obsoleta, no tenho estresse. O analisador vai indic-la.
Outra questo qual o prximo passo? : De que testes adicionais eu
preciso? s vezes, voc pensa em um teste que no deveria funcionar, e ele
funciona. Ento voc precisa descobrir o porqu. s vezes, um teste que no deve-
ria funcionar realmente no funciona, e voc pode grav-lo como uma limitao
conhecida ou como trabalho a ser feito depois.
Finalmente, quando a lista est vazia uma boa hora para rever o projeto. As
palavras e os conceitos jogam juntos? H duplicao que difcil de eliminar dado
o atual projeto? (Duplicao duradoura um sintoma de projeto latente.)

Mtafora
A maior surpresa para mim na codificao do exemplo financeiro foi como saiu
diferente dessa vez. Programei dinheiro em produo ao menos trs vezes, que
consigo pensar. Eu o usei como exemplo ilustrativo outra meia dzia de vezes.
Programei-o ao vivo no palco (relaxe, no to excitante como soa) outras quinze
vezes. Codifiquei outras trs ou quatro vezes me preparando para escrever (ras-
gando a Parte I e reescrevendo-a baseado em revises anteriores). Ento, enquanto
estava escrevendo isso, pensei em usar expresso como metfora, e o projeto foi
em uma direo completamente diferente de antes.
Eu realmente no esperava que a metfora fosse to poderosa. Uma metfora
deveria ser apenas uma fonte de nomes, no deveria? Aparentemente no.
A metfora que Ward Cunningham usou para diversos dinheiros juntos com
moedas potencialmente diferentes foi um vetor, como um vetor matemtico em
que os coeficientes eram moedas em vez de x2. Usei MoneySum por um tempo, ento
MoneyBag (que melhor e fsico), e, finalmente, Wallet (que mais comum na expe-
rincia da maioria das pessoas). Todas essas metforas implicam que a coleo de
Moneys plana. Por exemplo, 2 USD + 5 CHF + 3 USD resultaria em 5 USD + 5 CHF.
Dois valores com a mesma moeda seriam mesclados.
A metfora expresso me libertou de um monte de problemas desagradveis
sobre mesclar moedas duplicadas. O cdigo surgiu mais claro e mais limpo do que

* N. de R. T.: Analisador de cdigo (ou, mais completamente, analisador esttico de cdigo) uma
ferramenta para anlise automtica de cdigo, que detecta comandos/condies potencialmente suspei-
tos (por exemplo, retorno de funo que no usado, mtodos definidos mas no usados) e gerando
alertas (warnings) para a equipe. Ver mais informaes e uma lista de analisadores (entre os quais o
clebre Lint para a linguagem C e o Small-Lint citado no texto) na wikipedia (na inglesa, procure por
static code analysis).
Captulo 17 Retrospectiva Financeira 103

jamais havia visto. Estou preocupado com a performance de expresses, mas estou
feliz em esperar at ver algumas estatsticas de uso antes de comear a otimizar.
E se eu tiver que escrever 20 vezes tudo o que j escrevi? Continuaria a en-
contrar ideias e surpresas a cada vez? H algum jeito de ser mais atento conforme
programo, de forma a poder ter todas as ideias nas primeiras trs vezes? Na pri-
meira vez?

Uso de JUnit
Eu tinha JUnit para manter um log enquanto eu estava codificando o exemplo
financeiro. Eu apertei o boto Run exatamente 125 vezes. Devido a eu estar es-
crevendo ao mesmo tempo em que estava programando, o intervalo entre as exe-
cues no foi representativo, mas durante as vezes em que estava apenas pro-
gramando, rodei os testes cerca de uma vez por minuto. Somente uma vez, nesse
tempo todo, fui surpreendido pelo sucesso ou falha, e isso foi em uma refatorao
feita s pressas.
A Figura 17.1 um histograma do intervalo de tempo entre os testes execu-
tados. O grande nmero de intervalos longos provavelmente por causa do tempo
que eu gastava escrevendo.

50

45

40

35
Ocorrncias

30

25

20

15

10

0
0 1 <5 < 10 >= 10

Minutos entre Execues

Figura 17.1 Histograma do intervalo de tempo entre execuo de testes.


104 Parte I O Exemplo Financeiro

Mtricas de cdigo
A Tabela 17.1 d algumas estatsticas sobre o cdigo.

Tabela 17.1 Mtricas de cdigo


Funcional Teste
Classes 5 1
Funes (1) 22 15
Linhas (2) 91 89
Complexidade ciclomtica (3) 1,04 1
Linhas/funo 4,1 (4) 5,9 (5)

(1) Devido a no termos implementado a API completa, no podemos avaliar


o nmero absoluto de funes, ou o nmero de funes por classe, ou
o nmero de linhas por classe. Contudo, os valores so instrutivos. H
aproximadamente a mesma quantidade de linhas e funes no teste e no
cdigo funcional.
(2) O nmero de linhas de cdigo teste pode ser reduzido extraindo padres
comuns. Todavia, a correspondncia aproximada entre linhas do cdigo
modelo e linhas do cdigo teste permanecer.
(3) Complexidade ciclomtica uma medida convencional de complexidade de
fluxo. Complexidade de teste 1, pois no h decises ou laos no cdigo
teste. A complexidade do cdigo funcional baixa por causa do uso pesado
de polimorfismo como um substituto para controle de fluxo explcito.
(4) Isso inclui o cabealho da funo e as chaves.
(5) Linhas por funo nos testes so infladas porque no fatoramos cdigo
comum, como explicado na seo anterior.

Processo
O ciclo do TDD como segue:

Adicione um pequeno teste


Rode todos os testes e falhe
Faa uma mudana
Rode todos os testes e seja bem-sucedido
Refatore para remover duplicao
Captulo 17 Retrospectiva Financeira 105

Partindo do princpio de que escrever um teste um nico passo, quantas mu-


danas so necessrias para compilar, executar e refatorar? (Por mudana, quero
dizer mudar uma definio de mtodo ou classe.) A Figura 17.2 mostra um histo-
grama do nmero de mudanas para cada um dos testes financeiros que voc viu.
Eu espero que, se reunimos dados para um grande projeto, o nmero de mu-
danas para compilar e executar permanea relativamente pequeno (eles poderiam
ser inclusive menores se o ambiente de programao entender o que os testes esto
tentando dizer criando stubs automaticamente, por exemplo). Contudo, (e aqui
est ao menos uma dissertao de mestrado) o nmero de mudanas por refato-
rao deveria seguir uma cauda pesada ou perfil leptocrtico, o qual como a
curva de um sino, mas com mais mudanas radicais que o previsto por uma curva
de sino padro. Muitas medidas na natureza seguem esse perfil, tal como variaes
de preo no mercado de aes.1

Qualidade de teste
Os testes que so um subproduto natural de TDD so certamente bastante teis
para serem mantidos enquanto o sistema estiver rodando. No espere que eles
substituam os outros tipos de teste:

Performance
Estresse
Usabilidade

10

8
Ocorrncias

0
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28

Nmero de Mudanas

Figura 17.2 Nmero de mudanas por refatorao.

1
Mandelbrot, Benoit, ed. 1997. Fractals and Scaling in Finance. New York: Springer-Verlag. ISBN:
0387983635.
106 Parte I O Exemplo Financeiro

Contudo, se a densidade de defeitos do cdigo guiado por testes suficien-


temente baixa, ento o papel do teste profissional inevitavelmente mudar de
superviso de um adulto para algo mais parecido com um amplificador de
comunicao entre aqueles que geralmente tm um feeling para o que o sistema
deveria fazer e aqueles que o faro. Como um entendimento para uma conversa
longa e interessante sobre o futuro do teste profissional, aqui esto algumas medi-
das amplamente compartilhadas pelos testes descritos.

Cobertura de comando certamente no uma medida suficiente de quali-


dade de teste, mas um ponto de partida. TDD seguido religiosamente de-
veria resultar em 100% de cobertura de comandos. JProbe (www.sitraka.
com/software/jprobe) informa apenas uma linha em um mtodo no co-
berta pelos casos de teste Money.toString(), o qual adicionamos explicita-
mente como uma ajuda de depurao e no como um cdigo modelo real.
Insero de defeitos outra forma de avaliar qualidade de teste. A ideia
simples: mude o significado de uma linha de cdigo e um teste dever
parar de funcionar. Voc pode fazer isso manualmente, ou usando uma
ferramenta como Jester (jester.sourceforge.net). Jester informa apenas
uma linha capaz de mudar sem parar de funcionar, Pair.hashCode(). Ns
podemos fazer de conta a implementao para apenas retornar 0 (zero).
Retornar uma constante diferente no muda realmente o significado do
programa (um nmero faz de conta to bom como qualquer outro),
ento isso no realmente um defeito que foi inserido.

Phlip, um de meus revisores, fez uma observao sobre a cobertura de teste


que vale a pena ser repetida aqui. Uma medida grosseira de cobertura o nmero
de testes que testam diferentes aspectos de um programa dividido pelo nmero de
aspectos que precisam de teste (a complexidade da lgica). Uma forma de melho-
rar a cobertura escrever mais testes, e da vem a diferena dramtica entre o n-
mero de testes que um desenvolvedor dirigido por testes escreveria para o cdigo
e o nmero de testes que um testador profissional escreveria. (O Captulo 32 d
detalhes sobre um exemplo em que escrevi 6 testes e um testador escreveu 65 testes
para o mesmo problema.) Contudo, outra forma de melhorar a cobertura pegar
um conjunto fixo de testes e simplificar a lgica do programa. O passo de refato-
rao frequentemente tem esse efeito condicionais substitudos por mensagens,
ou por nada. Nas palavras de Phlip, em vez de aumentar a cobertura de testes
para caminhar por todas as permutaes das entradas (mais propriamente, uma
amostra de todas as permutaes possveis), apenas deixamos os mesmos testes
cobrirem vrias permutaes do cdigo conforme ele diminui.
Captulo 17 Retrospectiva Financeira 107

Uma ltima reviso


Os trs itens que surgem de novo como surpresas ao ensinar TDD so:

As trs abordagens para fazer um teste funcionar de forma limpa fazer


de conta, triangulao e implementao bvia.
Remover duplicao entre teste e cdigo como uma forma de guiar o
projeto.
A habilidade de controlar a lacuna entre testes para aumentar a trao
quando a estrada fica escorregadia e para viajar mais rpido quando as
condies esto boas.
Parte II
O Exemplo xUnit

Como, como, falar sobre a implementao de uma ferramenta para desenvolvi-


mento guiado por testes? Guiado por testes, naturalmente.
A arquitetura xUnit apareceu suavemente em Python, por isso vou trocar
para Python na Parte II. No se preocupe, darei uma comentadinha sobre Python
para aqueles de vocs que nunca a viram antes. Quando estiver pronto, voc ter
uma introduo Python, ser capaz de escrever seu prprio framework de testes
e ter visto um exemplo mais complicado de TDD trs pelo preo de um.
Captulo 18
Primeiros Passos para xUnit

Dirigir uma ferramenta de testes usando a prpria ferramenta de testes para rodar
os testes pode parecer um pouco com realizar uma cirurgia cerebral em si prprio.
(No mexa nesses centros motores opa, muito ruim, fim de jogo.) Ficar estra-
nho de vez em quando. Entretanto, a lgica do framework de testes mais compli-
cada que o exemplo financeiro fraquinho da Parte I. Voc pode ler a Parte II como
um passo em direo ao desenvolvimento guiado por testes de softwares reais.
Voc pode l-lo como um exerccio de cincia da computao em programao
com autorreferncia.
Primeiro, precisamos ser capazes de criar um caso de teste e rodar um mtodo
de teste. Por exemplo: TestCase(testMethod).run(). Temos um problema de inicia-
lizao (bootstrap): estamos escrevendo casos de teste para testar um framework
que ser usado para escrever casos de teste. Ainda no temos um framework, en-
to teremos que verificar a operao do primeiro pequeno passo mo. Felizmen-
te, estamos bem descansados e relaxados e longe de cometer erros, e por isso que
iremos a passos pequeninos, verificando tudo de diversas maneiras. Aqui est a
lista de tarefas que vm mente para um framework de testes.

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados

Ainda estamos trabalhando no primeiro teste, certamente. Para nosso pri-


meiro prototeste, precisamos de um pequeno programa que mostrar verdadeiro
se um mtodo de teste for chamado, e falso caso contrrio. Se tivermos um caso de
112 Parte II O Exemplo xUnit

teste que inicializa uma flag dentro do mtodo de teste, ento podemos mostrar a
flag depois que tivermos feito e tivermos certeza de que est correto. Uma vez que
tenhamos verificado isso manualmente, podemos automatizar o processo.
Aqui est a estratgia para nosso teste de inicializao (bootstrap): criaremos
um caso de teste que contenha uma flag. Antes de o mtodo de teste ser executado,
a flag deveria ser falsa. O mtodo de teste inicializar a flag. Depois que esse mto-
do de teste rodar, a flag dever ser verdadeira. Ns chamaremos a classe TestCase de
WasRun, pois um caso de teste que informa se um mtodo rodou. A flag tambm
ser chamada wasRun (talvez confuso, mas um bom nome), ento podemos final-
mente escrever assert test.wasRun (assert um recurso j existente em Python).
Python executa comandos como l um arquivo, ento podemos comear in-
vocando o mtodo de teste manualmente:

test= WasRun("testMethod")
print test.wasRun
test.testMethod()
print test.wasRun

Esperamos que isso exiba (print) None antes do mtodo ser executado, e
1 depois disso. (None em Python como null ou nil, e significa falso, junto com
0 e alguns outros objetos.) Mas ele no faz o que espervamos, pois no definimos
a classe WasRun ainda (teste primeiro, teste primeiro).

WasRun
class WasRun:
pass

(A palavra-chave pass usada quando no h implementao de uma classe ou


mtodo.) Agora somos informados de que precisamos de um atributo wasRun. Pre-
cisamos criar o atributo quando criamos a instncia (o construtor chamado
__init__ por convenincia ). Nele, inicializamos a flag wasRun como falso.

WasRun
class WasRun:
def __init__(self, name):
self.wasRun= None

Executando, o arquivo exibe fielmente None, ento nos diz que precisa-
mos definir o mtodo testMethod. (No seria maravilhoso se sua IDE avisasse isso,
fornecesse um stub e abrisse um editor para ele? N, til demais.)

WasRun
def testMethod(self):
pass

Agora quando executamos o arquivo, vemos None e None. Queremos


ver None e 1. Podemos ter isso inicializando a flag em testMethod():
Captulo 18 Primeiros Passos para xUnit 113

WasRun
def testMethod(self):
self.wasRun= 1

Agora temos a resposta certa a barra verde, ipi! Temos agora um monte
de refatorao para fazer, mas enquanto mantivermos a barra verde, sabemos que
fizemos progresso.
Em seguida, precisamos usar nossa interface real, run(), em vez de chamar o
mtodo de teste diretamente. O teste muda para o seguinte:

test= WasRun("testMethod")
print test.wasRun
test.run()
print test.wasRun

A implementao pode ser construda no momento como:

WasRun
def run(self):
self.testMethod()

E nosso teste volta a exibir os valores certos de novo. Montes de refatoraes tm


essa ideia separar em duas partes para que voc possa trabalhar nelas separada-
mente. Se elas voltarem a se juntar quando voc terminar, legal; se no, voc pode
deix-las separadas. Nesse caso, esperamos criar uma superclasse TestCase, em al-
gum momento, mas, primeiro, temos que diferenciar as partes para nosso nico
exemplo. H provavelmente alguma analogia inteligente com mitose aqui, mas eu
no conheo o suficiente de biologia celular para explic-la.
O prximo passo invocar dinamicamente o testMethod. Uma das caracters-
ticas mais legais de Python que itens como os nomes de classes e mtodos podem
ser tratados como funes (veja a invocao de WasRun acima). Quando temos o
atributo correspondente ao nome do caso de teste, retornamos um objeto que,
1
quando invocado como uma funo, invoca o mtodo.

WasRun
class WasRun:
def __init__(self, name):
self.wasRun= None
self.name= name
def run(self):
method = getattr(self, self.name)
method()

Aqui est outro padro geral de refatorao: pegar cdigo que funciona em
uma instncia e o generalizar para funcionar em vrias, substituindo constantes
por variveis. Aqui a constante foi forada no cdigo, no um valor de dados,
mas o princpio o mesmo. TDD faz bem o trabalho, dando a voc exemplos con-
cretos para generalizar, em vez de ter que generalizar puramente pelo raciocnio.

1
Obrigado a Duncan Booth por corrigir meus erros em Python e por sugerir a soluo em Python.
114 Parte II O Exemplo xUnit

Agora nossa pequena classe WasRun est fazendo dois trabalhos distintos: um
manter o rastro se um mtodo foi invocado ou no, e outro invocar dinami-
camente o mtodo. Hora de um pouco de ao de mitose. Primeiro, criamos uma
superclasse TestCase vazia e fazemos de WasRun uma subclasse:

TestCase
class TestCase:
pass

WasRun
class WasRun(TestCase): . . .

Agora podemos mover o atributo name para a superclasse:

TestCase
def __init__(self, name):
self.name= name

WasRun
def __init__(self, name):
self.wasRun= None
TestCase.__init__(self, name)

Finalmente, o mtodo run() usa apenas atributos da superclasse, ento ele


provavelmente pertence superclasse. (Estou sempre procurando pr as operaes
prximas aos dados.)

TestCase
def __init__(self, name):
self.name= name
def run(self):
method = getattr(self, self.name)
method()

Entre cada uma dessas etapas, eu rodei os testes para ter certeza que estou tendo a
mesma resposta.
Estamos ficando cansados de ver aquele None e 1 mostrados a cada vez.
Usando o mecanismo que recm construmos, podemos escrever agora:

TestCaseTest
class TestCaseTest(TestCase):
def testRunning(self):
test= WasRun("testMethod")
assert(not test.wasRun)
test.run()
assert(test.wasRun)
TestCaseTest("testRunning").run()
Captulo 18 Primeiros Passos para xUnit 115

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados

O corpo do teste est apenas exibindo declaraes transformadas em atribuies,


ento voc poderia ver o que fizemos como uma forma complicada de Extract
Method*.
Vou contar-lhe um segredinho. Eu olho para o tamanho dos passos no desen-
volvimento que recm mostrei para voc, e parece ridculo. Por outro lado, tentei
isso com passos maiores, gastando provavelmente seis horas em tudo (gastei um
tempo procura de coisas de Python) e comeando do zero duas vezes, e, em am-
bas as vezes, pensei ter cdigo funcionando quando no tinha. Esse o pior caso
possvel para TDD, pois estamos tentando superar a etapa de inicializao.
No necessrio trabalhar em passos to pequenos como esses. Uma vez
que voc tenha dominado TDD, ser capaz de trabalhar funcionalidades em saltos
muito maiores entre os casos de teste. Entretanto, para dominar TDD, voc precisa
ser capaz de trabalhar em passos muito pequenos quando eles so necessrios.
A seguir, vamos chamar setUp() antes de rodar o teste. Para revisar, primeiro:

Depois de um monte de falsos comeos orgulhosos, descobrimos como


comear com um passo pequeno.
Implementamos funcionalidade, primeiro forando sua implementao, e
ento tornando-a mais geral, substituindo constantes por variveis.
Usamos Pluggable Selector**, o qual prometemos no usar de novo por
quatro meses no mnimo, pois faz o cdigo difcil de se analisar estatisti-
camente.
Inicializamos nosso framework de testes; tudo em passos pequenos.

* N. de R. T.: Extract Method uma refatorao que extrai um trecho de cdigo de um mtodo e o
torna um mtodo cujo nome explica adequadamente seu propsito. Mais detalhes em http://www.re-
factoring.com/catalog/extractMethod.html ou no livro FOWLER, Martin. Refatorao: aperfeioando
o projeto de cdigo existente. Porto Alegre: Bookman, 2004.
** N. de R. T.: Pluggable Selector o padro de implementao usado quando o nome do mtodo
a ser invocado parametrizvel (neste caso em um atributo) e assim uma classe pode executar lgica
diferente sem subclasses.
Captulo 19
Iniciando

Quando voc comea a escrever testes, descobrir um padro comum (Bill Wake
cunhou o termo 3A para isso).

1. Arranje Crie alguns objetos.


2. Aja Estimule-os.
3. (Faa) Asseres Verifique os resultados.

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados

O primeiro passo, arranje (organize), frequentemente o mesmo de teste em


teste, enquanto a segunda e terceira etapas, agir e (fazer) asseres, so nicas. Eu
tenho um 7 e um 9. Se som-los, eu espero 16; se subtra-los, eu espero -2; e, se
multiplic-los, eu espero 63. O estmulo e os resultados esperados so nicos, mas
o 7 e o 9 no mudam.
Se esse padro se repete em diferentes escalas (e ele se repete, sim), ento
somos confrontados com a questo de quo frequentemente queremos criar novos
objetos para testar. Duas restries entram em conflito.

Performance Gostaramos que nossos testes rodassem o mais rpido


possvel, isto , se usamos objetos similares em muitos testes, gostaramos
de cri-los uma vez para todos os testes.
118 Parte II O Exemplo xUnit

Isolamento Gostaramos que o sucesso ou a falha de um teste fosse ir-


relevante para outros testes. Se testes dividem objetos e um teste muda os
objetos, os testes seguintes provavelmente mudaro seus resultados.

O acoplamento de testes possui um efeito desagradvel e bvio pelo qual


fazer um teste falhar faz os prximos dez falharem, mesmo que o cdigo esteja
correto. O acoplamento de testes pode ter um efeito sutil, mas muito desagradvel,
em situaes nas quais a ordem dos testes importa: se eu executo um teste A antes
do teste B, os dois funcionam, mas se eu executo o teste B antes do teste A, ento o
teste A falha. Ou ainda pior, o cdigo exercitado por B est errado, mas devido ao
teste A rodar primeiro, os testes passam.
Acoplamento de testes no v por a. Vamos assumir por um momento
que podemos criar objetos de forma rpida o suficiente. Nesse caso, gostaramos
de criar os objetos para um teste cada vez que ele executa. J vimos uma forma
disfarada disso em WasRun, onde queramos ter uma flag mudada para falso antes
de rodarmos o teste. Seguindo os passos nessa direo, primeiro, precisamos de
um teste:

TestCaseTest
def testSetUp(self):
test= WasRun("testMethod")
test.run()
assert(test.wasSetUp)

Ao executar isso (adicionando a ltima linha TestCaseTest(testSetUp).run() ao


nosso arquivo), Python nos informa educadamente que no h um atributo wasSetUp.
certo que no. Ns no o inicializamos. Esse mtodo deveria fazer:

WasRun
def setUp(self):
self.wasSetUp= 1

Ele existiria se o estivssemos chamando. Chamar setUp o trabalho de TestCase,


assim, voltamos l:

TestCase
def setUp(self):
pass
def run(self):
self.setUp()
method = getattr(self, self.name)
method()

Ou seja, dois passos para ter um caso de teste rodando, o que demais em
tais circunstncias delicadas. Veremos se funcionar. Sim, ele passa. Contudo, se
Captulo 19 Iniciando 119

quiser aprender algo, tente descobrir como poderamos ter obtido o teste funcio-
nando mudando no mais do que um mtodo de cada vez.
Podemos usar imediatamente nossa nova fbrica para encurtar nossos testes.
Primeiro, podemos simplificar WasRun iniciando a flag wasRun em setUp:

WasRun
def setUp(self):
self.wasRun= None
self.wasSetUp= 1

Temos que simplificar testRunning para no buscar a flag antes de rodar o tes-
te. Estamos dispostos a desistir desse tanto de confiana do nosso cdigo? Apenas
se testSetUp estiver no lugar. Esse um padro comum um teste pode ser simplifi-
cado se, e somente se, outro teste est no lugar e executando corretamente:

TestCaseTest
def testRunning(self):
test= WasRun("testMethod")
test.run()
assert(test.wasRun)

Podemos tambm simplificar os prprios testes. Em ambos os casos, criamos


uma instncia de WasRun, exatamente a fixture que estvamos falando anteriormen-
te. Podemos criar WasRun em setUp e us-lo nos mtodos de teste. Cada mtodo de
teste executado em uma instncia limpa de TestCaseTest, assim no h como os
dois testes estarem acoplados. (Estamos assumindo que os objetos no interagem
de uma forma incrivelmente feia como inicializar variveis globais, mas no fara-
mos isso, no com todos esses outros leitores olhando.)

TestCaseTest
def setUp(self):
self.test= WasRun("testMethod")
def testRunning(self):
self.test.run()
assert(self.test.wasRun)
def testSetUp(self):
self.test.run()
assert(self.test.wasSetUp)

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
120 Parte II O Exemplo xUnit

Em seguida, rodaremos tearDown() depois do mtodo de teste. Para revisar


esse captulo:

Decidimos que a simplicidade da escrita do teste era mais importante que


a performance para o momento.
Testamos e implementamos setup().
Usamos setup() para simplificar o caso de teste exemplo.
Usamos setUp() para simplificar casos de teste verificando o caso de teste
exemplo (eu falei para voc que isso seria como fazer uma cirurgia no
prprio crebro).
Captulo 20
Limpando em Seguida

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados

s vezes, testes precisam alocar recursos externos em setUp(). Se quisermos que os


testes mantenham-se independentes, ento um teste que aloca recursos externos
precisa liber-los antes de estar acabado, talvez em um mtodo tearDown().
O jeito simplista de escrever o teste para desalocao introduzir outra flag.
Todas essas flags esto comeando a me incomodar e esto deixando escapar um
aspecto importante dos mtodos: setUp() chamado antes que o mtodo de teste
seja executado, e tearDown() chamado depois disso. Vou mudar a estratgia de
testes para manter um pequeno registro (log) de mtodos que so chamados. Ao
acrescentar estas chamadas ao registro, preservaremos a ordem em que os mtodos
so chamados.

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
122 Parte II O Exemplo xUnit

WasRun
def setUp(self):
self.wasRun= None
self.wasSetUp= 1
self.log= "setUp "

Agora podemos mudar testSetUp() para olhar para o registro (log) e no para
a flag:

TestCaseTest
def testSetUp(self):
self.test.run()
assert("setUp " == self.test.log)

Em seguida, podemos deletar a flag wasSetUp. Podemos tambm gravar a execuo


do mtodo de teste:

WasRun
def testMethod(self):
self.wasRun= 1
self.log= self.log + "testMethod "

Isso estraga testSetUp, pois o registro atual contm setUp testMethod . Muda-
mos o valor esperado:

TestCaseTest
def testSetUp(self):
self.test.run()
assert("setUp testMethod " == self.test.log)

Agora esse teste est fazendo o trabalho de ambos os testes, ento podemos deletar
testRunning e renomear testSetUp:

TestCaseTest
def setUp(self):
self.test= WasRun("testMethod")
def testTemplateMethod(self):
self.test.run()
assert("setUp testMethod " == self.test.log)

Infelizmente, estamos usando a instncia WasRun em apenas um lugar, ento


temos que desfazer nosso inteligente hack setUp:

TestCaseTest
def testTemplateMethod(self):
test= WasRun("testMethod")
test.run()
assert("setUp testMethod " == test.log)

Fazer uma refatorao baseada em um monte de usos anteriores e ento ter que
desfaz-la logo depois bastante comum. Algumas pessoas esperam at terem trs
Captulo 20 Limpando em Seguida 123

ou quatro usos antes de refatorar, pois no gostam de desfazer trabalho. Eu prefiro


gastar meus ciclos de pensamento no projeto, assim apenas fao as refatoraes re-
flexivamente, sem me preocupar se terei que desfaz-las imediatamente depois.

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun

Agora estamos preparados para implementar tearDown(). Peguei voc! Agora


estamos preparados para testar tearDown:

TestCaseTest
def testTemplateMethod(self):
test= WasRun("testMethod")
test.run()
assert("setUp testMethod tearDown " == test.log)

Ele falha. Faz-lo funcionar simples:

TestCase
def run(self, result):
result.testStarted()
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()

WasRun
def setUp(self):
self.log= "setUp "
def testMethod(self):
self.log= self.log + "testMethod "
def tearDown(self):
self.log= self.log + "tearDown "

Surpreendentemente, temos um erro; no em WasRun, mas em TestCaseTest. No


temos uma implementao nop (no-faa-nada) de tearDown() em TestCase:

TestCase
def tearDown(self):
pass

Dessa vez temos que valorizar o uso do mesmo framework de testes que estamos
desenvolvendo. Ipi! Nenhuma refatorao necessria. A Implementao bvia,
depois daquela nica falha, funcionou e estava limpa.
124 Parte II O Exemplo xUnit

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun

Em seguida, continuaremos a informar os resultados de rodar um teste ex-


plicitamente em vez de deixar o sistema de tratamento de erros nativo do Python
tratar e nos informar quando h um problema com uma assero. Para revisar,
neste captulo:

Reestruturamos a estratgia de testes de flags para um registro (log).


Testamos e implementamos tearDown() usando o novo registro.
Encontramos um problema e, ousadamente, o corrigimos em vez de vol-
tarmos atrs (Foi uma boa ideia?).
Captulo 21
Contagem

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun

Eu ia incluir uma implementao para assegurar que tearDown() fosse chamada a


despeito das excees durante o mtodo de teste. Contudo, precisamos capturar
excees de forma a fazer o teste funcionar. (Eu sei, eu tentei e recuei.) Se cometer-
mos um engano implementando isso, no seremos capazes de ver o engano, pois as
excees no sero informadas.
Em geral, a ordem da implementao dos testes importante. Quando eu
pego o prximo teste para implementar, eu acho que um teste me ensinar algo e
eu tenho confiana que posso fazer funcionar. Se eu deixar aquele teste funcionan-
do, mas ficar emperrado no prximo, ento considero o backup de dois passos.
Seria timo se o ambiente de programao me ajudasse com isso, funcionando
como um checkpoint para o cdigo cada vez que todos os testes rodarem.
Gostaramos de ver os resultados da execuo de qualquer nmero de testes
5 rodaram, 2 falharam, TestCaseTest.testFooBar ExceoDeDivisoPorZero,
MoneyTest.testNegation ErroDeAssero. Ento, se os testes pararem de ser
chamados ou se os resultados pararem de ser informados, ao menos temos uma
chance de capturar o erro. Ter o framework informando automaticamente todos
os casos de teste dos quais ele no sabe nada parece um pouco forado, ao menos
para o primeiro caso de teste.
126 Parte II O Exemplo xUnit

Teremos TestCase.run() retornando um objeto TestResult que grava os resulta-


dos da execuo do teste (singular, por enquanto, mas vamos chegar l).

TestCaseTest
def testResult(self):
test= WasRun("testMethod")
result= test.run()
assert("1 run, 0 failed" == result.summary())

Comearemos com uma implementao faz de conta:

TestResult
class TestResult:
def summary(self):
return "1 run, 0 failed"

e retornando TestResult como o resultado de TestCase.run()

TestCase
def run(self):
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()
return TestResult()

Agora que o teste roda, podemos tornar concreta a implementao de


summary() um pouco de cada vez. Primeiro, podemos fazer do nmero de testes que
executam uma constante simblica:

TestResult
def __init__(self):
self.runCount= 1
def summary(self):
return "%d run, 0 failed" % self.runCount

(O operador % o sprintf do Python.) Entretanto, runCount no devia ser uma


constante; devia ser computada pela contagem do nmero de testes que executam.
Podemos inicializ-la com 0 e ento increment-la cada vez que um teste execu-
tado.

TestResult
def __init__(self):
self.runCount= 0
def testStarted(self):
self.runCount= self.runCount + 1
def summary(self):
return "%d run, 0 failed" % self.runCount

Temos realmente que chamar esse novo mtodo bacana:


Captulo 21 Contagem 127

TestCase
def run(self):
result= TestResult()
result.testStarted()
self.setUp()
method = getattr(self, self.name)
method()
self.tearDown()
return result

Poderamos transformar a string constante 0 do nmero de testes que fa-


lharam em uma varivel da mesma forma que concretizamos runCount, mas os testes
no exigem isso. Ento, em vez disso, escrevemos outro teste:

TestCaseTest
def testFailedResult(self):
test= WasRun("testBrokenMethod")
result= test.run()
assert("1 run, 1 failed", result.summary)

onde:

WasRun
def testBrokenMethod(self):
raise Exception

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
Informar testes que falharam

A primeira coisa que notamos que no estamos capturando a exceo lan-


ada por WasRun.testBrokenMethod. Gostaramos de capturar a exceo e fazer uma
anotao no resultado que o teste falhou. Colocaremos esse teste na prateleira por
enquanto.
Para revisar:

Escrevemos uma implementao faz de conta e, gradualmente, comea-


mos a torn-la concreta, substituindo constantes por variveis.
Escrevemos outro teste.
Quando o teste falhou, escrevemos outro teste, em menor escala, para dar
suporte e fazer o teste que falhou funcionar.
Captulo 22
Lidando com Falha

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
Informar testes que falharam

Escreveremos um teste mais granular para garantir que, se notarmos que um teste
falhou, mostraremos os resultados certos:

TestCaseTest
def testFailedResultFormatting(self):
result= TestResult()
result.testStarted()
result.testFailed()
assert("1 run, 1 failed" == result.summary())

testStarted() e testFailed() so as mensagens que esperamos enviar para


o resultado quando um teste comea e quanto um teste falha, respectivamente.
Se pudermos fazer o sumrio mostrar corretamente quando essas mensagens fo-
rem enviadas nessa ordem, ento nosso problema de programao est reduzido a
como enviar essas mensagens. Uma vez que elas sejam enviadas, esperamos que a
coisa toda funcione.
A implementao para manter uma contagem de falhas:

TestResult
def __init__(self):
130 Parte II O Exemplo xUnit

self.runCount= 0
self.errorCount= 0
def testFailed(self):
self.errorCount= self.errorCount + 1

Com a contagem correta (a qual suponho que poderamos ter testado, se estivsse-
mos dando passos muito, muito pequeninos mas eu no vou me incomodar, pois
o caf fez efeito agora), podemos mostrar corretamente:

TestResult
def summary(self):
return "%d run, %d failed" % (self.runCount, self.failureCount)

Agora esperamos que se chamarmos testFailed() corretamente, teremos a


resposta esperada. Quando a chamamos? Quando capturarmos uma exceo no
mtodo de teste:

TestCase
def run(self):
result= TestResult()
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()
return result

H uma sutileza escondida nesse mtodo. Pela forma como foi escrito, se um
desastre acontecer durante setUp(), ento a exceo no ser pega. Isso pode no ser
o que queremos queremos que nossos testes executem independentemente uns dos
outros. Contudo, precisamos de outro teste antes que possamos mudar o cdigo.
(Eu ensinei para Bethany, minha filha mais velha, TDD como seu primeiro estilo
de programao quando ela tinha uns 12 anos. Ela acha que voc no pode digitar
cdigo a menos que exista um teste que no funciona. O resto de ns tem que se
atrapalhar todo lembrando de escrever os testes.) Deixarei aquele prximo teste e
sua implementao como um exerccio para voc (dedos doloridos, de novo).

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
Informar testes que falharam
Capturar e informar erros em setUp
Captulo 22 Lidando com Falha 131

A seguir, trabalharemos para obter vrios testes rodando juntos. Para revisar
este captulo:

Fizemos nosso trabalho de teste em pequena escala.


Reintroduzimos o teste em larga escala.
Fizemos o trabalho do teste maior rapidamente, usando o mecanismo de-
monstrado pelo teste menor.
Percebemos um problema potencial e o anotamos na lista de tarefas em
vez de atac-lo imediatamente.
Captulo 23
Como a Sute?

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
Informar testes que falharam
Capturar e informar erros em setUp

No podemos deixar xUnit sem visitar TestSuite (sute* de testes). O final de nosso
arquivo, onde invocamos todos os testes, est parecendo um ninho de ratos:

print TestCaseTest("testTemplateMethod").run().summary()
print TestCaseTest("testResult").run().summary()
print TestCaseTest("testFailedResultFormatting").run().summary()
print TestCaseTest("testFailedResult").run().summary()

Duplicao sempre uma coisa ruim, a menos que voc olhe para ela como uma
motivao para encontrar o elemento de projeto que falta. Aqui gostaramos da
habilidade para compor testes e os rodar juntos. (Trabalhar duro para fazer os tes-
tes executarem isoladamente no nos faz to bem se apenas rodamos um teste por
vez.) Outra boa razo para implementar TestSuite que isso nos d um exemplo

* N. de R. T.: O termo sute usado como sinnimo de conjunto pela comunidade de teste de
software.
134 Parte II O Exemplo xUnit

puro de Composite* queremos ser capazes de tratar um nico teste e um grupo


de testes exatamente da mesma forma.
Gostaramos de ser capazes de criar um TestSuite, adicionar alguns testes a ele
e, ento, obter resultados coletivos ao execut-lo:

TestCaseTest
def testSuite(self):
suite= TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
result= suite.run()
assert("2 run, 1 failed" == result.summary())

Implementar o mtodo add() meramente adiciona testes a uma lista:

TestSuite
class TestSuite:
def __init__(self):
self.tests= []
def add(self, test):
self.tests.append(test)

(nota de Python: [ ] cria uma coleo vazia.)


O mtodo run um pequeno problema. Queremos que apenas um TestResult seja
usado por todos os testes que executam. Portanto, devemos escrever:

TestSuite
def run(self):
result= TestResult()
for test in tests:
test.run(result)
return result

(nota de Python: for test in self.tests itera atravs dos elementos de tests, asso-
ciando cada um a tests e avaliando o cdigo a seguir.) Contudo, uma das princi-
pais restries em Composite que a coleo (objeto composto) deve responder
s mesmas mensagens que os objetos primitivos. Se adicionamos um parmetro a
TestCase.run(), ento temos que adicionar o mesmo parmetro a TestSuite.run(). Eu
posso pensar em trs alternativas:

Usar o mecanismo de parmetros padro de Python. Infelizmente, o valor


padro avaliado em tempo de compilao, no em tempo de execuo,
e no queremos reusar o mesmo TestResult.
Dividir o mtodo em duas partes uma que aloca o TestResult e outra que
executa o teste dado um TestResult. Eu no consigo pensar em bons nomes

* N. de R. T.: Composite um padro de projeto GoF que permite tratar uniformemente objetos
primitivos e compostos.
Captulo 23 Como a Sute? 135

para as duas partes desse mtodo, o que sugere que essa no uma boa
estratgia.
Alocar o TestResult no chamador.

Alocaremos os TestResults nos chamadores. Esse padro chamado Collec-


ting Parameter*.

TestCaseTest
def testSuite(self):
suite= TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
result= TestResult()
suite.run(result)
assert("2 run, 1 failed" == result.summary())

Essa soluo tem a vantagem de run() agora no ter retorno explcito:

TestSuite
def run(self, result):
for test in tests:
test.run(result)

TestCase
def run(self, result):
result.testStarted()
self.setUp()
try:
method = getattr(self, self.name)
method()
except:
result.testFailed()
self.tearDown()

Agora podemos limpar a invocao dos testes no final do arquivo:

suite= TestSuite()
suite.add(TestCaseTest("testTemplateMethod"))
suite.add(TestCaseTest("testResult"))
suite.add(TestCaseTest("testFailedResultFormatting"))
suite.add(TestCaseTest("testFailedResult"))
suite.add(TestCaseTest("testSuite"))
result= TestResult()
suite.run(result)
print result.summary()

* N. de R. T.: Padro de implementao Collecting Parameter, em que um objeto na verdade uma


coleo (lista, vetor, etc.) passado repetidamente como um parmetro para um mtodo de forma a
coletar informao desse mtodo, que adiciona itens coleo. Kent Beck definiu esse padro. Mais
detalhes em KERIEVSKY, Joshua. Refatorao para padres. Porto Alegre: Bookman, 2008.
136 Parte II O Exemplo xUnit

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
Informar testes que falharam
Capturar e informar erros em setUp
Crie TestSuite de uma classe TesteCase

H duplicao substancial aqui que poderamos eliminar se tivssemos uma forma


de construir uma sute automaticamente, dada uma classe de testes.
Contudo, primeiro temos que corrigir os quatro testes que falham (eles usam
a antiga interface de run sem argumentos):

TestCaseTest
def testTemplateMethod(self):
test= WasRun("testMethod")
result= TestResult()
test.run(result)
assert("setUp testMethod tearDown " == test.log)
def testResult(self):
test= WasRun("testMethod")
result= TestResult()
test.run(result)
assert("1 run, 0 failed" == result.summary())
def testFailedResult(self):
test= WasRun("testBrokenMethod")
result= TestResult()
test.run(result)
assert("1 run, 1 failed" == result.summary())
def testFailedResultFormatting(self):
result= TestResult()
result.testStarted()
result.testFailed()
assert("1 run, 1 failed" == result.summary())

Perceba que cada teste aloca um TestResult, exatamente o problema resolvido por
setUp(). Podemos simplificar os testes (ao custo de faz-los um pouco mais difceis
de ler) criando o TestResult em setUp():

TestCaseTest
def setUp(self):
self.result= TestResult()
def testTemplateMethod(self):
test= WasRun("testMethod")
test.run(self.result)
assert("setUp testMethod tearDown " == test.log)
def testResult(self):
test= WasRun("testMethod")
test.run(self.result)
Captulo 23 Como a Sute? 137

assert("1 run, 0 failed" == self.result.summary())


def testFailedResult(self):
test= WasRun("testBrokenMethod")
test.run(self.result)
assert("1 run, 1 failed" == self.result.summary())
def testFailedResultFormatting(self):
self.result.testStarted()
self.result.testFailed()
assert("1 run, 1 failed" == self.result.summary())
def testSuite(self):
suite= TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
suite.run(self.result)
assert("2 run, 1 failed" == self.result.summary())

Invoque o mtodo teste


Invoque setUp primeiro
Invoque tearDown depois
Invoque tearDown mesmo se o mtodo teste falhar
Rode mltiplos testes
Informe resultados coletados
String de registro em WasRun
Informar testes que falharam
Capturar e informar erros em setUp
Crie TestSuite de uma classe TesteCase

Todos aqueles selfs extras so um pouco feios, mas isso Python. Se fosse
uma linguagem objeto, ento o self seria assumido e referncias para variveis
globais requereriam qualificao. Em vez disso, uma linguagem de script com
adio de suporte a objetos (excelente suporte a objetos para ser exato), ento a
referncia global implcita e se referir a self explcito.
Eu deixarei o resto desses itens para voc e suas recm-descobertas habilidades
em TDD.
Para revisar, nesse captulo:

Escrevemos um teste para um TestSuite (sute de testes).


Escrevemos parte da implementao, mas sem fazer o teste funcionar. Isso
era uma violao da Regra*. Se voc observou isso agora, pegue dois casos
de testes do fundo da caixa. Estou certo de que h uma implementao faz
de conta simples que teria feito o caso de teste funcionar, ento poderamos
refatorar sob uma barra verde, mas no posso achar que o momento.
Mudamos a interface do mtodo run para que o item e o Composite de
itens pudessem funcionar de forma idntica, e, ento, finalmente obtive-
mos o teste funcionando.
Fatoramos o cdigo comum de inicializao.

* N. de R. T.: Aluso do autor regra bsica do TDD de escrever testes que funcionam antes de es-
crever cdigo.
Captulo 24
Retrospectiva xUnit

Se chegar a hora de implementar seu prprio framework de testes, ento a sequn-


cia apresentada na Parte II desse livro pode servir como seu guia. Os detalhes da
implementao no so to importantes como os casos de teste. Se voc puder dar
suporte a um conjunto de casos de teste como aqueles mostrados daqui, ento
pode escrever testes que so isolados e podem ser compostos, e voc estar no ca-
minho de ser capaz de desenvolver de modo test-first (teste primeiro).
xUnit foi portado para mais de 30 linguagens de programao no momento
da escrita deste livro. provvel que a linguagem que voc usa j tenha uma im-
plementao. Mas h um monte de razes para implementar sua prpria xUnit,
mesmo se houver uma verso j disponvel:

Domnio O esprito da xUnit simplicidade. Martin Fowler disse, nun-


ca antes nos anais da engenharia de software tantos deveram tanto a algo
que foi desenvolvido em to poucas linhas de cdigo. Algumas das im-
plementaes so um pouco complicadas para o meu gosto. Desenvolver
a sua prpria dar a voc uma ferramenta sobre a qual tem um sentimen-
to de domnio.
Explorao Quando estou diante de uma nova linguagem de programa-
o, eu implemento xUnit. Quanto eu tiver os primeiros oito testes dos
dez rodando terei explorado muitas das habilidades que usarei na progra-
mao do dia a dia.

Quando voc comea a usar xUnit, descobrir uma grande diferena entre
asseres que falham e os outros tipos de erros que ocorrem na execuo dos tes-
tes: falhas de assero sistematicamente levam mais tempo para depurar. Por causa
disso, a maioria das implementaes de xUnit distingue entre falhas isto , falhas
de assero e erros. As GUIs as apresentam de forma diferente, frequentemente
com os erros no topo.
140 Parte II O Exemplo xUnit

JUnit declara uma nica interface Test que TestCase e TestSuite implementam.
Se voc quiser que ferramentas JUnit sejam capazes de rodar seus testes, ento
pode implementar a interface Test tambm.

public interface Test {


public abstract int countTestCases();
public abstract void run(TestResult result);
}

Linguagens com tipagem otimista (dinmica) nem mesmo precisam declarar


sua fidelidade a uma interface elas podem simplesmente implementar as opera-
es. Se voc escrever uma linguagem de script de testes, ento Script pode imple-
mentar countTestCases() para retornar 1 e executar para notificar o TestResult no
caso de falha, e voc pode executar seus scripts com os TestCases comuns.
Parte III
Padres para
Desenvolvimento
Guiado por Testes

O que segue so os padres de maior sucesso para TDD. Alguns dos padres
so truques de TDD, alguns so padres de projeto e alguns so refatoraes. Se
voc est familiarizado com esses truques, ento os padres daqui mostraro a
voc como os tpicos interagem em TDD. Caso contrrio, h material suficiente
para lev-lo pelos exemplos do livro e para aguar seu apetite pelos tratamentos
abrangentes encontrados em outros lugares.
Captulo 25
Padres de
Desenvolvimento
Guiado por Testes

Precisamos responder algumas questes estratgicas bsicas antes de falarmos so-


bre os detalhes de como testar:

O que queremos dizer com testar?


Quando testamos?
Como escolhemos que lgica testar?
Como escolhemos quais dados testar?

Teste (substantivo)
Como voc testa seu software? Escreva um teste automtico.
Testar um verbo que significa avaliar. Nenhum engenheiro de software
libera nem mesmo a menor mudana sem test-la, exceto os muito confiantes e os
muito desleixados. Eu vou assumir que se voc chegou to longe, no nenhum
dos dois. Embora voc possa testar suas mudanas, testar mudanas no o mes-
mo que ter testes. Teste tambm um substantivo, um procedimento que conduz
a aceitao ou rejeio. Por que o substantivo teste, um procedimento que se
executa automaticamente, parece diferente do verbo testar, como apertar alguns
botes e olhar respostas na tela?
O que segue um diagrama de influncia, la Gerncia de Qualidade de
Software de Gerry Weinberg. Uma seta entre nodos significa que um aumento
no primeiro nodo implica em um aumento no segundo nodo. Uma seta com um
crculo significa que um aumento no primeiro nodo implica um decrscimo no
segundo nodo.
O que acontece quando o nvel de estresse aumenta?
144 Parte III Padres para Desenvolvimento Guiado por Testes

Estresse

Execuo
de Testes

Figura 25.1 A espiral da morte do sem tempo para testar.

Isso um lao de feedback positivo. Quanto mais estresse voc sentir, menos
vai testar. Quanto menos voc testar, mais erros vai cometer. Quanto mais erros
voc cometer, mais estresse vai sentir. Enxague e repita.
Como voc escapa desse lao? Ou introduz um novo elemento, substituindo
um dos elementos, ou muda as setas. Nesse caso, substituiremos Testes por Testes
Automticos.
Acabei de estragar alguma outra coisa com aquela mudana? A Figura
25.1 mostra a dinmica no trabalho. Com testes automticos, quando eu comeo
a sentir estresse, eu rodo os testes. Testes so a Pedra Filosofal do programador,
transmutando medo em tdio. No, eu no estraguei nada. Os testes ainda esto
verdes. Quanto mais estresse eu sentir, mais testes vou rodar. Rodar os testes
imediatamente me traz uma sensao agradvel e reduz o nmero de erros que eu
cometo, o que reduz ainda mais o estresse que sinto.
No temos tempo para fazer testes. Entregue assim mesmo!! O segundo
quadro no garantido. Se o nvel de estresse sobe alto o suficiente, ele desaba.
Contudo, com os testes automticos, voc tem uma chance de escolher seu nvel
de temor.
Voc deveria rodar o teste depois de t-lo escrito, mesmo sabendo que vai
falhar? No, no se incomode. Por exemplo, estava trabalhando com um monte de
jovens programadores na implementao de transaes em memria (uma tcnica
muito legal que toda linguagem de programao deveria ter). A questo era: como
implementaramos o rollback (retrocesso) se comessemos uma transao, mu-
dssemos algumas variveis e deixssemos a transao ser recolhida pelo coletor
de lixo? Muito fcil de testar, rapazes. Para trs, e observem o mestre trabalhar.
Aqui est o teste. Agora, como vamos implementar isso?
Duas horas depois horas marcadas por frustrao, pois um erro ao imple-
mentar caractersticas de to baixo nvel geralmente fecham o ambiente de desen-
volvimento voltamos para onde comeamos. Escrevemos o teste. Executamos ele
no capricho. Ele passou. D.... A questo toda do mecanismo de transao era que
as variveis no eram realmente mudadas at a transao ser enviada. Certo, eu
suponho que voc poderia ir em frente e rodar aquele novo teste se quiser.
Captulo 25 Padres de Desenvolvimento Guiado por Testes 145

Teste isolado (Isolated Test)*


Como a execuo de testes deveria afetar outra execuo? Em nada.
Quando eu era um jovem programador h muito, muito tempo quando
tnhamos que desenterrar nossos prprios bits da neve e carreg-los descalos em
baldes pesados de volta a nossos cubculos, deixando pequenas pegadas sangrentas
para que os lobos seguissem.... Desculpe, apenas reminiscncias. Minha primeira
experincia com testes automticos foi um conjunto de testes baseados em GUI de
longa durao, durante a noite (voc sabe, gravar as teclas pressionadas e os even-
tos de teclado e repeti-los), para um depurador em que eu estava trabalhando. (Oi,
Jothy, oi John!) Toda manh, quando eu chegava, havia uma bela pilha de papel
em minha cadeira descrevendo a execuo dos testes da ltima noite. (Oi, Al!) Em
dias bons, haveria uma nica folha resumindo que nada estava estragado. Em dias
ruins, haveria muitas, muitas folhas, uma para cada teste que no funcionou. Eu
comecei a temer os dias em que via uma pilha de papel na minha cadeira.
Eu tirei duas lies dessa experincia. Primeiro, faa testes to rpidos de
executar que possa rod-los sozinho e rod-los frequentemente. Desse jeito, eu
posso capturar erros antes que outros possam v-los e no ter que temer chegar
de manh. Segundo, percebi depois de um tempo que uma enorme pilha de papel
geralmente no significava uma grande lista de problemas. Mais frequentemente,
significava que um teste inicial no tinha funcionado, deixando o sistema em um
estado imprevisvel para o prximo teste.
Tentamos contornar esse problema comeando e parando o sistema entre
cada teste, mas isso levava muito tempo, o que me ensinou outra lio sobre bus-
car testes em pequena escala em vez de buscar na aplicao inteira. Mas a lio
principal que eu aprendi foi que testes deveriam ser capazes de ignorar um ao ou-
tro completamente. Se tenho um teste que no funciona, eu quero um problema. Se
tenho dois testes que no funcionam, eu quero dois problemas.
Uma implicao conveniente de testes isolados que os testes so indepen-
dentes de ordem. Se quero pegar um subconjunto de testes e rod-los, ento eu
posso faz-lo sem me preocupar com que o teste v falhar agora porque um teste
que pr-requisito sumiu.
Performance a razo usual citada para ter testes compartilhando dados.
Uma segunda implicao de testes isolados que voc tem que trabalhar, s ve-
zes trabalhar duro, para dividir seu problema em pequenas dimenses ortogonais,
ento configurar o ambiente para cada teste fcil e rpido. Isolamento de testes
o encoraja a compor solues de muitos objetos altamente coesos e fracamente
acoplados. Eu sempre ouo que isso uma boa ideia e ficava feliz quando a alcan-
ava, mas nunca sabia exatamente como atingir alta coeso e baixo acoplamento
regularmente at comear a escrever testes isolados.

* N. de R. T.: Decidimos manter o nome original do padro em ingls para facilitar identificao, em
caso de consulta a bibliografia em ingls.
146 Parte III Padres para Desenvolvimento Guiado por Testes

Lista de testes (Test List)


O que voc deveria testar? Antes de comear, escreva uma lista de todos os testes
que sabe que ter que escrever. A primeira parte de nossa abordagem para lidar
com estresse de programao nunca dar um passo frente a menos que saibamos
que nosso p est indo para terra firme. Quando sentamos para uma sesso de
programao, o que que pretendemos realizar?
Uma estratgia para manter o rastro do que estamos tentando fazer manter
isso tudo em nossas cabeas. Eu tentei isso por muitos anos antes e acho que entrei
em um lao de feedback positivo. Quanto mais experincia eu acumulei, eu sabia
que mais coisas precisariam ser feitas. Quanto mais coisas eu sabia que precisa-
riam ser feitas, menos ateno eu dava para aquilo que estava fazendo. Quanto
menos ateno, menos eu fazia. Quanto menos eu fazia, mais coisas eu sabia que
precisariam ser feitas.
Apenas ignorar itens aleatrios na lista e program-los ao bel prazer no
parece quebrar esse ciclo.
Eu tinha o hbito de escrever tudo o que queria fazer nas prximas horas em
um pedao de papel perto do meu computador. Tinha uma lista similar, mas com
um escopo semanal ou mensal, fixada na parede. To logo tivesse tudo escrito, eu
sabia que no esqueceria alguma coisa. Quando um novo item chegava, eu decidia
rpida e conscientemente se ele pertencia lista agora ou lista mais tarde,
ou se ele no precisava realmente ser feito.
Aplicado ao desenvolvimento dirigido por testes, o que pusemos na lista so
os testes que queremos implementar. Primeiro, coloque na lista exemplos de cada
operao que voc sabe que precisa implementar. Depois, para aquelas operaes
que j no existem, coloque a verso nula daquela operao na lista. Finalmente,
liste todas as refatoraes que voc acha que ter que fazer de forma a limpar o
cdigo no final daquela sesso.
Em vez de definir os testes, poderamos apenas ir em frente e implement-los
todos. H um monte de razes para a escrita em massa de testes no ter funcio-
nado para mim. Primeiro, cada teste que voc implementa um pouco de inr-
cia quando tem que refatorar. Com ferramentas de refatorao automtica (por
exemplo, voc tem um item no menu que renomeia a declarao e todos os usos de
uma varivel), isso o menor problema. Mas, quando voc implementa dez testes
e ento descobre que os argumentos precisam estar em ordem contrria, muito
menos provvel que voc v arrum-los. Segundo, se voc tem dez testes que no
funcionam, tem um longo caminho at a barra verde. Se voc quer chegar ao verde
rapidamente, tem que jogar fora os dez testes. Se voc quer todos os testes funcio-
nando, ento vai ficar olhando para barra vermelha por um longo tempo. Se voc
est to viciado na barra verde que no pode ir ao banheiro se ela est vermelha,
ento isso pode ser uma eternidade.
Captulo 25 Padres de Desenvolvimento Guiado por Testes 147

Alpinistas conservadores tm uma regra segundo a qual, de seus dois ps e


duas mos, trs deles devem estar sempre fixados. Movimentos em que voc vai
soltar dois deles ao mesmo tempo so muito mais perigosos. A forma pura de
TDD, na qual voc nunca est a mais que uma mudana da barra verde, como
essa regra de trs dos quatro.
Conforme voc faz os testes rodarem, a implementao implicar em novos
testes. Escreva os testes no final da lista. Da mesma forma com refatoraes.
Isso est ficando feio.
(suspiro) Ponha isso na lista. Ns chegaremos l antes da verificao.
Itens que so deixados na lista quando a sesso est feita precisam de cuida-
dos. Se voc est realmente no meio de um pedao de funcionalidade, ento use
a mesma lista depois. Se voc descobriu refatoraes maiores que esto fora do
escopo do seu momento, ento as coloque na lista de mais tarde. No consigo
lembrar de alguma vez ter movido um caso de teste para a lista de mais tarde. Se
eu conseguir pensar em um teste que pode no funcionar, faz-lo funcionar mais
importante que liberar meu cdigo.

Teste primeiro (Test First)


Quando voc deveria escrever seus testes? Antes de escrever o cdigo que vai ser
testado.
Voc no testar depois. Sua meta como programador executar a funcio-
nalidade. Contudo, voc precisa de uma maneira de pensar sobre o projeto; voc
precisa de um mtodo para o controle de escopo.
Vamos considerar o diagrama de influncia usual que relaciona estresse e teste
(mas no teste de estresse, isso diferente): Estresse acima, negativamente conectado
a Teste abaixo, negativamente conectado a Estresse (veja Teste (substantivo) anterior-
mente neste captulo). Quanto mais estresse sentir, menos provvel que testar o
suficiente. Quando voc sabe que no testou o suficiente, aumenta seu estresse. Lao
de feedback positivo. Mais uma vez, necessria uma forma de quebrar o ciclo.
E se adotarmos a regra de sempre testarmos primeiro? Ento poderamos
inverter o diagrama e obter um ciclo virtuoso: Teste Primeiro acima est negativa-
mente conectado a Estresse abaixo, negativamente conectado a Teste Primeiro.
Quando testamos primeiro, reduzimos o estresse, o que nos faz mais aptos a
testar. H um monte de outros elementos que alimentam o estresse, contudo, assim
os testes devem existir em outros ciclos virtuosos, ou eles sero abandonados quan-
do o estresse aumentar o bastante. Mas, a compensao imediata para teste uma
ferramenta para controle de projeto e escopo sugere que seremos capazes de co-
mear a faz-lo e nos mantermos fazendo-o mesmo sob estresse moderado.
148 Parte III Padres para Desenvolvimento Guiado por Testes

Defina uma assero primeiro (Assert First)


Quando eu deveria escrever as asseres? Tente escrev-las primeiro. Voc simples-
mente no ama autossimilaridade?

Por onde voc deve comear a construir um sistema? Com histrias de


que quer ser capaz de contar sobre o sistema terminado.
Por onde voc deve comear a escrever um pedao de funcionalidade?
Com testes que quer que passem com o cdigo terminado.
Por onde voc deve comear a escrever um teste? Com as asseres que
passaro (sero bem-sucedidas) quando estiver feito.

Jim Newkirk me introduziu a essa tcnica. Quando eu testo definindo uma


assero primeiro, acho que isso tem um poderoso efeito simplificador. Quando
voc est escrevendo um teste, est resolvendo muitos problemas de uma vez, mes-
mo se no tiver mais que pensar sobre a implementao.

A que lugar a funcionalidade pertence? Ela uma modificao de um


mtodo existente, um novo mtodo em uma classe existente, um nome de
mtodo existente implementado em um novo lugar ou uma nova classe?
Quais nomes deveriam ser chamados?
Como voc vai verificar a resposta certa?
Qual a resposta certa?
Quais outros testes esse teste sugere?

Crebros do tamanho de ervilhas como o meu no podem, possivelmente,


fazer um bom trabalho ao resolver todos esse problemas de uma vez. Os dois pro-
blemas da lista que podem ser facilmente separados do resto so qual a resposta
certa? e como eu vou verificar?
Aqui est um exemplo. Suponha que quero me comunicar com outro sistema
atravs de um socket. Quando estiver pronto, o socket deveria estar fechado e de-
veramos ter lido a string abc.

testCompleteTransaction() {
...
assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
}
Captulo 25 Padres de Desenvolvimento Guiado por Testes 149

De onde a resposta vem? Do socket, certamente:

testCompleteTransaction() {
...
Buffer reply= reader.contents();
assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
}

E o socket? Ns o criamos conectando ao servidor:

testCompleteTransaction() {
...
Socket reader= Socket("localhost", defaultPort());
Buffer reply= reader.contents();
assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
}

Mas, antes disso, precisamos abrir um servidor:

testCompleteTransaction() {
Server writer= Server(defaultPort(), "abc");
Socket reader= Socket("localhost", defaultPort());
Buffer reply= reader.contents();
assertTrue(reader.isClosed());
assertEquals("abc", reply.contents());
}

Agora podemos ter que ajustar alguns nomes baseados no uso real, mas cria-
mos o esboo do teste em passos pequeninos, informando cada deciso com feed-
back em poucos segundos.

Dados de teste (Test Data)


Que dados voc usa para testes da forma testar primeiro? Use dados que
faam os testes fceis de ler e seguir. Voc est escrevendo testes para uma audi-
ncia. No espalhe valores de dados apenas por espalhar. Se h uma diferena
nos dados, ento ela deveria ser significativa. Se no h diferena conceitual
entre 1 e 2, use 1.
Dados de Teste no so uma licena para uma parada repentina na confiana
plena. Se seu sistema tem que manipular mltiplas entradas, ento seus testes de-
150 Parte III Padres para Desenvolvimento Guiado por Testes

veriam refletir mltiplas entradas. Contudo, no tenha uma lista de dez itens como
entradas se uma lista de trs itens guiaro voc s mesmas decises de projeto e
implementao.
Um truque nos Dados de Teste tentar nunca usar a mesma constante para
significar mais de uma coisa. Se estou testando um mtodo plus(), tentador testar
2 + 2, pois o exemplo clssico de adio, ou 1 + 1, pois muito simples. E se es-
tivermos com os argumentos invertidos na implementao? (Certo, certo, isso no
importa no caso de plus(), mas voc pegou a ideia.) Se usarmos 2 para o primeiro
argumento, por exemplo, ento deveramos usar 3 para o segundo argumento. (3
+ 4 foi um caso de teste divisor de guas quando trazia de volta uma nova mquina
virtual para Smalltalk nos velhos dias.)
A alternativa para Dados de Teste o uso de Dados Realistas, em que voc
usa dados do mundo real. Dados Realistas so teis quando:

Voc est testando sistemas de tempo real usando rastros de eventos ex-
ternos recolhidos da execuo real.
Voc est equiparando a sada do sistema atual com a sada do sistema
anterior (teste paralelo).
Voc est refatorando uma simulao e esperando, precisamente, as mes-
mas respostas quando tiver terminado, particularmente se a preciso de
ponto flutuante pode ser um problema.

Dados evidentes (Evident Data)


Como voc representa o objetivo do dado? Inclua resultados esperados e reais
no prprio teste e tente fazer seu relacionamento evidente. Voc est escrevendo
testes para um leitor, no apenas para o computador. Algum daqui a dcadas
perguntar a si mesmo ou mesma, em que diabos esse palhao estava pensando?
Voc gostaria de deixar tantas pistas quanto possvel, especialmente se aquele lei-
tor frustrado ser voc.
Aqui est um exemplo. Se convertermos uma moeda em outra, levamos uma
porcentagem de 1,5 como comisso pela transao. Se a taxa de cmbio de USD
para GBP est 2:1, ento, se trocamos $100, deveramos ter 50 GBP 1,5% =
49,25 GBP.
Captulo 25 Padres de Desenvolvimento Guiado por Testes 151

Poderamos escrever esse teste assim:

Bank bank= new Bank().


bank.addRate("USD", "GBP", STANDARD_RATE);
bank.commission(STANDARD_COMMISSION);
Money result= bank.convert(new Note(100, "USD"), "GBP");
assertEquals(new Note(49.25, "GBP"), result);

ou poderamos tentar fazer o clculo bvio:

Bank bank= new Bank();


bank.addRate("USD", "GBP", 2);
bank.commission(0.015);
Money result= bank.convert(new Note(100, "USD"), "GBP");
assertEquals(new Note(100 / 2 * (1 - 0.015), "GBP"), result);

Posso ler esse teste e ver a conexo entre os nmeros usados na entrada e os nme-
ros usados para calcular o resultado esperado.
Um efeito colateral benfico de Dados Evidentes que ele torna a programa-
o mais fcil. Uma vez que tenhamos escrito a expresso na assero, sabemos
o que precisamos programar. De alguma forma, temos que comear o programa
para avaliar uma diviso e uma multiplicao. Podemos at usar Faa de conta
(Fake it*) para descobrir aos poucos a que pertencem as operaes.
Dados Evidentes parece ser uma exceo para a regra de no quer nmeros
mgicos em seu cdigo. Dentro do escopo de um s mtodo, o relacionamento en-
tre os 5s bvio. Se tenho constantes simblicas que j foram definidas, contudo,
eu usaria a forma simblica.

* N. de R. T.: Fake it um padro de teste (testing pattern) retorne uma constante e gradualmente
substitua constantes por variveis, ver Captulo 28.
Captulo 26
Padres de Barra Vermelha

Esses padres so sobre quando voc escreve testes, onde voc escreve testes e
quando voc para de escrever testes.

Teste de um s passo (One Step Test)*


Qual o prximo teste que voc deveria pegar da lista? Pegue um teste que ensina-
r a voc algo e que voc confia que pode implementar.
Cada teste deveria representar um passo em direo sua meta global. Olhan-
do para a seguinte Lista de Testes, qual o prximo teste que deveramos pegar?
Soma
Subtrao
Multiplicao
Diviso
Soma de similares
Igualdade
Igualdade entre nulos
Troca nula
Trocar uma moeda
Trocar duas moedas
Taxa cruzada
No h resposta certa. O que um passo para mim, nunca tendo implementado
esses objetos antes, ser um dcimo de um passo para voc com sua vasta expe-
rincia. Se voc no achar que qualquer teste na lista representa um s passo,

* N. de R. T.: Decidimos manter tambm o nome original do padro em ingls para facilitar identifi-
cao, em caso de consulta a bibliografia em ingls.
154 Parte III Padres para Desenvolvimento Guiado por Testes

ento adicione alguns testes novos que representariam progresso em direo aos
itens de l.
Quando olho uma Lista de Testes, eu penso, aquilo bvio, aquilo bvio,
eu no tenho ideia, bvio, em que eu estava pensando com esse aqui, ah, esse aqui
eu posso fazer. O ltimo teste o prximo teste que eu implemento. Ele no me
bate como bvio, mas eu estou confiante de que posso fazer o trabalho.
Um programa produzido a partir de testes como esse pode parecer ser escrito
de forma top-down, pois voc pode comear com um teste que representa um caso
simples da computao inteira. Um programa produzido a partir de teste pode
tambm parecer ser escrito de forma bottom-up, pois voc comea com pedaos
pequenos e os agrega mais e mais.
Nem a viso top-down, nem a viso bottom-up descrevem realmente o pro-
cesso de forma til. Primeiro, uma metfora vertical uma visualizao simplista
de como programas mudam com o tempo. Produo implica em um tipo de lao
de feedback recproco no qual o ambiente afeta o programa e o programa afeta
o ambiente. Segundo, se precisamos ter uma direo em nossa metfora, ento
de-conhecido-a-desconhecido uma descrio til. De-conhecido-a-desconhecido
implica que temos algum conhecimento e alguma experincia no que desenhar, e
que esperamos aprender no curso do desenvolvimento. Ponha esses dois juntos e
temos programas produzidos do conhecido ao desconhecido.

Teste inicial (Starter Test)


Com qual teste voc deveria comear? Comece testando uma variante de uma
operao que no faz nada.
A primeira questo que voc tem que perguntar com uma nova operao a
que lugar ela pertence?. At ter respondido essa questo, voc no saber o que
digitar para o teste. No esprito de resolver um problema por vez, como voc pode
responder apenas essa questo e no outra?
Se voc escrever um teste realista primeiro, ento se encontrar resolvendo
um monte de problemas de uma vez:

A que lugar essa operao pertence?


Quais so as entradas corretas?
Qual a sada correta para dadas entradas?

Comear com um teste realista deixar voc sem feedback por muito tempo. Ver-
melho/verde/refatore, vermelho/verde/refatore. Voc quer que esse lao leve apenas
alguns minutos.
Voc pode encurtar o lao escolhendo entradas e sadas que so trivialmente
fceis de descobrir. Por exemplo, um cartaz no grupo de discusso de Programao
Extrema perguntava como escrever um redutor de polgonos com teste primeiro.
As entradas so uma malha de polgonos e a sada uma malha de polgonos que
Captulo 26 Padres de Barra Vermelha 155

descreve precisamente a mesma superfcie, mas com o menor nmero de polgonos


possvel. Como eu posso me guiar por testes nesse problema quando ter um teste
funcionando requer a leitura de teses de doutorado?
Teste Inicial fornece uma resposta:

A sada deveria ser a mesma da entrada. Algumas configuraes de po-


lgonos j esto normalizadas, incapazes de sofrerem reduo.
A entrada deveria ser a menor possvel, como um polgono simples, ou
mesmo uma lista vazia de polgonos.

Meu Teste Inicial pareceu com isso:


Reducer r= new Reducer(new Polygon());
assertEquals(0, reducer.result().npoints);

Pronto! O primeiro teste est rodando. Agora para todo o resto dos testes na lista
...
Testes de Um S Passo se aplicam a seu Teste Inicial. Pegue um Teste Inicial
que o ensinar alguma coisa, mas que voc est certo que pode fazer funcionar
rapidamente. Se est implementando uma aplicao pela ensima vez, ento pegue
um teste que requer uma operao ou duas. Voc estar, justificadamente, con-
fiante de que pode faz-lo funcionar. Se voc est implementando algo cabeludo
e complicado pela primeira vez, ento precisa de uma pequena dose de coragem
imediatamente.
Acho que meu Teste Inicial est muitas vezes em um nvel mais alto: mais
como um teste de aplicao que como os testes a seguir. Um exemplo que eu fre-
quentemente uso para guiar testes um servidor simples baseado em socket. O
primeiro teste parece com isso:
StartServer
Socket= new Socket
Message= hello
Socket.write(message)
AssertEquals(message, socket.read)

O resto dos testes so escritos no servidor, Assumindo que recebamos uma string
como essa....

Teste de explicao (Explanation Test)


Como voc difunde o uso de teste automatizado? Pea e d explicaes em termos
de testes.
Pode ser frustrante ser o nico TDD em um time. Em breve, voc perceber
menos problemas de integrao e notificaes de defeitos no cdigo testado, e os
projetos sero mais simples e mais fceis de explicar. Pode at acontecer de pessoas
ficarem completamente entusiasmadas com testes e com testar primeiro.
156 Parte III Padres para Desenvolvimento Guiado por Testes

Cuidado com o entusiasmo dos recm-convertidos. Nada vai parar mais r-


pido a disseminao de TDD que enfi-lo na cara das pessoas. Se voc no um
gerente ou um lder, no pode forar ningum a mudar o jeito que trabalha.
O que pode fazer? Um incio simples comear pedindo explicaes em ter-
mos de casos de teste: Deixa eu ver se entendi o que est dizendo. Por exemplo, se
eu tenho um Foo como esse e um Bar como aquele, ento a resposta devia ser 76?
Uma tcnica acompanhante comear a dar explicaes em termos de teste: Aqui
como funciona agora. Quando eu tenho um Foo como esse e um Bar como aque-
le, ento a resposta 76. Se eu tenho um Foo como aquele e um Bar como esse,
entretanto, eu gostaria que a resposta fosse 67.
Voc pode fazer isso em mais altos nveis de abstrao. Se algum est ex-
plicando um diagrama de sequncia para voc, ento pode pedir permisso para
convert-lo em uma notao mais familiar. Ento voc digita um caso de teste que
contenha todos os objetos e variveis externamente visveis no diagrama.

Teste de aprendizado (Learning Test)1


Quando voc escreve testes para software produzido externamente? Antes da pri-
meira vez que voc vai usar uma nova fbrica no pacote.
Digamos que vamos desenvolver algo em cima da biblioteca Perfil de Dis-
positivo de Informao Mvel para Java. Queremos armazenar alguns dados no
Record-Store e recuper-los. Ns apenas escrevemos o cdigo e esperamos que fun-
cione? Isso um jeito de desenvolver.
Uma alternativa perceber que estamos prestes a usar um novo mtodo de
uma classe. Em vez de apenas us-lo, escrevemos um pequeno teste que verifica
que a API funciona como esperado. Ento, poderamos escrever:

RecordStore store;

public void setUp() {


store= RecordStore.openRecordStore("testing", true);
}

public void tearDown() {


RecordStore.deleteRecordStore("testing");
}

public void testStore() {


int id= store.addRecord(new byte[] {5, 6}, 0, 2);
assertEquals(2, store.getRecordSize(id));
byte[] buffer= new byte[2];

1
Obrigado a Jim Newkirk e Laurent Bossavit por sugerirem (independentemente um do outro) esse
padro.
Captulo 26 Padres de Barra Vermelha 157

assertEquals(2, store.getRecord(id, buffer, 0));


assertEquals(5, buffer[0]);
assertEquals(6, buffer[1]);
}

Se nosso entendimento da API estiver correto, ento o teste passar de primeira.


Jim Newkirk apresentou um projeto em que Testes de Aprendizagem eram
rotineiramente escritos. Quando novas liberaes do pacote chegavam, primeiro
os testes eram executados (e corrigidos, se necessrio). Se os testes no rodavam,
ento no havia sentido executar a aplicao, pois certamente no rodaria. Uma
vez que os testes executavam, a aplicao executava sempre.

Outro teste (Another Test)


Como voc evita que uma discusso tcnica desvie do assunto? Quando uma ideia
tangencial emerge, adicione um teste lista e volte ao assunto.
Eu amo discusses errantes (voc leu a maior parte do livro agora, ento,
provavelmente, chegou a essa concluso sozinho). Manter uma conversa es-
tritamente no caminho uma grande forma de asfixiar ideias brilhantes. Voc
pula daqui para l para acol, e como chegamos aqui? Quem se importa, isso
legal!
s vezes, programao apoia-se em avanos. A maioria da programao,
contudo, progride em pequenos avanos. Eu tenho dez coisas para implemen-
tar. Eu me torno um perfeito procrastinador do item nmero quatro. Recuar a
conversas fiadas uma das minhas formas de evitar trabalho (e talvez o medo
que vem com ele).
Dias improdutivos inteiros me ensinaram que, s vezes, melhor ficar na
trilha. Quando eu me sinto dessa forma, sado novas ideias com respeito, mas no
permito que desviem minha ateno. Eu as escrevo no final da lista e, ento, volto
ao que estava trabalhando.

Teste de regresso (Regression Test)


Qual a primeira coisa que voc faz quando um defeito informado? Escreva o
menor teste possvel que falhe e que, uma vez rodado, ser reparado.
158 Parte III Padres para Desenvolvimento Guiado por Testes

Testes de regresso so testes que, com perfeito conhecimento prvio, voc


teria escrito quando estava codificando originalmente. Cada vez que voc tem que
escrever um teste de regresso, pense em como poderia saber, em primeiro lugar,
escrever o teste.
Voc tambm ganhar valor testando em nvel de aplicao inteira. Testes
de regresso para a aplicao do a seus usurios a chance de falar concretamente
a voc sobre o que est errado e o que esperavam. Testes de regresso em menor
escala so um jeito de melhorar seu teste. O relatrio de defeitos ser um grande
e bizarro nmero negativo em um relatrio. A lio para voc que precisa usar
todos os testes que fez quando estiver escrevendo sua Lista de Testes.
Voc pode ter que refatorar o sistema antes de poder isolar facilmente o defei-
to. O defeito nesse caso era o jeito do seu sistema dizer: Voc ainda no terminou
de me projetar completamente.

Pausa (Break)
O que voc faz quando se sente cansado ou travado? Faa uma pausa.
Tome um drink, d uma caminhada, tire uma soneca. Lave suas mos do com-
promisso emocional das decises que acabou de tomar e dos caracteres que digitou.
Frequentemente, essa distncia o que precisa para libertar a ideia que estava
faltando. Voc estar de p quando perceber Eu no tentei com os parmetros
invertidos! Faa uma pausa de qualquer forma. D a si alguns minutos. A ideia
no ir embora.
Se voc no tiver a ideia, ento reveja seus objetivos para a sesso. Eles
ainda so realistas, ou voc deveria escolher novos objetivos? O que est tentando
realizar impossvel? Se for, quais so as implicaes para o time?
Dave Ungar chama isso de sua Metodologia do Chuveiro. Se voc sabe o
que digitar, ento digite. Se voc no sabe o que digitar, ento tome uma ducha
e fique no chuveiro at saber o que digitar. Muitos times sero mais felizes, mais
produtivos e vo cheirar muito melhor se seguirem seu conselho. TDD um refi-
namento da Metodologia do Chuveiro de Ungar. Se voc sabe o que digitar, digite
a Implementao bvia. Se voc no sabe o que digitar, ento Faa de conta*. Se
o projeto certo ainda no est claro, ento Triangule**. Se voc ainda no sabe o
que digitar, ento pode tomar aquela ducha.
A Figura 26.1 mostra a dinmica no trabalho em fazer um Intervalo. Voc
est ficando cansado, ento menos capaz de perceber que est cansado, ento vai
ficando mais e mais cansado.

* N. de R. T.: Use o padro de teste Faa de conta (Fake It), ver Captulo 28.
** N. de R. T.: Use o padro de teste Triangule (Triangulate), ver Captulo 28.
Captulo 26 Padres de Barra Vermelha 159

Fadiga

Julgamento

Figura 26.1 Fadiga afeta negativamente o Julgamento, o que afeta negativamente a Fadiga.

O caminho para fora desse lao introduzir um elemento adicional de fora.

Na escala de horas, manter uma garrafa dgua ao lado do teclado para


que a biologia fornea a motivao para intervalos regulares.
Na escala de um dia, compromissos depois do horrio regular de tra-
balho podero ajud-lo a parar quando precisar dormir antes de conti-
nuar.
Na escala de uma semana, compromissos de finais de semana ajudam a
tirar sua conscincia, pensamentos sugadores de energia, do trabalho.
(Minha esposa jura que eu tenho minhas melhores ideias na sexta-feira
noite.)
Na escala de um ano, polticas de frias obrigatrias ajudam voc a re-
novar-se completamente. Os franceses fazem isso direito duas semanas
contguas de frias no so o suficiente. Voc gasta a primeira semana des-
comprimindo e a segunda semana se preparando para voltar ao trabalho.
Assim, trs semanas, ou melhor, quatro semanas so necessrias para voc
ser o mais eficiente possvel pelo resto do ano.

H um outro lado em fazer intervalos. s vezes, quando confrontados com


um problema difcil, o que voc precisa fazer continuar, avanar sobre ele. Con-
tudo, a cultura de programao to infectada pelo esprito do macho Eu vou
arruinar minha sade, me alienar da minha famlia e me matar se necessrio
que no me sinto compelido a dar qualquer conselho nesse sentido. Se voc est
viciado em cafena e no est fazendo qualquer progresso, ento talvez no devesse
fazer tantos intervalos. Entretanto, d uma caminhada.

Faa de novo (Do Over)


O que voc faz quando se sente perdido? Jogue o cdigo fora e comece de
novo.
160 Parte III Padres para Desenvolvimento Guiado por Testes

Voc est perdido. Fez um intervalo, lavou as mos no riacho, escutou o sino
do templo Tibetano e ainda est perdido. O cdigo que estava indo to bem uma
hora atrs est uma confuso agora; voc no consegue pensar em como ter o pr-
ximo caso de teste funcionando e pensou em mais 20 testes que realmente deveria
implementar.
Isso me aconteceu muitas vezes enquanto estava escrevendo este livro. Eu
tinha o cdigo um pouco retorcido. Mas, eu tenho que terminar o livro. As
crianas esto famintas, e os coletores de impostos esto batendo porta. Minha
reao seria desenrol-lo o suficiente para seguir em frente. Depois de uma pausa
para reflexo, comear de novo sempre fazia mais sentido. A nica vez que avan-
cei de qualquer maneira, tive que jogar fora 25 pginas de manuscrito, pois estava
baseado em uma bvia deciso de programao estpida.
Meu exemplo preferido de fazer de novo uma histria que Tim Mackinnon
me contou. Ele estava entrevistando uma pessoa atravs do recurso simples de
pedir a ela para programar em par com ele por uma hora. No final da sesso, eles
implementaram muitos novos casos de teste e fizeram algumas boas refatoraes.
Contudo, era final do dia, e eles se sentiam cansados quando fizeram, ento des-
cartaram seu trabalho.
Se voc programa em pares, mudar de parceiros uma boa forma de motivar
fazer de novo produtivos. Voc tentar explicar a baguna complicada que fez por
alguns minutos quando seu novo parceiro, completamente no envolvido com os
enganos que voc fez, pegar gentilmente o teclado e dir: Eu sinto muitssimo
por ser to direto, mas e se comessemos assim. . .

Mesa barata, cadeira legal (Cheap Desk, Nice Chair)


Qual configurao fsica voc deveria usar para TDD? Tenha uma cadeira real-
mente legal, poupando no resto dos mveis se necessrio.
Voc no consegue programar bem se suas costas doem. Contudo, organi-
zaes que gastaro $100.000 por ms em um time no gastaro $10.000 em
cadeiras decentes.
Minha soluo usar mesas dobrveis baratas e feias para meus computa-
dores, mas comprar as melhores cadeiras que eu puder encontrar. Eu tenho mui-
to espao na mesa e posso facilmente conseguir mais, e estou fresco e preparado
para programar de tarde e pela manh. Fique confortvel quando estiver progra-
mando em pares. Limpe a superfcie da mesa o suficiente para poder deslizar o
teclado para trs e para frente. Cada parceiro deveria ser capaz de sentar-se con-
fortvel e diretamente em frente ao teclado quando estiver pilotando*. Um dos
meus truques de treino favorito chegar atrs de um par que est programando
sem parar e gentilmente deslizar o teclado para que fique confortavelmente colo-
cado para a pessoa digitar.

* N. de R. T.: Na programao por pares, usa-se o termo piloto para designar quem est usando o
teclado e copiloto para o outro parceiro.
Captulo 26 Padres de Barra Vermelha 161

Manfred Lange assinala que alocao cuidadosa de recursos tambm se apli-


ca ao hardware de computador. Obtenha mquinas baratas/lentas/velhas para ver
e-mails e navegao pessoal e as mquinas mais potentes possveis para desenvol-
vimento compartilhado.
Captulo 27
Padres de Teste

Esses padres so tcnicas mais detalhas para escrever testes.

Teste filho (Child Test)


Como voc executa um caso de teste que se mostrou muito grande? Escreva um
caso de teste menor que represente a parte que no funciona do caso de teste
maior. Consiga rodar o caso de teste menor. Reintroduza o caso de teste maior.
O ritmo vermelho/verde/refatore to importante para sucesso contnuo que,
quando voc est arriscando perd-lo, vale a pena um esforo extra para mant-lo.
Isso comumente me acontece quando escrevo um teste que, acidentalmente, requer
muitas mudanas de forma a funcionar. Mesmo dez minutos com uma barra ver-
melha me d arrepios.
Quando escrevo um teste que muito grande, primeiro tento aprender a li-
o. Por que ele era muito grande? O que eu poderia ter feito diferente para ser
menor? Como estou me sentindo agora mesmo?
Com a contemplao metafsica central terminada, eu apago o teste desagra-
dvel e comeo de novo. Bem, ter essas trs coisas funcionando de uma vez era
demais. Contudo, se eu tiver A, B e C funcionando, conseguir fazer a coisa inteira
funcionar seria moleza. s vezes, eu realmente excluo o teste e, s vezes, apenas
mudo o nome para comear com um x para que no execute. (Posso contar um se-
gredo? s vezes, nem mesmo me incomodo em apagar o teste desagradvel. Pssss...
eu permaneo com os dois, conte-os, dois testes no funcionando por no mais que
alguns minutos enquanto eu fao o teste filho funcionar. Eu poderia estar cometen-
do um erro ao fazer isso. Dois testes no funcionando poderiam facilmente ser um
resqucio dos meus maus velhos tempos de teste-depois-se-der.)
164 Parte III Padres para Desenvolvimento Guiado por Testes

Tente ambas as formas. Veja se voc se sente diferente; programe diferente


quando tiver dois testes que no funcionam. Responda conforme o caso.

Objeto simulado (Mock Object)


Como voc testa um objeto que se baseia em um recurso caro ou complicado? Crie
uma verso faz de conta do recurso que responde com constantes.
H ao menos um material com valor equivalente a um livro sobre Objeto
Simulado1, mas isso servir como uma introduo. O exemplo clssico um banco
de dados. Bancos de dados levam um longo tempo para iniciarem; so difceis de
manter limpos; e, se esto localizados em um servidor remoto, amarram seus testes
a um local fsico em uma rede. O banco de dados tambm uma fonte frtil de
erros no desenvolvimento.
A soluo no usar um banco de dados real na maior parte do tempo. A
maioria dos testes escrita em termos de um objeto que age como um banco de
dados, mas est apenas assentado na memria.

public void testOrderLookup() {


Database db= new MockDatabase();
db.expectQuery("select order_no from Order where cust_no is 123");
db.returnResult(new String[] {"Order 2" ,"Order 3"});
. . .
}

Se o MockDatabase no tiver a consulta que espera, ento ele lana uma exceo. Se a
consulta est correta, ento ele retorna algo que parece com um conjunto resulta-
do construdo por strings constantes.
Outro valor de simulaes, alm de desempenho e confiabilidade, legibi-
lidade. Voc pode ler o teste precedente de um extremo ao outro. Se tivesse um
banco de dados de testes cheio de dados reais, quando v que uma consulta deveria
ter resultado em 14 respostas, no tem ideia do porqu de 14 ser a resposta certa.
Se voc quiser usar Objetos Simulados, no pode armazenar facilmente re-
cursos caros em variveis globais (mesmo que mascarados como Singletons). Se
voc fizer isso, ento ter que configurar a global para um Objeto Simulado, rodar
o teste e ter certeza de reinicializar a global quando tiver terminado.
Houve vezes em que eu estava furioso com essa restrio. Massimo Arnoldi
e eu estvamos trabalhando em algum cdigo que se baseava em um conjunto de
taxas de cmbio armazenadas em uma varivel global. Cada teste precisava de
subconjuntos diferentes de dados, e, s vezes, precisavam de diferentes taxas de
cmbio. Depois de um tempo tentando fazer a varivel global funcionar, decidi-
mos, em uma manh (decises de projeto corajosas vm mais frequentemente de
manh para mim), apenas passar o Exchange sempre que precisssemos dele. Pen-
svamos que teramos que modificar centenas de mtodos. No final, adicionamos

1
Por exemplo, ver www.mockobjects.com
Captulo 27 Padres de Teste 165

um parmetro a dez ou quinze mtodos, e limpamos outros aspectos do projeto


pelo caminho.
Objetos Simulados o encorajam no caminho de considerar cuidadosamente
a visibilidade de cada objeto, reduzindo o acoplamento no seu projeto. Eles adi-
cionam um risco ao projeto e se o Objeto Simulado no se comportar como o
objeto real? Voc pode reduzir essa estratgia tendo um conjunto de testes para o
Objeto Simulado que pode tambm ser aplicado ao objeto real quando tornar-se
disponvel.

Autodesvio (Self Shunt)


Como voc testa se um objeto se comunica corretamente com outro? Tenha o ob-
jeto que est sob teste se comunicando com o caso de teste, em vez do objeto que
ele espera.
Suponha que quisssemos atualizar dinamicamente a barra verde na interface
de teste do usurio. Se pudssemos conectar um objeto ao TestResult, ento pode-
ramos ser notificados quando um teste executou, quando ele falhou, quando uma
sute inteira comeou e terminou, e assim por diante. Se formos notificados de que
um teste executou, atualizaremos a interface.
Aqui est um teste para isso:

ResultListenerTest
def testNotification(self):
result= TestResult()
listener= ResultListener()
result.addListener(listener)
WasRun("testMethod").run(result)
assert 1 == listener.count

O teste precisa de um objeto para contar o nmero de notificaes:

ResultListener
class ResultListener:
def __init__(self):
self.count= 0
def startTest(self):
self.count= self.count + 1

Mas, espere. Por que precisamos de um objeto separado para o ouvinte? Podemos
apenas usar o prprio caso de teste. O prprio TestCase se torna uma espcie de
Objeto Simulado.

ResultListenerTest
def testNotification(self):
self.count= 0
result= TestResult()
result.addListener(self)
166 Parte III Padres para Desenvolvimento Guiado por Testes

WasRun("testMethod").run(result)
assert 1 == self.count
def startTest(self):
self.count= self.count + 1

Testes escritos com Self Shunt tendem a serem lidos melhor que testes escritos
sem ele. O teste anterior um bom exemplo. A contagem era 0, e, ento, ela era
1. Voc pode ler a sequncia certa no teste. Como ele chegou a 1? Algum deve ter
chamado startTest(). Como startTest() foi chamado? Deve ter acontecido quando
o teste foi executado. Esse outro exemplo de simetria a segunda verso do m-
todo teste tem dois valores para count em um s lugar, enquanto a primeira verso
tem count configurado para 0 em uma classe e esperava ser 1 em outra.
Self Shunt pode necessitar que voc use Extrair Interface (Extract Interface)*
para ter uma interface para implementar. Voc ter que decidir se extrair a interfa-
ce mais fcil, ou se testar a classe existente como uma caixa preta mais fcil. No
entanto, eu tenho notado que interfaces extradas para shunts tendem a ter suas
terceira e subsequentes implementaes logo em seguida.
Como resultado de usar AutoDesvio, voc ver testes em Java que imple-
mentam todo o tipo de interfaces bizarras. Em linguagens de tipagem dinmica,
a classe do caso de teste precisa apenas implementar aquelas operaes que
so realmente usadas na execuo do teste. Em Java, todavia, voc tem que
implementar todas as operaes da interface, mesmo que a maioria das im-
plementaes esteja vazia; portanto, voc gostaria de interfaces to restritas
quanto possvel. As implementaes deveriam retornar um valor razovel ou
lanar uma exceo, dependendo se voc quer ser notificado se uma operao
inesperada for invocada.

String de registro (Log String)


Como voc testa se a sequncia em que as mensagens so chamadas est correta?
Mantenha um registro (log) em uma string e, quando uma mensagem for chama-
da, acrescente-a string.
O exemplo de xUnit serve. Temos um Mtodo Template, no qual esperamos
chamar setUp(), um mtodo de teste, e tearDown(), nessa ordem. Implementando os
mtodos para gravarem em uma string quando so chamados, l-se bem o teste:

def testTemplateMethod(self):
test= WasRun("testMethod")
result= TestResult()
test.run(result)
assert("setUp testMethod tearDown " == test.log)

* N. de R. T.: Extrair Interface uma refatorao que cria uma nova interface a partir da interface
de uma classe, geralmente um subconjunto da original. Ver mais informaes no Captulo 31 deste
livro, FOWLER, Martin. Refatorao: aperfeioando o projeto de cdigo existente. Porto Alegre:
Bookman, 2004.
Captulo 27 Padres de Teste 167

E a implementao simples tambm:

WasRun
def setUp(self):
self.log= "setUp "
def testMethod(self):
self.log= self.log + "testMethod "
def tearDown(self):
self.log= self.log + "tearDown "

Strings de Registro so particularmente teis quando voc est implementan-


do Observer e espera notificaes chegarem em uma certa ordem. Se esperava de-
terminadas notificaes, mas no se importa com a ordem, ento poderia manter
um conjunto de strings e usar comparao de conjuntos na assero.
String de registro funciona bem com AutoDesvio. O caso de teste implemen-
ta os mtodos na interface desviada adicionando ao registro e ento retornando
valores razoveis.

Modelo de teste de acidentes (Crash Test Dummy)*


Como voc testa cdigo de erro que provavelmente ser pouco invocado? Invo-
que-o de qualquer forma com um objeto especial que lana uma exceo em vez
de fazer trabalho real.
Cdigo que no testado no funciona. Essa parece ser a suposio segura.
O que voc faz com todas aquelas condies estranhas de erro ento? Voc tem
que test-las tambm? Apenas se quiser que elas funcionem.
Vamos dizer que queiramos testar o que acontece com a nossa aplicao quan-
do o sistema de arquivos est cheio. Poderamos ter um trabalho criando muitos
arquivos grandes e enchendo o sistema de arquivos, ou poderamos Fazer de con-
ta**. Fazer de conta no soa digno, no ? Ns vamos simul-lo.
Aqui est nosso Modelo de Teste de Acidentes para um arquivo:

private class FullFile extends File {


public FullFile(String path) {
super(path);
}
public boolean createNewFile() throws IOException {
throw new IOException();
}
}

* N. de R. T.: Crash test dummy o nome dado aos bonecos usados para testes e simulaes de aci-
dentes de carro. O nome traduzido visa manter essa metfora.
** N. de R. T.: Usar o padro Faz de conta (Fake It), ver Captulo 28.
168 Parte III Padres para Desenvolvimento Guiado por Testes

Agora podemos escrever nosso teste de Exceo Esperada:

public void testFileSystemError() {


File f= new FullFile("foo");
try {
saveAs(f);
fail();
} catch (IOException e) {
}
}

Um Modelo de Teste de Acidente como um Objeto Simulado, exceto por


no precisar simular todo o objeto. Classes internas annimas de Java funcionam
bem para sabotar apenas o mtodo certo para simular o erro que queremos exerci-
tar. Voc pode sobrescrever apenas aquele mtodo que quiser, bem ali no seu caso
de teste, fazendo o caso de teste mais fcil de ler:

public void testFileSystemError() {


File f= new File("foo") {
public boolean createNewFile() throws IOException {
throw new IOException();
}
};
try {
saveAs(f);
fail();
} catch (IOException e) {
}
}

Teste quebrado (Broken Test)


Como voc deixa uma sesso de programao quando est programando sozinho?
Deixe o ltimo teste quebrado.
Richard Gabriel me ensinou esse truque de terminar uma sesso de escrita no
meio da frase. Quando voc se senta de novo, olha a meia frase e tem que desco-
brir o que estava pensando quando a escreveu. Uma vez que tenha a linha de pen-
samento de volta, termina a frase e continua. Sem o estmulo de terminar a frase,
voc pode gastar muitos minutos farejando o que trabalhar em seguida, tentando
lembrar seu estado mental e ento, finalmente, voltando a digitar.
Eu tentei a tcnica anloga para meus projetos solo, e realmente gosto
do efeito. Terminar uma sesso solo escrevendo um caso de teste e o rodando
Captulo 27 Padres de Teste 169

para ter certeza de que no passe. Quando voc volta ao cdigo, tem ento um
lugar bvio para comear. Voc tem uma marca bvia e concreta para ajudar a
lembr-lo do que estava pensando; e fazer com que o teste funcione deveria ser
trabalho rpido, assim voc colocar rapidamente seus ps de volta no caminho
da vitria.
Eu pensei que me incomodaria ter um teste quebrado durante a noite. Isso
no me incomoda, talvez por que saiba que o programa no est terminado. Um
teste que no funciona no faz do programa menos terminado, ele apenas faz o
estado do programa se manifestar. A habilidade de pegar uma linha de desenvol-
vimento rapidamente depois de semanas de intervalo digna daquele pequeno
remorso de se afastar de uma barra vermelha.

Check-in limpo (Clean Check-in)


Como voc deixa uma sesso de programao quando est programando em um
time? Deixe todos os testes rodando.
Eu me contradigo? Difcil.

Bubba Whitman, irmo estivador de Walt (Whitman)


Quando voc responsvel por sua equipe, o cenrio muda completamente.
Quando voc comea a programar em um projeto em equipe, no sabe em de-
talhes o que aconteceu no cdigo desde a ltima vez que o viu. Voc precisa co-
mear de um lugar de confiana e certeza. Portanto, certifique-se sempre de que
todos os testes esto rodando antes de dar entrada no seu cdigo. ( um pouco
como cada caso de teste deixar o mundo em um bom estado conhecido, se voc
inclinado a metforas de computador para comportamentos humanos, o que
no sou, geralmente.)
A sute de testes que voc roda quando faz check-in pode ser mais exten-
sa que aquela que roda a cada minuto durante o desenvolvimento. (No desista
de rodar a sute inteira sempre at ser lento o suficiente para ser irritante.) Voc
ocasionalmente encontrar um teste que no funciona no conjunto de integrao
quando tenta fazer check-in. O que fazer?
A regra mais simples apenas jogar fora seu trabalho e comear de novo. O
teste que no funciona uma evidncia muito forte de que voc no sabia o sufi-
ciente para programar o que acabou de programar. Se o time adotasse essa regra,
ento haveria uma tendncia de as pessoas fazerem check-in mais frequentemente,
pois a primeira pessoa a fazer check-in no se arriscaria a perder trabalho. Fazer
check-in mais frequentemente provavelmente uma coisa boa.
170 Parte III Padres para Desenvolvimento Guiado por Testes

Uma abordagem ligeiramente mais libertina d a voc a chance de corrigir


o defeito e tentar novamente. Para evitar tomar conta dos recursos de integrao,
voc provavelmente deveria desistir depois de alguns minutos e comear de novo.
Nem preciso dizer, mas vou dizer mesmo assim, que comentar testes para fazer
a sute passar proibido e justifica a multa de ter que pagar algumas cervejas no
final da tarde de sexta-feira depois da reunio de planejamento.
Captulo 28
Padres de Barra Verde

Depois de ter um teste no funcionando, voc precisa arrum-lo. Se voc trata


uma barra vermelha como uma condio a ser corrigida o mais rpido possvel,
ento descobrir que pode ter o verde rapidamente. Use esses padres para fazer
o cdigo funcionar (mesmo se o resultado no algo com o qual queira conviver
por mais de uma hora).

Fazer de conta (at faz-lo) Fake It (Til You Make It)


Qual sua primeira implementao uma vez que tem um teste que no funciona?
Retorne uma constante. Depois de ter o teste rodando, gradualmente transforme a
constante em uma expresso usando variveis.
Um exemplo simples aconteceu em nossa implementao de xUnit:

return "1 run, 0 failed"

tornou-se:

return "%d run, 0 failed" % self.runCount

tornou-se:

return "%d run, %d failed" % (self.runCount , self failureCount)

Fazer de conta um pouco como cravar uma estaca* acima de sua cabea
quando est escalando uma rocha. Voc realmente no chegou l ainda (o teste

* N. de R. T.: O termo correto em montanhismo pton, mas estaca parece ser um conceito mais fcil
de entender.
172 Parte III Padres para Desenvolvimento Guiado por Testes

est l, mas a estrutura do cdigo est errada). Mas, quando voc chegar l, voc
sabe que estar seguro (o teste ainda vai rodar).
Fazer de conta realmente arrasta algumas pessoas para o caminho errado. Por
que voc faria algo que sabe que ter que arrancar? Porque ter algo rodando me-
lhor que no ter nada rodando, especialmente se voc tem o teste para provar isto.
Peter Hansen enviou essa histria:

Algo aconteceu ontem quando, como dois novatos em TDD, meu parceiro e eu agres-
sivamente seguimos a lei risca e cometemos pecados para termos um teste fun-
cionando rapidamente. No processo, percebemos que no tnhamos implementado
adequadamente o teste, voltamos atrs e o corrigimos, e ento fizemos o cdigo fun-
cionar de novo. O primeiro cdigo que funcionou acabou no sendo visto durante o
tempo em que trabalhamos para que funcionasse e ns escolhemos olhar um para o
outro e dissemos . .. voc olhou aquilo!, pois aquela abordagem nos ensinou
algo que no sabamos.

Como uma implementao faz de conta os teria ensinado que seu teste estava
escrito errado? Eu no sei, mas aposto que ficariam felizes em no investir na so-
luo real para descobrir.
H um monte de efeitos que tornam o faz de conta poderoso.

Psicolgico Ter uma barra verde nos faz sentir completamente diferentes
do que ter uma barra vermelha. Quando a barra est verde, voc sabe
onde est. Voc pode refatorar dali com confiana.
Controle de escopo Programadores so bons em imaginar todo o tipo de
problemas futuros. Comeando com um exemplo concreto e generalizan-
do da, voc se previne de se confundir prematuramente com preocupa-
es alheias. Voc pode fazer um trabalho melhor resolvendo o problema
imediato, pois est focado. Quando vai implementar o prximo caso de
teste, voc pode se focar tambm naquele, sabendo que o teste anterior
est garantidamente funcionando.

Faz de conta viola a regra que diz para voc no escrever qualquer cdigo
que no precisa? Eu no acho, porque no passo de refatorao voc est eliminan-
do duplicao de dados entre o caso de teste e o cdigo. Quando eu escrevo:

MyDate
public MyDate yesterday() {
return new MyDate("28.2.02");
}

h duplicao entre o teste e o cdigo. Posso contorn-la escrevendo:

MyDate
public MyDate yesterday() {
return new MyDate(new MyDate("31.3.02").days()-1);
}
Captulo 28 Padres de Barra Verde 173

Mas ainda h duplicao. Contudo, posso eliminar a duplicao de dados (por-


que this igual a MyDate(31.1.02) para os propsitos do meu teste) escrevendo:

MyDate
public MyDate yesterday() {
return new MyDate(this.days()-1);
}

Nem todo mundo convencido por esse monte de sofismas, motivo pelo qual
voc pode usar Triangulao ao menos at se cansar e comear a usar Fazer de
conta ou mesmo Implementao bvia.
Quando uso Fazer de conta, lembro de longas viagens de carro com os filhos
no banco de trs. Escrevo o teste, o fao funcionar de algum jeito feio, e ento:
No me faa parar esse carro e escrever outro teste. Se eu tiver que encostar, voc
vai se arrepender.
Certo, certo, pai. Eu vou limpar o cdigo. No tem que ficar to zangado.

Triangular (Triangulate)
Como voc conduz abstrao com testes de forma mais convervadora? Abstraia
apenas quando tiver dois ou mais exemplos.
Aqui est uma situao. Suponha que queiramos escrever uma funo que
retornar a soma de dois inteiros. Escrevemos:

public void testSum() {


assertEquals(4, plus(3, 1));
}

private int plus(int augend, int addend) {


return 4;
}

Se estamos triangulando para o projeto certo, temos de escrever:

public void testSum() {


assertEquals(4, plus(3, 1));
assertEquals(7, plus(3,4));
}

Quando temos o segundo exemplo, podemos abstrair a implementao de plus():

private int plus(int augend, int addend) {


return augend + addend;
}
174 Parte III Padres para Desenvolvimento Guiado por Testes

Triangulao atrativa, pois as regras para ela parecem to claras. As regras


para Fazer de conta, o qual se baseia em nosso senso de duplicao entre os casos
de teste e a implementao faz de conta para guiar a abstrao, parecem um pouco
vagas e sujeitas interpretao. Embora paream simples, as regras para Triangu-
lao criam um lao infinito. Uma vez que tenhamos as duas asseres e tenhamos
abstrado a implementao correta para plus, podemos apagar uma das asseres
em razo de ser completamente redundante com a outra. Se fizermos isso, todavia,
podemos simplificar a implementao de plus() para retornar apenas uma constan-
te que requer que adicionemos uma assero.
Eu uso Triangulao apenas quando estou realmente muito inseguro sobre a
abstrao correta para o clculo. Caso contrrio, confio na Implementao bvia
ou Fazer de conta.

Implementao bvia (Obvious Implementation)


Como voc implementa operaes simples? Apenas implemente-as.
Fazer de conta e Triangulao so passinhos pequeninos. s vezes, voc tem
certeza que sabe como implementar uma operao. V em frente. Por exemplo,
eu realmente usaria Faz de conta para implementar algo simples como plus()? Ge-
ralmente no. Eu apenas digitaria a Implementao bvia. Se percebesse que fui
surpreendido por barras vermelhas, ento iria em passos menores.
No h virtude especial na natureza intermediria de Faz de conta ou Trian-
gulao. Se voc sabe o que digitar e pode faz-lo rapidamente, ento faa. Con-
tudo, usando apenas Implementao bvia, voc est exigindo perfeio de si.
Psicologicamente, esse pode ser um movimento devastador. E se o que voc escreve
no realmente a mais simples mudana que faria o teste funcionar? E se seu par-
ceiro mostrar a voc um mais simples? Voc um fracassado. Seu mundo desaba a
sua volta! Voc morre. Voc congela.
Resolver cdigo limpo ao mesmo tempo que resolve que funciona pode
ser demais para fazer de uma s vez. Logo que possvel, volte a resolver que fun-
ciona, e ento resolva cdigo limpo no tempo livre.
Mantenha o rastro de quo frequente voc surpreendido por barras ver-
melhas usando Implementao bvia. Eu ficarei preso nesses ciclos onde di-
gitarei uma Implementao bvia, mas no vai funcionar. Mas, agora tenho
certeza que sei o que devia digitar, ento eu digito aquilo. No funciona. Ento
agora. . . . Isso acontece especialmente com erros falta-ou-sobra-um e erros de
positivo/negativo.
Voc quer manter o ritmo vermelho/verde/refatore. Implementao bvia
uma segunda marcha. Esteja preparado para reduzir a marcha se seu crebro co-
mear a preencher cheques que seus dedos no podem descontar.
Captulo 28 Padres de Barra Verde 175

Um para muitos (One to Many)


Como voc implementa uma operao que funciona com colees de objetos? Im-
plemente-a sem as colees primeiro, ento a faa funcionar com colees.
Por exemplo, suponha que estejamos escrevendo uma funo para somar um
vetor de nmeros. Podemos comear com um:

public void testSum() {


assertEquals(5, sum(5));
}

private int sum(int value) {


return value;
}

(Estou implementando sum() na classe TestCase para evitar escrever uma nova clas-
se apenas para um mtodo.)
Queremos testar sum(new int[] {5, 7}) em seguida. Primeiro, adicionamos um
parmetro para sum(), pegando um vetor de valores:

public void testSum() {


assertEquals(5, sum(5, new int[] {5}));
}

private int sum(int value, int[] values) {


return value;
}

Podemos olhar para esse passo como um exemplo de Isolar a Mudana*. Uma vez
que adicionemos o parmetro no caso de teste, estamos livres para mudar a imple-
mentao sem afetar o caso de teste.
Agora podemos usar a coleo em vez de um s valor:

private int sum(int value, int[] values) {


int sum= 0;
for (int i= 0; i<values.length; i++)
sum += values[i];
return sum;
}

Em seguida, podemos apagar o parmetro simples no usado:

public void testSum() {


assertEquals(5, sum(new int[] {5}));
}

* N. de R. T.: Isolar a Mudana (Isolate Change) uma refatorao descrita no Captulo 31.
176 Parte III Padres para Desenvolvimento Guiado por Testes

private int sum(int[] values) {


int sum= 0;
for (int i= 0; i<values.length; i++)
sum += values[i];
return sum;
}

O passo anterior tambm um exemplo de Isolar a Mudana, onde mudamos o


cdigo para que possamos mudar os casos de teste sem afetar o cdigo. Agora
podemos enriquecer o caso de teste como planejado:

public void testSum() {


assertEquals(12, sum(new int[] {5, 7}));
}
Captulo 29
Padres xUnit

Esses so padres para utilizao de um membro da famlia xUnit de frameworks


de teste.

Assero (Assertion)
Como voc verifica que os testes funcionaram corretamente? Escreva expresses
booleanas que automatizem seu julgamento sobre se o cdigo funcionou ou no.
Se vamos fazer os testes totalmente automatizados, ento cada pedao de
julgamento humano tem de ser extrado da avaliao de resultados. Precisamos
pressionar um boto e fazer todas as decises necessrias para verificar o correto
funcionamento do cdigo que o computador roda. Isso sugere o seguinte.

As decises tm de ser booleanas Verdadeiro geralmente significa que


tudo est certo, e falso significa que algo inesperado aconteceu.
O estado dos booleanos tem de ser verificado pelo computador chamando
alguma variante de um mtodo assert().

Eu vi asseres como assertTrue(rectangle.area() != 0). Voc poderia re-


tornar qualquer coisa no nula e satisfazer esse teste, logo isso no mui-
to til. Seja especfico. Se a rea deveria ser 50, ento diga que ela deve ser 50:
assertTrue(rectangle.area() ==50). Muitas implementaes de xUnit tm uma asser-
o especial para teste de igualdade. Testar igualdade comum, e, se voc sabe que
est testando igualdade, pode escrever uma mensagem de erro instrutiva. O va-
lor esperado geralmente vem primeiro, assim, em JUnit, escreveramos isso como
assertEquals(50, rectangle.area()).
178 Parte III Padres para Desenvolvimento Guiado por Testes

Pensar em objetos como caixas pretas difcil. Se eu tenho um Contract com


um Status que pode ser uma instncia de Offered ou Running, poderia me sentir como
se estivesse escrevendo um teste baseado na minha implementao esperada:

Contract contract= new Contract(); // Estado Offered por padro


contract.begin(); // Muda o estado para Running
assertEquals(Running.class, contract.status.class);

Esse teste muito dependente da implementao atual de status. O teste deveria


passar mesmo se a representao de status mudasse para um booleano. Talvez uma
vez que status mude para Running, seja possvel pedirmos a data de incio real.

assertEquals(. . ., contract.startDate()); // Lana uma exceo se o estado Offered

Estou ciente de que estou nadando contra a mar ao insistir que todos os tes-
tes devam ser escritos usando apenas o protocolo pblico. H at um pacote que
estende JUnit, chamado JXUnit, que permite testar o valor de variveis, mesmo
daquelas declaradas privadas.
Desejar teste caixa branca no um problema de teste, um problema de
projeto. Sempre que eu quiser usar uma varivel como uma forma de verificar se
o cdigo roda corretamente ou no, eu tenho uma oportunidade de melhorar o
projeto. Se eu me entregar a meu medo e apenas verificar a varivel, ento eu perco
a oportunidade. Dito isso, se a ideia de projeto no vier, ela no vem. Eu verificarei
a varivel, derramarei uma lgrima, farei uma nota para voltar aqui em um dos
meus dias mais espertos, e seguirei em frente.
O SUnit original (a primeira verso do framework de teste em Smalltalk) tinha
asseres simples. Se uma delas falha, ento um depurador surgia, voc arrumava o
cdigo e ia embora. Devido s IDEs de Java no serem to sofisticadas, e devido
construo de software baseado em Java frequentemente acontecer em um ambiente
batch, faz sentido adicionar informao da assero que ser exibida se ela falhar.
Em JUnit, isso toma a forma de um primeiro parmetro opcional1. Se voc
escrever assertTrue(Deveria ser verdadeiro, false) quando o teste executado, ver
uma mensagem de erro como Assero falhou: Deveria ser verdadeiro. Isso
frequentemente informao suficiente para envi-lo diretamente fonte do erro no
cdigo. Alguns times adotam a conveno de todas as asseres deverem ser acom-
panhadas de uma mensagem de erro instrutiva. Tente isso de ambas as formas e
veja se o investimento nas mensagens de erro compensa.

Fixture
Como voc cria objetos comuns necessrios para vrios testes? Converta as va-
riveis locais nos testes em variveis de instncia. Sobrescreva setUp() e inicialize
aquelas variveis.
1
Parmetros opcionais viriam no fim, mas, por legibilidade dos testes, ajuda ter a string explicativa
no incio.
Captulo 29 Padres xUnit 179

Se quisermos remover duplicao de nosso cdigo modelo, queremos remo-


v-la do nosso cdigo de teste tambm? Talvez.
Aqui est o problema: frequentemente voc escreve mais cdigo para confi-
gurar objetos em um estado interessante do que escreve para manipul-los e verifi-
car resultados. O cdigo para configurar os objetos o mesmo para muitos testes
(aqueles objetos que so as fixtures do teste, tambm conhecidos como scaffolding).
Essa duplicao ruim pelas seguintes razes.

Levam muito tempo para escrever, mesmo para copiar e colar, e gostara-
mos de escrever testes rpido.
Se precisamos mudar uma interface na mo, ento temos que mud-la em
muitos testes (exatamente o que esperaramos de duplicao).

Contudo, a mesma duplicao tambm boa. Testes escritos com o cdigo


de configurao bem ali, junto com as asseres, so legveis de cima a baixo. Se
fatorarmos o cdigo de configurao em um mtodo separado, ento teramos de
lembrar qual mtodo foi chamado e lembrar quais objetos eram usados antes de
podermos escrever o resto do teste.
xUnit d suporte a ambos os estilos de escrita de teste. Voc pode escrever
o cdigo de criao-de-base-de-teste com o teste, se espera que os leitores no se-
jam capazes de lembrar os objetos base facilmente. Todavia, pode tambm mover
cdigo comum de criao-de-base-de-teste em um mtodo chamado setUp(). Nele,
configure variveis de instncia para os objetos que sero usados no teste.
Aqui est um exemplo muito simples para motivar o valor de refatorar cdi-
go comum de configurao, mas curto o bastante para caber neste livro. Podera-
mos escrever:

EmptyRectangleTest
public void testEmpty() {
Rectangle empty= new Rectangle(0,0,0,0);
assertTrue(empty.isEmpty());
}

public void testWidth() {


Rectangle empty= new Rectangle(0,0,0,0);
assertEquals(0.0, empty.getWidth(), 0.0);
}

(Isso tambm demonstra a verso em ponto flutuante de assertEquals(), a qual re-


quer uma certa tolerncia.) Poderamos nos livrar da duplicao escrevendo:

EmptyRectangleTest
private Rectangle empty;

public void setUp() {


empty= new Rectangle(0,0,0,0);
}
180 Parte III Padres para Desenvolvimento Guiado por Testes

public void testEmpty() {


assertTrue(empty.isEmpty());
}

public void testWidth() {


assertEquals(0.0, empty.getWidth(), 0.0);
}

Extraamos o cdigo comum como um mtodo, um que o framework garan-


te chamar antes de nosso mtodo de teste ser chamado. Os mtodos de teste so
mais simples, mas temos que lembrar o que est em setUp() antes de podermos
entend-los.
Qual estilo voc deveria usar? Tente ambos. Eu quase sempre fatoro cdigo
de configurao comum, mas tenho uma memria forte para detalhes. Os leitores
dos meus testes s vezes reclamam que h demais para lembrar, dessa forma, talvez
eu devesse fatorar menos.
O relacionamento de subclasses de TestCase e de instncias daquelas subclas-
ses uma das mais confusas partes de xUnit. Cada novo tipo de fixture deveria
ser uma nova subclasse de TestCase. Cada nova fixture criada em uma instncia
daquela subclasse, usada uma vez, e ento descartada.
No exemplo anterior, se quisssemos escrever testes para um Rectangle no
vazio, ento criaramos uma nova classe, talvez NormalRectangleTest, e inicializar-
amos uma varivel diferente para um retngulo diferente em setUp(). Em geral, se
eu me vejo querendo uma fixture ligeiramente diferente, ento comeo uma nova
subclasse de TestCase.
Isso implica que no h relacionamento simples entre classes de teste e classes
de modelo. s vezes, uma fixture serve para testar vrias classes (embora isso seja
raro). s vezes, duas ou trs fixtures so necessrias para uma s classe do modelo.
Na prtica, voc geralmente vai terminar com aproximadamente o mesmo nmero
de classes de teste e de classes de modelo, mas no porque para cada classe de mo-
delo voc escreva uma, e apenas uma, classe de teste.

Fixture externa
Como voc libera recursos externos na fixture? Sobrescreva tearDown() e libere os
recursos.
Lembre que a meta de cada teste deixar o mundo exatamente no mesmo
estado de antes de rodar. Por exemplo, se voc abriu um arquivo durante o
teste, voc precisa certificar-se de fech-lo antes do teste terminar. Voc poderia
escrever:

testMethod(self):
file= File("foobar").open()
Captulo 29 Padres xUnit 181

try:
...run the test...
finally:
file.close()

Se o arquivo foi usado em muitos testes, ento poderia faz-lo parte da fixture
comum:

setUp(self):
self.file= File("foobar").open()
testMethod(self):
try:
...run the test...
finally:
self.file.close()

Primeiro, h aquela maldita duplicao da clusula finally nos dizendo que


estamos esquecendo algo no projeto. Segundo, esse mtodo est propenso a erros,
pois fcil esquecer a clusula finally ou esquecer completamente de fechar o ar-
quivo. Finalmente, h trs linhas de rudo no teste try, finally e o prprio close,
que no so centrais para a execuo do teste.
xUnit garante que um mtodo chamado tearDown() ser executado depois do
mtodo de teste. TearDown() ser chamado a despeito do que acontea no mtodo
de teste (apesar de se setUp() falhar, tearDown() no ser chamado). Podemos trans-
formar o teste anterior em:

setUp(self):
self.file= File("foobar").open()
testMethod(self):
...run the test...
tearDown(self):
self.file.close()

Mtodo de teste (Test Method)


Como voc representa um nico caso de teste? Como um mtodo cujo nome co-
mea com teste.
Voc vai ter centenas, at milhares, de testes em seu sistema. Como vai man-
ter o rastro de todos eles?
Linguagens de programao orientada a objetos tm trs nveis de hierarquia
para organizao:

Mdulo (pacote em Java)


Classe
Mtodo
182 Parte III Padres para Desenvolvimento Guiado por Testes

Se estamos escrevendo testes como fonte comum de cdigo, ento precisamos


achar um jeito de ajustar isso nessa estrutura. Se estamos usando classes para re-
presentar fixtures, ento o habitat para testes so os mtodos. Todos os testes que
dividem uma nica fixture sero mtodos na mesma classe. Testes que requerem
uma fixture diferente estaro em uma classe diferente.
Por conveno, o nome do mtodo comea com test. Ferramentas po-
dem procurar esse padro para automaticamente criar conjuntos de testes dada
uma classe. O restante do nome do mtodo deve sugerir a um futuro leitor de-
sinformado por que esse teste foi escrito. JUnit, por exemplo, tem um teste cha-
mado testAssertPosInfinityNotEqualsNegInfinity. Eu no lembro de escrever
esse teste, mas, pelo nome, assumo que em algum ponto o cdigo de assero
do JUnit para nmeros em ponto flutuante no distinguia entre infinito positivo
e negativo. ( meio feio h um condicional especial para manipular infinito.)
Mtodos de teste deveriam ser fceis de ler e ser cdigo muito direto. Se um
mtodo de teste est ficando longo e complicado, ento voc precisa jogar Passos
de Beb. O objetivo desse jogo escrever o menor mtodo de teste que representa
progresso real em direo a seu objetivo final. Trs linhas parecem ser o mnimo
sem discutir ofuscao (e lembre, voc est escrevendo esses testes para pessoas,
no apenas para o computador ou para si).
Patrick Logan contribuiu com uma ideia que vou experimentar, tambm des-
crita por McConnell2 e Caine e Gordon3,

Por alguma razo, eu estive trabalhando com esboos em praticamente tudo que
fao ultimamente. Teste no diferente. Quando escrevo testes, eu primeiro crio um
esboo do teste que eu quero escrever, por exemplo...

/* Adicionar a espaos da tupla. */


/* Pegar dos espaos da tupla. */
/* Ler dos espaos da tupla. */

Esses so lacunas indicadas at eu adicionar testes especficos sob cada cate-


goria. Quando adiciono testes, adiciono outro nvel de comentrios ao esboo. . .

/* Adicionar a espaos da tupla. */


/* Pegar dos espaos da tupla. */
/** Pegar uma tupla no existente. **/
/** Pegar uma tupla existente. **/
/** pegar mltiplas tuplas. **/
/* Ler dos espaos da tupla. */

Eu geralmente tenho apenas dois ou trs nveis no esboo. No consigo pen-


sar quando tenho mais. Mas, o esboo essencialmente se torna documentao do
contrato para a classe sendo testada. Os exemplos aqui esto abreviados, mas eles

2
McConnel, Steve. 1993. Code Complete, chapter 4. Seattle, Washington: Microsoft Press. ISBN
1556154844.
3
Caine, S.H. & Gordon, E. K. 1975. PDL: A Toll for Software Design, AFIPS Proceedings of the
1975 National Computer Conference.
Captulo 29 Padres xUnit 183

seriam mais especficos em linguagem contratual. (Eu no uso qualquer tipo de


suplemento para Java para automao parecida com Eiffel.)
Imediatamente abaixo do mais baixo nvel do esboo est o cdigo do caso
de teste.

Teste de exceo (Exception Test)


Como voc testa excees inesperadas? Pegue excees esperadas e ignore-as, fa-
lhando apenas se a exceo no lanada.
Vamos dizer que estamos escrevendo algum cdigo para buscar um valor. Se
o valor no encontrado, ento queremos lanar uma exceo. Testar a busca
muito fcil:

public void testRate() {


exchange.addRate("USD", "GBP", 2);
int rate= exchange.findRate(USD, "GBP");
assertEquals(2, rate);
}

Testar a exceo pode no ser to bvio. Aqui est como fazemos:

public void testMissingRate() {


try {
exchange.findRate("USD", "GBP");
fail();
} catch (IllegalArgumentException expected) {
}
}

Se findRate() no lana uma exceo, chamaremos fail(), um mtodo xUnit que


notifica que o teste falhou. Perceba que somos cuidadosos apenas para pegar a
exceo particular que esperamos, ento seremos tambm notificados se o tipo
errado de exceo lanado (incluindo falhas de assero).

Todos os testes (All Tests)


Como rodar todos os testes juntos? Faa uma sute de todas as sutes uma para
cada pacote, e uma agregando os pacotes de teste da aplicao inteira.
Suponha que voc adicione uma subclasse TestCase a um pacote e voc adicio-
na um mtodo de teste a esta classe. A prxima vez que todos os testes rodarem,
esse mtodo de teste deveria rodar tambm. (H aquela coisa guiada por testes o
anterior o esboo para um teste que eu provavelmente implementaria se no esti-
vesse ocupado escrevendo um livro.)
184 Parte III Padres para Desenvolvimento Guiado por Testes

Devido a isso no ser suportado na maioria das implementaes ou IDEs


xUnit, cada pacote deve declarar uma classe AllTests que implementa um mtodo
esttico suite() que retorna um TestSuite. Aqui est AllTests para o exemplo finan-
ceiro:

public class AllTests {


public static void main(String[] args) {
junit.swingui.TestRunner.run(AllTests.class);
}

public static Test suite() {


TestSuite result= new TestSuite("TFD tests");
result.addTestSuite(MoneyTest.class);
result.addTestSuite(ExchangeTest.class);
result.addTestSuite(IdentityRateTest.class);
return result;
}
}

Voc pode tambm fornecer a AllTests um mtodo main() para que a classe possa
ser diretamente rodada da IDE ou por uma linha de comando.
Captulo 30
Padres de Projeto

Uma das principais ideias de padres que, embora possa parecer como se esti-
vssemos todo o tempo resolvendo problemas completamente diferentes, a maio-
ria dos problemas que resolvemos gerada pelas ferramentas que usamos e no
1
por problemas externos prximos . Por causa disso, podemos esperar encontrar (e
realmente vamos encontrar) problemas comuns com solues comuns, mesmo em
meio a uma incrvel diversidade de contextos externos de soluo de problemas.
Aplicar objetos para organizar a computao um dos melhores exemplos
de subproblemas comuns internamente gerados, sendo resolvidos de formas pre-
visveis e comuns. O enorme sucesso de padres de projeto um atestado das
coisas em comum vistas por programadores na orientao a objetos. O sucesso
2
do livro Padres de Projeto , contudo, asfixiou qualquer diversidade na expres-
so desses padres. O livro parece ter um vis sutil em direo ao projeto como
uma fase. Isso certamente no faz meno refatorao como uma atividade
de projeto. Projeto em TDD requer um olhar ligeiramente diferente sobre os
padres de projeto.
Os padres de projeto tratados aqui no pretendem ser completos. Eles so
descritos o suficiente apenas para nos guiarmos atravs dos exemplos. Aqui esto
eles em resumo.

Command Representa a invocao de uma computao como um obje-


to e no apenas como uma mensagem.
Value Object Evita problemas de sinnimos construindo objetos cujos
valores nunca mudam depois de criados.

1
Alexander, Christofer 1970. Notes on the Synthesis of Form. Cambridge, MA: Harvard University
Press. ISBN: 0674627512.
2
Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. 1995. Design Patterns: Elements of
Reusable Object Oriented Software. Reading, MA: Addison-Wesley. ISBN 0201633612.
186 Parte III Padres para Desenvolvimento Guiado por Testes

Null Object Representa o caso bsico de uma computao por um ob-


jeto.
Template Method Representa sequncias invariveis de computao com
um mtodo abstrato que precisa ser especializado atravs de herana.
Pluggable Object Representa variao invocando outro objeto com duas
ou mais implementaes.
Pluggable Selector Evita subclasses gratuitas invocando dinamicamente
mtodos diferentes para instncias diferentes.
Factory Method Cria um objeto chamando um mtodo em vez de um
construtor.
Imposter Introduz variao introduzindo uma nova implementao de
um protocolo existente.
Composite Representa a composio do comportamento de uma lista de
objetos como um s objeto.
Collecting Parameter Passa um parmetro para ser usado para agregar
os resultados de uma computao em muitos objetos diferentes.

Os padres de projeto so agrupados com base em como so usados em


TDD, conforme mostrado na Tabela 30.1.

Tabela 30.1 Uso de padres de projeto no desenvolvimento dirigido por testes


Padro Escrita de teste Refatorao
Command X
Value Object X
Null Object X
Template Method X
Pluggable Object X
Pluggable Selector X
Factory Method X X
Imposter X X
Composite X X
Collecting Parameter X X
Captulo 30 Padres de Projeto 187

Command
O que voc faz quando precisa que a invocao de uma computao seja mais
complicada do que uma simples chamada de mtodo? Faa um objeto para a com-
putao e invoque-o.
Enviar mensagens maravilhoso. Linguagens de programao tornam o
envio de mensagens sintaticamente fcil; e ambientes de programao tornam a
manipulao de mensagens fcil (por exemplo, refatoraes para renomear uma
mensagem automaticamente). Contudo, s vezes, apenas enviar uma mensagem
no o suficiente.
Por exemplo, suponha que queiramos registrar o fato de uma mensagem ser
enviada. Poderamos adicionar caractersticas linguagem (mtodos envelope
wrappers) para fazermos isso, mas o registro raro o suficiente e o valor de lin-
guagens simples alto demais, de forma a preferirmos no fazer isso. Ou, suponha
que quisssemos invocar uma computao, mas mais tarde. Poderamos comear
o processo, imediatamente suspend-lo e recome-lo depois, mas, ento, teramos
todas as alegrias de concorrncia para lidar.
Invocaes complicadas de computaes requerem mecanismos caros. Mas,
na maioria do tempo, no precisamos toda a complexidade e preferimos no pagar
o custo. Quando precisamos que a invocao seja s um pouquinho mais concreta
e manipulvel que uma mensagem, objetos nos trazem a resposta. Faa um objeto
representar a invocao. Semeio-o com todos os parmetros que a computao ir
precisar. Quando estivermos preparados para invoc-lo, use um protocolo genri-
co como run().
A interface Runnable de Java um excelente exemplo disso:

Runnable
interface Runnable
public abstract void run();

Na implementao de run(), voc pode fazer o que quiser. Infelizmente, Java no


tem uma forma sintaticamente leve de criar e invocar Runnables, ento eles no so
usados tanto quanto o equivalente em outras linguagens blocos ou lambda em
Smalltalk/Ruby ou LISP.

Value object
Como voc projeta objetos que sero amplamente compartilhados, mas para os
quais a identidade no importante? Configure seu estado quando so criados e
nunca mude-o. Operaes no objeto sempre retornam um novo objeto.
188 Parte III Padres para Desenvolvimento Guiado por Testes

Objetos so maravilhosos. Posso dizer isso aqui, no posso? Objetos so uma


grande forma de organizar lgica para posterior entendimento e extenso. Con-
tudo, h um pequeno problema (certo, mais que um, mas esse aqui vai bastar por
agora).
Suponha que eu (um objeto) tenha um Retngulo. Eu computo alguns valores
baseado no Retngulo, como sua rea. Depois, algum educadamente pede o meu
Retngulo, e eu, no querendo parecer no cooperativo, dou a ele. Momentos de-
pois, vejam s, o Retngulo foi alterado pelas minhas costas. A rea que computei
antes est desatualizada e no h jeito de saber.
Esse um clssico problema de sinnimos. Se dois objetos dividem uma re-
ferncia com um terceiro, e se um objeto muda o objeto compartilhado, ento
melhor que os outros objetos no contem com o estado do objeto compartilhado.
H muitas maneiras de escapar do problema de sinnimos. Uma soluo
nunca fornecer os objetos dos quais voc depende, mas, em vez disso, sempre fazer
cpias. Isso pode parecer caro em tempo e espao e ignora aquelas vezes em que
voc quer dividir mudanas de um objeto compartilhado. Outra soluo Ob-
server, onde voc explicitamente registra objetos dos quais depende e espera ser
notificado quando eles mudarem. Observer pode fazer o controle de fluxo difcil
de seguir e a lgica de configurao e remoo de dependncias ficar feia.
Outra soluo tratar o objeto como menos que um objeto. Objetos tem
um estado que muda no decorrer do tempo. Podemos, se escolhermos, eliminar o
que muda no decorrer do tempo. Se eu tiver um objeto e souber que ele no vai
mudar, ento posso passar suas referncias por todo o lugar que quiser sabendo
que sinnimos no sero um problema. No podem haver mudanas escondidas
em um objeto compartilhado se no h mudanas.
Eu lembro da confuso sobre inteiros quando estava aprendendo Smalltalk
pela primeira vez. Se eu mudo o bit 2 para 1, por que todos os 2 no se tornam 6?

a := 2.
b := a.
a := a bitAt: 2 put: 1.
a => 6
b => 2

Inteiros so realmente valores mascarados como objetos. Em Smalltalk isso literal-


mente verdadeiro para inteiros pequenos, e simulado no caso de inteiros que no ca-
bem em uma nica word da mquina. Quando eu marco aquele bit, o que trazido
de volta um novo objeto com o bit marcado e no o antigo com o bit trocado.
Quando implementar um Value Object, cada operao tem que retornar um
objeto fresco, deixando o original inalterado. Usurios tm que estar cientes de
que esto usando um Value Object e tm que armazenar o resultado (como no
exemplo precedente com a, b). Todas essas alocaes de objetos podem criar pro-
blemas de desempenho, os quais devem ser tratados como todos os problemas
de desempenho, quando voc usa conjuntos de dados reais, padres de uso real,
dados de perfis e queixas sobre desempenho.
Captulo 30 Padres de Projeto 189

Eu tenho a tendncia a usar Value Object sempre que tenho uma situao
que parece como lgebra com interseco e unio de figuras geomtricas, valores
de unidades em que unidades so transportadas com um nmero, aritmtica sim-
blica. Sempre que Value Object faz o mnimo sentido, eu tento, pois isso torna a
leitura e a depurao muito mais fceis.
Todos os Value Objects tm de implementar igualdade (e em muitas lingua-
gens, por implicao, tm de implementar hashing). Se eu tenho esse contrato e
aquele contrato e eles no so o mesmo objeto, ento so diferentes, e no iguais.
Contudo, se tenho esses cinco francos e aqueles cinco francos, no importa se
so os mesmos cinco francos; cinco francos so cinco francos, e eles deveriam
ser iguais.

Null object
Como voc representa casos especiais usando objetos? Crie um objeto que repre-
senta o caso especial. D a ele o mesmo protocolo dos objetos regulares.

inspirado por java.io.File


public boolean setReadOnly() {
SecurityManager guard = System.getSecurityManager();
if (guard != null) {
guard.canWrite(path);
}
return fileSystem.setReadOnly(this);
}

H 18 verificaes para guard != null em java.io.File. Embora eu aprecie o


empenho deles em fazer arquivos seguros para o mundo, tambm fico um pouco
nervoso. Eles so cuidadosos a ponto de sempre verificarem um nulo como resul-
tado de getSecurityManager()?
A alternativa criar uma nova classe, LaxSecurity, que nunca lana excees:

LaxSecurity
public void canWrite(String path) {
}

Se algum pede um SecurityManager e no h nenhum disponvel, ento, em vez dis-


so, enviamos de volta um LaxSecurity:

SecurityManager
public static SecurityManager getSecurityManager() {
return security == null ? new LaxSecurity() : security;
}
190 Parte III Padres para Desenvolvimento Guiado por Testes

Agora no temos que nos preocupar se algum esquecer de verificar o nulo. O


cdigo original fica consideravelmente mais limpo:

Arquivo
public boolean setReadOnly() {
SecurityManager security = System.getSecurityManager();
security.canWrite(path);
return fileSystem.setReadOnly(this);
}

Erich Gamma e eu uma vez tivemos uma discusso em um tutorial OOPSLA


sobre se um Null Object era adequado em algum lugar do JHotDraw. Eu estava
frente por pontos quando Erich calculou o custo de introduzir o Null Object
como dez linhas de cdigo para as quais ns eliminaramos um condicional. Eu
odeio esses nocautes tcnicos no ltimo round. (Ns tambm tivemos indicaes
ruins da audincia por no sermos organizados. Aparentemente eles no estavam
cientes de que ter discusses produtivas de projeto uma difcil, porm frutfera,
habilidade.)

Template method
Como voc representa a sequncia invarivel de uma computao enquanto prepa-
ra refinamento futuro? Escreva um mtodo que implementado inteiramente em
termos de outros mtodos.
A programao est cheia de sequncias clssicas:

Entrada/processo/sada
Enviar mensagem/receber mensagem
Ler comando/retornar resultado

Gostaramos de ser capazes de comunicar claramente a universalidade dessas se-


quncias e, ao mesmo tempo, preparar variao na implementao dos passos.
Em herana, linguagens orientadas a objetos fornecem um simples, mesmo
que limitado, mecanismo para comunicar sequncias universais. Uma superclas-
se pode conter um mtodo escrito inteiramente em termos de outros mtodos, e
subclasses podem implementar esses mtodos de diferentes formas. Por exemplo,
JUnit implementa a sequncia bsica de executar um teste como:

TestCase
public void runBare() throws Throwable {
setUp();
try {
runTest();
Captulo 30 Padres de Projeto 191

}
finally {
tearDown();
}
}

As subclasses podem implementar setUp(), runTest(), e tearDown() de qualquer forma


que queiram.
Uma questo ao escrever um Template Method saber escrever uma imple-
mentao padro dos submtodos. Em TestCase.runBare(), todos os trs submto-
dos tm implementaes padro.

setUp() e tearDown() so mtodos nop*.


runTest() dinamicamente encontra e invoca um mtodo de teste baseado
no nome do caso de teste.

Se a computao no faz sentido sem um subpasso sendo preenchido, ento


anote isso da forma que sua linguagem de programao oferecer.

Em Java, declare o submtodo abstrato.


Em Smalltalk, implemente o mtodo lanando um erro
SubclassResponsibility.

Template method melhor encontrado por meio de experincia, em vez de


projetado de um jeito desde o comeo. Sempre que eu digo a mim mesmo, ah,
essa a sequncia e aqui esto os detalhes, me vejo otimizando os mtodos depois
e reextraindo as partes verdadeiramente variantes.
Quando voc encontra duas variantes de uma sequncia em duas subclasses,
precisa mov-las gradualmente para mais perto. Uma vez que tenha extrado as
partes diferentes de outros mtodos, o que nos resta o Template Method. Ento
voc pode mover o Template Method para a superclasse e eliminar a duplicao.

Pluggable object
Como voc expressa variao? O jeito mais simples com condicionais explcitos:

if (circle) then {
. . . cdigo para crculo. . .
} else {
. . . cdigo para o que no crculo
}

* N. de R. T.: Acrnimo de mtodos no-operation, isto , mtodos que no executam nada, no alte-
ram nada.
192 Parte III Padres para Desenvolvimento Guiado por Testes

Voc rapidamente vai achar que tal deciso explcita comea a se espalhar.
Se voc representa a distino entre crculos e no crculos como um condicional
explcito em um lugar, ento provvel que o condicional se espalhe.
Devido ao segundo imperativo de TDD ser a eliminao de duplicao, voc
deve conter a praga de condicionais explcitos no embrio. Na segunda vez que
ver um condicional, hora de sacar o mais bsico dos movimentos de projeto de
orientao a objetos, o Pluggable Object.
Os Pluggable Objects revelam-se por simplesmente eliminar a duplicao que
algumas vezes contra intuitiva. Erich Gamma e eu encontramos isso, um dos
meus exemplos favoritos de um Pluggable Object imprevisvel. Ao escrever um edi-
tor grfico, a seleo geralmente um pouco complicada. Se voc est sobre uma
figura quando pressiona o boto do mouse, movimentos subsequentes do mouse
movem aquela figura, e liberar o boto do mouse libera a figura selecionada. Se
voc no est sobre uma figura, ento est selecionando um grupo de figuras,
e movimentos subsequentes do mouse tipicamente redimensionam um retngulo
usado para selecionar vrias figuras. Liberar o boto do mouse seleciona as figuras
dentro do retngulo. O cdigo inicial parece algo como isso:

FerramentaDeSelecao
Figure selected;
public void mouseDown() {
selected= findFigure();
if (selected != null)
select(selected);
}
public void mouseMove() {
if (selected != null)
move(selected);
else
moveSelectionRectangle();
}
public void mouseUp() {
if (selected == null)
selectAll();
}

Existe aquele condicional feio duplicado (eu disse para voc que eles se espa-
lham como uma doena). A resposta nesses casos criar um Pluggable Object, um
SelectionMode, com duas implementaes: SingleSelection e MultipleSelection.

FerramentaDeSelecao
SelectionMode mode;
public void mouseDown() {
selected= findFigure();
if (selected != null)
mode= SingleSelection(selected);
else
mode= MultipleSelection();
Captulo 30 Padres de Projeto 193

}
public void mouseMove() {
mode.mouseMove();
}
public void mouseUp() {
mode.mouseUp();
}

Em linguagens com interfaces explcitas, voc ter que implementar uma interface
junto com os dois (ou mais) Pluggable Objects.

Pluggable selector3
Como voc invoca comportamentos diferentes de instncias diferentes? Armazene
o nome de um mtodo e invoque o mtodo dinamicamente.
O que voc faz quando tem dez subclasses de uma classe, cada uma imple-
mentando apenas um mtodo? Fazer subclasses um mecanismo pesado para cap-
turar to pouca quantidade de variao.

abstract class Report {


abstract void print();
}

class HTMLReport extends Report {


void print() { ...
}
}
class XMLReport extends Report {
void print() { ...
}
}

Uma alternativa ter uma s classe com um comando switch. Dependendo


do valor de um campo, voc invoca mtodos diferentes. Contudo, o nome do m-
todo aparece em trs lugares:

A criao da instncia
O estado do interruptor
O mtodo em si

3
Para mais detalhes, ver Beck, K. The Smalltalk Best Practices Patterns, pp. 70-73, Englewood-Cli-
ffs, NJ: Prentice-Hall. ISBN 013476904X. ruim referenciar seu prprio trabalho, mas, como disse
o filsofo Phyllis Diller uma vez: Claro que eu rio de minhas prprias piadas. Voc no pode confiar
em estranhos.
194 Parte III Padres para Desenvolvimento Guiado por Testes

abstract class Report {


String printMessage;

Report(String printMessage) {
this.printMessage= printMessage;
}

void print() {
switch (printMessage) {
case "printHTML" :
printHTML();
break;
case "printXML" :
printXML():
break;
}
};

void printHTML() {
}

void printXML() {
}
}

Cada vez que voc adiciona um novo tipo de impresso, tem que se certificar de
adicionar o mtodo de impresso e mudar o comando switch.
A soluo Pluggable Selector para invocar dinamicamente o mtodo usando
reflexo:

void print() {
Method runMethod= getClass().getMethod(printMessage, null);
runMethod.invoke(this, new Class[0]);
}

Agora ainda h uma dependncia feia entre criadores de relatrios e os nomes


dos mtodos de impresso, mas, ao menos, voc no tem a clusula case l
tambm.
O Pluggable Selector pode, definitivamente, ser usado demais. O maior pro-
blema com ele rastrear cdigo para ver se o mtodo invocado. Use Pluggable
Selector apenas quando est limpando uma situao razoavelmente direta na qual
cada um dos ramos de subclasses tem apenas um mtodo.

Factory method
Como voc cria um objeto quando quer flexibilidade na criao de novos objetos?
Crie um objeto em um mtodo em vez de usar um construtor.
Captulo 30 Padres de Projeto 195

Construtores so expressivos. Voc pode ver que definitivamente est criando


um objeto quando usa um. Contudo, construtores, particularmente em Java, tm
uma falta de expressividade e flexibilidade.
Um eixo de flexibilidade que queramos em nosso exemplo financeiro era
sermos capazes de retornar um objeto de uma classe diferente quando crissemos
um objeto. Temos testes como:

public void testMultiplication() {


Dollar five= new Dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}

Queremos introduzir a classe Money, mas no podemos enquanto estivermos


presos criando uma instncia de Dollar. Introduzindo um nvel de indireo atravs
de um mtodo, ganhamos a flexibilidade de retornar uma instncia de uma classe
diferente sem mudar o teste:

public void testMultiplication() {


Dollar five = Money.dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}

Money
static Dollar dollar(int amount) {
return new Dollar(amount);
}

Esse mtodo chamado um Factory Method, pois fabrica objetos.


A desvantagem do uso do Factory Method precisamente sua indireo.
Voc tem que lembrar que o mtodo est realmente criando um objeto mesmo que
no parea um construtor. Use o Factory Method apenas quando voc precisar
da flexibilidade que ele cria. Caso contrrio, construtores funcionam bem para a
criao de objetos.

Imposter
Como introduzir uma nova variao em uma computao? Introduza um novo
objeto com o mesmo protocolo de um objeto existente, mas com uma implemen-
tao diferente.
Introduzir variao em um programa procedural envolve adicionar lgica con-
dicional. Como vimos com Pluggable Object, tal lgica tende a se proliferar e uma
dose saudvel de mensagens polimrficas so necessrias para curar a duplicao.
Suponha que tenha uma estrutura j no lugar. J existe um objeto. Agora
voc precisa que o sistema faa algo diferente. Se h um lugar bvio para inserir
196 Parte III Padres para Desenvolvimento Guiado por Testes

uma clusula if e voc no est duplicando lgica de nenhum outro lugar, ento
v em frente.Frequentemente, entretanto, a variao obviamente vai precisar de
mudanas em muitos mtodos.
Esse momento de deciso surge de duas formas em TDD. s vezes, voc est
escrevendo um caso de teste e precisa representar um novo cenrio. Nenhum dos
objetos existentes expressa o que voc quer expressar. Suponha que estamos tes-
tando um editor grfico e que j tenhamos retngulos desenhados corretamente:

testRectangle() {
Drawing d= new Drawing();
d.addFigure(new RectangleFigure(0, 10, 50, 100));
RecordingMedium brush= new RecordingMedium();
d.display(brush);
assertEquals("rectangle 0 10 50 100\n", brush.log());
}

Agora queremos mostrar ovais. Nesse caso, o Imposter fcil de perceber subs-
titua um RectangleFigure por um OvalFigure.

testOval() {
Drawing d= new Drawing();
d.addFigure(new OvalFigure(0, 10, 50, 100));
RecordingMedium brush= new RecordingMedium();
d.display(brush);
assertEquals("oval 0 10 50 100\n", brush.log());
}

Geralmente, perceber a possibilidade de um Imposter pela primeira vez re-


quer um insight. A ideia de Ward Cunnungham de que um vetor de Moneys poderia
agir como um Money um momento nico. Voc pensava que eram diferentes, e
agora consegue v-los como a mesma coisa.
A seguir esto dois exemplos de Imposters que surgem durante a refato-
rao:

Null Object Voc pode tratar a ausncia de dados da mesma forma que
a presena de dados.
Composite Voc pode tratar a coleo de objetos da mesma forma que
um s objeto.

O encontro de Imposters durante a refatorao guiado pela eliminao de dupli-


cao, assim como toda refatorao guiada pela eliminao de duplicao.

Composite
Como voc implementa um objeto cujo comportamento a composio do com-
portamento de uma lista de outros objetos? Torne-o um Imposter para objetos
componentes.
Captulo 30 Padres de Projeto 197

Meu exemplo favorito tambm um exemplo da contradio de Composites:


Account e Transaction. Transactions armazenam um incremento de valor (elas so
realmente muito mais complexas e interessantes, mas, por enquanto...):

Transaction
Transaction(Money value) {
this.value= value;
}

Accounts calculam saldo somando os valores de suas Transactions:

Account
Transaction transactions[];
Money balance() {
Money sum= Money.zero();
for (int i= 0; i < transactions.length; i++)
sum= sum.plus(transactions[i].value);
return sum;
}

Parece bastante simples.

Transactions tm um valor.
Accounts tm um saldo.

Ento vem a parte interessante. Um cliente tem um monte de contas e gostaria


de ver um saldo geral. O jeito bvio de implementar isso com uma nova classe,
OverallAccount, a qual soma os saldos de uma lista de Accounts. Duplicao! Dupli-
cao!
E se Account e Balance implementarem a mesma interface? Vamos cham-la
Holding, porque no consigo pensar em nada melhor no momento.

Holding
interface Holding
Money balance();

Transactions podem implementar balance() retornando seu valor:

Transaction
Money balance() {
return value;
}

Agora, Accounts pode ser composta de Holdings, no de Transactions:

Account
Holding holdings[];
Money balance() {
198 Parte III Padres para Desenvolvimento Guiado por Testes

Money sum= Money.zero();


for (int i= 0; i < holdings.length; i++)
sum= sum.plus(holdings[i].balance());
return sum;
}

Agora, seu problema com OverallAccounts desaparece. Uma OverallAccount apenas


uma Account contendo Accounts.
O cheiro de Composite ilustrado acima. Transaes no tm saldos, no no
mundo real. Aplicar Composite um truque de programador geralmente no apre-
ciado pelo resto do mundo. Entretanto, os benefcios ao projeto do programa so
enormes, assim a desconexo contextual frequentemente vale a pena. Diretrios
contm Diretrios, SutesDeTestes contm SutesDeTestes, Desenhos contm Desenhos
nenhuma dessas traduz bem o mundo, mas todas elas fazem o cdigo muito mais
simples.
Eu tive que brincar com Composite por um longo tempo antes que encontrasse
onde us-lo e onde no us-lo. Como bvio a partir dessa discusso, ainda no sou
capaz de articular como adivinhar se uma coleo de objetos apenas uma coleo
de objetos ou se voc realmente tem um Composite. A boa notcia que, desde que
voc esteja ficando bom em refatorar, no momento em que a duplicao aparecer,
voc pode introduzir Composite e observar a complexidade do programa desapa-
recer.

Collecting parameter
Como voc coleta os resultados de uma operao que est espalhada em vrios
objetos? Adicione um parmetro operao em que os resultados sero cole-
tados.
Um exemplo simples a interface java.io.Externalizable . O mtodo
writeExternal escreve em um objeto e em todos os objetos que ele referencia. Como
todos os objetos tm que cooperar livremente para a escrita, ao mtodo passado
um parmetro, um ObjectOutput, como parmetro coletor (Collecting Parameter):

java.io.Externalizable
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
}

Adicionar um Collecting Parameter uma consequncia comum de Compo-


site. No desenvolvimento de JUnit, no precisvamos que o TestResult colocasse os
resultados de vrios testes at que tivssemos escrito vrios testes.
Captulo 30 Padres de Projeto 199

medida que a sofisticao dos resultados esperados cresce, voc pode en-
contrar a necessidade de introduzir um Collecting Parameter. Por exemplo, supo-
nha que estamos mostrando Expressions.
Se tudo que quisermos uma string plana, ento concatenao suficiente:

testSumPrinting() {
Sum sum= new Sum(Money.dollar(5), Money.franc(7));
assertEquals("5 USD + 7 CHF", sum.toString());
}

String toString() {
return augend + " + " + addend;
}

Contudo, se quisermos a forma de rvore indentada na expresso, o cdigo


se pareceria com isso:

testSumPrinting() {
Sum sum= new Sum(Money.dollar(5), Money.franc(7));
assertEquals("+\n\t5 USD\n\t7 CHF", sum.toString());
}

Teremos que introduzir um Collecting Parameter; algo como isso:

String toString() {
IndentingStream writer= new IndentingStream();
toString(writer);
return writer.contents();
}

void toString(IndentingWriter writer) {


writer.println("+");
writer.indent();
augend.toString(writer);
writer.println();
addend.toString(writer);
writer.exdent();
}

Singleton
Como voc fornece variveis globais em linguagens sem variveis globais? No
faa isso. Seus programas agradecero por aproveitar o tempo para pensar sobre
o projeto em vez disso.
Captulo 31
Refatorao

Esses padres descrevem como mudar o projeto do sistema, mesmo radicamente,


em passos pequenos.
1
Em TDD, usamos refatorao de uma forma interessante. Geralmente, uma
refatorao no pode mudar a semntica do programa sob qualquer circunstncia.
Em TDD, as circunstncias com as quais nos importamos so os testes que j pas-
saram. Ento, por exemplo, podemos mudar constantes por variveis em TDD e,
com conscincia, chamar essa operao de refatorao, pois no muda o conjunto
de testes bem-sucedidos. A nica circunstncia sob a qual a semntica preservada
pode, realmente, ser aquele caso de teste em particular. Qualquer outro caso de
teste que estivesse funcionando falharia. Contudo, no temos esses testes ainda,
ento no precisamos nos preocupar com eles.
Essa equivalncia observacional pe uma obrigao sobre voc para que
tenha testes suficientes tal que at onde voc sabe e ao menos quando tiver aca-
bado uma refatorao com respeito aos testes o mesmo que uma refatorao
com respeito a todos os testes possveis. No desculpa para dizer: Eu sei que h
um problema, mas todos os testes passaram, ento eu verifiquei o cdigo. Escreva
mais testes.

Reconciliar diferenas (Reconcile Differences)


Como voc unifica dois trechos similares de cdigo? Gradualmente aproxime-os.
Unifique-os apenas quando eles forem absolutamente idnticos.
Refatorao pode ser uma experincia enervante. As mais fceis so bvias.
Se eu extraio um mtodo e fao isso de forma mecanicamente correta, h pouqus-
1
Fowler, Martin. 1999. Refactoring: Improving the Design of Existing Code. Reading, MA: Addi-
son-Wesley. ISBN 0201485672.
202 Parte III Padres para Desenvolvimento Guiado por Testes

sima chance de mudar o comportamento do sistema. Mas, algumas refatoraes


o impulsionam a examinar os fluxos de controle e os valores de dados cuidadosa-
mente. Uma longa cadeia de raciocnio conduz voc a acreditar que aquela mudan-
a que est prestes a fazer no mudar qualquer resposta. Essas so as refatoraes
que aumentam sua calvcie.
Tal refatorao com esse salto de f exatamente o que estamos tentando
evitar com nossa estratgia de passos pequenos e feedback concreto. Embora voc
nem sempre possa evitar refatoraes saltadas, pode reduzir sua incidncia.
Essa refatorao ocorre em todos os nveis de escala.

Duas estruturas de lao so similares. Fazendo-as idnticas, voc pode


mescl-las.
Dois desvios de um condicional so similares. Fazendo-os idnticos, voc
pode eliminar o condicional.
Dois mtodos so similares. Fazendo-os idnticos, voc pode eliminar um.
Duas classes so similares. Fazendo-as idnticas, voc pode eliminar
uma.

s vezes, voc precisa abordar a reconciliao de diferenas de forma inverti-


da isto , pensar em como o ltimo passo da mudana poderia ser trivial, ento
trabalhar no sentido contrrio. Por exemplo, se voc quer remover muitas subclas-
ses, o ltimo passo trivial se uma subclasse no contm nada. Ento a superclasse
pode substituir a subclasse sem mudar o comportamento do sistema. Para esvaziar
essa subclasse, esse mtodo precisa ser idntico quele na superclasse. Uma a uma,
esvazie as subclasses e, quando estiverem vazias, substitua referncias a elas por
referncias superclasse.

Isolar mudana (Isolate Change)


Como voc muda uma parte de um mtodo ou objeto multi-parte? Primeiro, isole
a parte que tem que mudar.
O cenrio que vem minha cabea cirurgia: O paciente inteiro, exceto
a parte a ser operada, est coberto. A cortina deixa o cirurgio com apenas um
conjunto fixo de variveis. Agora, poderamos ter longas discusses sobre se essa
abstrao de uma pessoa a um quadrante inferior esquerdo do abdmen leva a um
bom cuidado de sade, mas, no momento da cirurgia, eu fico feliz que o cirurgio
possa se concentrar.
Voc pode achar que uma vez isolada a mudana e ento feita a mudana, o
resultado to trivial que pode desfazer o isolamento. Se ns acharmos que tudo o
que realmente precisvamos era retornar a varivel de instncia em findRate(), ento
devemos considerar otimizar findRate() em todos os lugares que usado e delet-lo.
Entretanto, no faa essas mudanas automaticamente. Pese o custo de um mtodo
adicional em relao ao valor de ter um conceito adicional explcito no cdigo.
Captulo 31 Refatorao 203

Algumas formas possveis de isolar mudana so Extrair Mtodo (Extract


Method), o mais comum, Extrair Objeto (Extract Object) e Objeto Mtodo (Me-
thod Object).

Migrar dados (Migrate Data)


Como voc troca uma representao? Duplique os dados temporariamente.

Como
Aqui est a verso interna-para-externa na qual voc muda a representao inter-
namente e ento muda a interface visvel externamente.

Adicione uma varivel de instncia no novo formato.


Configure a varivel no novo formato em todo lugar que configurou o
antigo formato.
Use a varivel no novo formato em todo o lugar que usou o antigo for-
mato.
Apague o antigo formato.
Mude a interface externa para refletir o novo formato.

Contudo, s vezes voc vai querer mudar a API primeiro. Ento deve:

Adicionar um parmetro no novo formato.


Traduzir o parmetro de novo formato para a representao interna do
formato antigo.
Apagar o parmetro no formato antigo.
Substituir usos do formato antigo pelo formato novo.
Apagar o formato antigo.

Por qu?
Um para Muitos sempre cria um problema de migrao de dados. Suponha que
queiramos implementar TestSuite usando Um para Muitos. Comearamos com:

def testSuite(self):
suite= TestSuite()
suite.add(WasRun("testMethod"))
suite.run(self.result)
assert("1 run, 0 failed" == self.result.summary())
204 Parte III Padres para Desenvolvimento Guiado por Testes

que implementado (na parte Um do Um para Muitos) por:

class TestSuite:
def add(self, test):
self.test= test
def run(self, result):
self.test.run(result)

Agora comeamos a duplicar dados. Primeiro inicializamos a coleo de testes:

TestSuite
def __init__(self):
self.tests= []

Em todo o lugar em que test alterado (set), adicionamos coleo tambm:

TestSuite
def add(self, test):
self.test= test
self.tests.append(test)

Agora usamos a lista de testes em vez de um nico teste. Para o propsito dos
atuais casos de teste, essa uma refatorao (ela preserva a semntica), porque h
apenas um elemento na coleo.

TestSuite
def run(self, result):
for test in self.tests:
test.run(result)

Apagamos a varivel de instncia test no mais usada:

TestSuite
def add(self, test):
self.tests.append(test)

Voc pode tambm usar migrao de dados passo a passo quando trocar entre
formatos equivalentes com protocolos diferentes, como na troca de Vetor/Enume-
rador (Vector/Enumerator) de Java para Coleo/Iterador (Collection/Iterator).

Extrair mtodo (Extract Method)


Como voc faz um mtodo longo e complicado ser mais fcil de ler? Transforme
uma parte pequena dele em um mtodo separado e chame o novo mtodo.
Captulo 31 Refatorao 205

Como
Extrair Mtodo realmente uma das mais complicadas refatoraes atmicas. Eu
descreverei o caso tpico aqui. Felizmente, tambm a mais comumente implemen-
tada refatorao automtica, dessa forma improvvel que tenha que fazer isso
na mo.

1. Encontre uma regio do mtodo que faz sentido ter o prprio mtodo.
Corpos de laos, laos inteiros e ramos de condicionais so candidatos
comuns para extrao.
2. Assegure-se de que no h atribuies para variveis temporrias declara-
das fora do escopo da regio a ser extrada.
3. Copie o cdigo do mtodo antigo para o mtodo novo. Compile-o.
4. Para cada varivel ou parmetro do mtodo original usado no novo mto-
do, adicione um parmetro ao novo mtodo.
5. Chame o novo mtodo a partir do mtodo original.

Por qu?
Eu uso Extrair Mtodo quando estou tentando entender cdigo complicado.
Aqui, esse pedao aqui est fazendo alguma coisa. O que deveremos chamar
aqui? Depois de meia hora, o cdigo parece melhor, seu parceiro percebe que
realmente voc est ali para ajudar, e voc entende muito melhor o que est acon-
tecendo.
Eu uso Extrair Mtodo para eliminar duplicao quando vejo que dois
mtodos tem algumas partes iguais e outras partes diferentes. Extraio os peda-
os similares como mtodos. (A ferramenta Smalltalk Refactoring Browser at
avana e verifica para ver se voc est extraindo um mtodo que equivalente a
um que j tem e oferece o uso do mtodo existente em vez de criar um novo.)
Quebrar mtodos em partes pequenas pode, s vezes, ir longe demais.
Quando eu no posso mais ver um jeito de avanar, frequentemente uso Mto-
do em uma Linha Inline Method (convenientemente, a prxima refatorao)
para ter todo o cdigo em um lugar para que eu possa ver o que deve ser ex-
trado de novo.

Mtodo em uma linha (Inline Method)


Como voc simplifica fluxos e dados que se tornaram muito retorcidos ou espalha-
dos? Substitua uma invocao de mtodo pelo prprio mtodo.
206 Parte III Padres para Desenvolvimento Guiado por Testes

Como
1. Copie o mtodo.
2. Cole o mtodo sobre a invocao do mtodo.
3. Substitua todos os parmetros formais pelos parmetros reais. Se, por
exemplo, voc passa reader.getNext() (uma expresso que causa efeitos
colaterais), ento seja cuidadoso ao atribu-la a uma varivel local.

Por qu?
Um dos revisores deste livro reclamou sobre a sequncia na Parte I onde pedido
a um Bank para reduzir uma Expression para um Money simples.

public void testSimpleAddition() {


Money five= Money.dollar(5);
Expression sum= five.plus(five);
Bank bank= new Bank();
Money reduced= bank.reduce(sum, "USD");
assertEquals(Money.dollar(10), reduced);
}

Isso muito complicado. Por que apenas no pede para Money se reduzir? Como
ns experimentamos? Coloque em uma linha a implementao de Bank.reduce() e
veja o que ela parece:

public void testSimpleAddition() {


Money five= Money.dollar(5);
Expression sum= five.plus(five);
Bank bank= new Bank();
Money reduced= sum.reduce(bank, "USD");
assertEquals(Money.dollar(10), reduced);
}

Voc poderia gostar mais da segunda verso, ou no. O ponto a observar-


mos aqui que voc pode usar Mtodo em uma Linha para jogar com o fluxo
de controle. Quando estou refatorando, tenho o cenrio mental do sistema com
pedaos de lgica e fluxos de controle circulando entre os objetos. Quando penso
que vejo algo promissor, uso refatoraes para test-lo e ver o resultado.
No calor da batalha, eu ocasionalmente fui apanhado pela minha prpria
inteligncia (Eu no vou dizer quantas vezes isso aconteceu.) Quando isso acon-
tece, Mtodo em uma Linha uma forma de me rebobinar para: Eu tenho esse
enviando para aquele, enviando para aquele... Opa, Nelly. O que est acontecendo
aqui? Eu coloco em uma linha algumas camadas de abstrao, vejo o que est
realmente se passando e, ento, posso reabstrair o cdigo de acordo com suas reais
necessidades, no com meus preconceitos.
Captulo 31 Refatorao 207

Extrair interface (Extract Interface)


Como voc introduz uma segunda implementao de operaes em Java? Crie
uma interface contendo as operaes compartilhadas.

Como
1. Declare uma interface. s vezes, o nome da classe existente deve ser o
nome da interface, e, nesse caso, voc deve, primeiro, renomear a classe.
2. Faa a classe existente implementar a interface.
3. Adicione os mtodos necessrios interface, expandindo a visibilidade
dos mtodos na classe se necessrio.
4. Mude declaraes de tipo da classe para a interface onde for possvel.

Por qu?
s vezes, quando voc precisa extrair uma interface, est genuinamente trocando
da primeira implementao para a segunda. Voc tem um Rectangle e quer adicio-
nar Oval, ento voc cria uma interface Shape. Encontrar nomes para as interfaces,
nesse caso, geralmente fcil, embora, s vezes, tenha que se esforar para encon-
trar a metfora certa.
s vezes, voc est introduzindo um Modelo de Teste de Acidentes (Crash
Test Dummy) ou outro Objeto Simulado (Mock Object) quando precisa extrair
uma interface. Nomear geralmente mais difcil nesse caso, pois voc ainda s tem
um exemplo real. Esses so os momentos em que eu fico mais tentado a dar um jei-
tinho e nomear a interface como IFile e deixar a classe como File. Eu me eduquei
a parar um momento e ver se no estou entendendo alguma coisa mais profunda
sobre o que est acontecendo. Talvez a interface devesse ser chamada File e a clas-
se DiskFile, pois a classe assume que os bits esto em um disco.

Mover mtodo (Move Method)


Como voc move um mtodo para o lugar ao qual ele pertence? Adicione-o clas-
se qual ele pertence, ento invoque-o.

Como
1. Copie o mtodo.
2. Cole o mtodo, adequadamente nomeado, na classe alvo. Compile-o.
208 Parte III Padres para Desenvolvimento Guiado por Testes

3. Se o objeto original referenciado no mtodo, ento adicione um par-


metro para passar o objeto original. Se variveis do objeto original so
referenciadas, ento passe-as como parmetros. Se variveis do original
so alteradas (set), ento voc deveria desistir.
4. Substitua o corpo do mtodo original por uma invocao do novo m-
todo.

Como
Essa uma das minhas consultorias favoritas em refatorao, pois to bom
descobrir preconceitos no comprovados. Calcular reas a responsabilidade de
Shape:

Shape
...
int width= bounds.right() - bounds.left();
int height= bounds.bottom() - bounds.top();
int area= width * height;
...

A qualquer momento em que eu veja mais que uma mensagem ser enviada a outro
objeto em um mtodo, fico desconfiado. Nesse caso, vejo que bounds (um Rectangle)
est enviando quatro mensagens. Hora de mover essa parte do mtodo:

Rectangle
public int area() {
int width= this.right() - this.left();
int height= this.bottom() - this.top();
return width * height;
}

Shape
...
int area= bounds.area();
...

As trs grandes propriedades de Mover Mtodo so as seguintes.

fcil ver a necessidade dele sem conhecer profundamente o significado


do cdigo. Voc v duas ou mais mensagens para um objeto diferente e a
vai voc.
Captulo 31 Refatorao 209

As mecnicas so rpidas e seguras.


Os resultados so frequentemente iluminados. Mas Rectangles no fazem
qualquer clculo... , eu entendo. Isso melhor.

s vezes, voc vai querer mover apenas parte de um mtodo. Voc pode pri-
meiro extrair um mtodo, mover o mtodo inteiro, ento colocar em uma linha o
mtodo (agora cabendo em uma linha) na classe original. Ou voc pode descobrir
os mecanismos para faz-lo de uma vez.

Objeto mtodo (Method Object)


Como voc representa um mtodo complicado que requer muitos parmetros e
variveis locais? Faa um objeto a partir do mtodo.

Como?
Crie um objeto com os mesmos parmetros do mtodo.
Faa das variveis locais as variveis da instncia do objeto tambm.
Crie um mtodo chamado run() cujo corpo o mesmo do mtodo origi-
nal.
No mtodo original, crie um novo objeto e invoque run().

Por qu?
Objeto Mtodo til na preparao para adicionar um tipo de lgica completa-
mente nova no sistema. Por exemplo, voc poderia ter muitos mtodos envolvidos
em calcular o fluxo de caixa do componente fluxos de caixa. Quando quiser come-
ar a calcular o valor atual do fluxo de caixa lquido, voc pode, primeiro, criar o
Objeto Mtodo fora do primeiro estilo de computao. Ento, voc pode escrever
o novo estilo de computao com seus prprios testes em menor escala. Ento ligar
(plugar) o novo estilo ser um nico passo.
Objeto Mtodo tambm bom para simplificar cdigo que no se submete a
Extrair Mtodo. s vezes, voc encontrar um bloco de cdigo que tem um monte
de variveis temporrias e parmetros, e, toda vez que tenta extrair um pedao
dele, tem que carregar cinco ou seis temporrias e parmetros. O mtodo extrado
210 Parte III Padres para Desenvolvimento Guiado por Testes

resultante no parece melhor que o cdigo original, porque a assinatura do mto-


do muito longa. Criar um Objeto Mtodo d a voc um novo namespace no qual
voc pode extrair mtodos sem ter que passar nada.

Adicionar parmetro (Add Parameter)


Como voc adiciona um parmetro a um mtodo?

Como
1. Se o mtodo est em uma interface, adicione o parmetro interface
primeiro.
2. Adicione o parmetro.
3. Use os erros do compilador para ver qual cdigo chamado voc precisa
mudar.

Por qu?
Adicionar um parmetro , frequentemente, um passo de extenso. Voc teve o
primeiro caso de teste executando sem precisar do parmetro, mas, nessa nova
circunstncia, voc tem que levar em considerao mais informao, de forma a
computar corretamente.
Adicionar um parmetro pode ser tambm parte da migrao de uma repre-
sentao de dados para outra. Primeiro, voc adiciona o parmetro, ento elimina
todos os usos do parmetro antigo, e ento remove o parmetro antigo.

Parmetro de mtodo para parmetro de construtor


(Method Parameter to Constructor Parameter)
Como voc troca um parmetro de um mtodo ou de mtodos para o construtor?

Como
1. Adicione um parmetro ao construtor.
2. Adicione uma varivel de instncia com o mesmo nome do parmetro.
3. Atribua (set) a varivel no construtor.
4. Uma a uma, converta as referncias de parameter para this.parameter.
Captulo 31 Refatorao 211

5. Quando no existirem mais referncias ao parmetro, elimine o parme-


tro do mtodo e de todos aqueles que o chamam.
6. Remova os agora suprfluos this. das referncias.
7. Renomeie a varivel corretamente.

Por qu?
Se voc passa o mesmo parmetro a muitos mtodos diferentes no mesmo objeto,
ento pode simplificar a API passando o parmetro uma vez (eliminando duplica-
o). Voc pode executar essa refatorao ao reverso se achar que uma varivel de
instncia usada em apenas um mtodo.
Captulo 32
Dominando TDD

Eu espero levantar aqui questes para voc ponderar sobre como integrar TDD em
sua prpria prtica. Algumas das questes so pequenas, e algumas so grandes.
s vezes, as respostas esto aqui, ou, ao menos, sugeridas, e, s vezes, as questes
so deixadas para voc explorar.

Quo grandes deveriam ser seus passos?


H realmente duas questes espreitando aqui:

Quanto terreno cada teste deveria cobrir?


Por quantos estgios intermedirios voc deveria passar enquanto refa-
tora?

Voc poderia escrever os testes de forma que cada um deles incentive a adio
de uma s linha de lgica e um punhado de refatoraes. Voc poderia escrever
os testes de forma que cada um deles incentive a adio de centenas de linhas de
lgica e horas de refatorao. Qual voc deve escolher?
Parte da resposta que voc deveria ser capaz de fazer qualquer uma. A ten-
dncia de Desenvolvedores Guiados por Testes ao longo do tempo, porm, clara
passos menores. Contudo, pessoas esto experimentando dirigir desenvolvimen-
to por testes em nvel de aplicao sozinhos ou em conjunto com os testes em nvel
de programador que viemos escrevendo.
A princpio, quando voc refatora, deveria estar preparado para dar muitos
passinhos pequenos. Refatorao manual est propensa a erros, e, quanto mais
erros voc cometer e descobrir apenas depois, menos provvel a refatorao.
Uma vez que tenha feito uma refatorao 20 vezes mo em passinhos pequenos,
experimente deixar de fora alguns dos passos.
214 Parte III Padres para Desenvolvimento Guiado por Testes

Refatorao automtica acelera a refatorao enormemente. O que teria exi-


gido de voc 20 passos manuais agora se torna um nico item do menu. Uma or-
dem de magnitude mudada na quantidade geralmente constitui uma mudana na
qualidade, e isso verdadeiro para refatorao automtica. Quando voc sabe que
tem o suporte de uma ferramenta excelente, voc se torna muito mais agressivo em
suas refatoraes, tentando muito mais experimentos para ver como o cdigo quer
ser estruturado.
O Refactoring Browser for Smalltalk ainda, enquanto escrevo isso, a me-
lhor ferramenta de refatorao disponvel. Suporte refatorao em Java est
aparecendo em muitas IDEs de Java, e o suporte refatorao certamente vai se
espalhar rapidamente a outras linguagens e ambientes.

O que voc no tem que testar?


A resposta simples, fornecida por Phlip, : Escreva testes at que o medo se trans-
forme em tdio. Contudo, esse um lao de feedback e requer que voc encontre
a resposta sozinho. Como voc veio a esse livro para ter respostas, no questes
(nesse caso voc j est lendo a seo errada, mas chega de trechos literrios autor-
referenciais), tente essa lista. Voc deveria testar:

Condicionais
Laos
Operaes
Polimorfismo

Mas, apenas aqueles que voc escreve. A menos que tenha razo para desconfiar,
no teste cdigo de outros. s vezes, a especificao exata de cdigo externo re-
quer que voc escreva mais sua prpria lgica. Veja acima se voc tem que testar
isso. s vezes, apenas para ser mais cuidadoso, documentarei a presena de, hum,
comportamento incomum em cdigo externo com um teste que falhar se o erro
nunca for corrigido, digo, se o comportamento nunca for refinado.

Como voc sabe se tem bons testes?


Os testes so um canrio em uma mina de carvo revelando por sua aflio a
presena de vapores malignos no projeto. Aqui esto alguns atributos de teste que
sugerem um projeto com problemas.

Longo cdigo de configurao Se voc tem que gastar uma centena de


linhas criando objetos para uma assero simples, ento algo est errado.
Seus objetos so muito grandes e precisam ser divididos.
Duplicao de configurao Se voc no puder encontrar facilmente
um lugar comum para cdigo de configurao comum, ento h muitos
objetos fortemente entrelaados.
Captulo 32 Dominando TDD 215

Testes de longa execuo Testes de TDD que rodam por um longo tem-
po no sero rodados frequentemente, e frequentemente no foram exe-
cutados por algum tempo, e, provavelmente, no funcionam. Pior que
isso, eles sugerem que testar os trechos e partes da aplicao difcil. A
dificuldade de testar trechos e partes um problema de projeto e precisa
2
ser tratado com projeto. (O equivalente a 9,8m/s * a sute de teste de
dez minutos. Sutes que levam mais que dez minutos inevitavelmente so
aparadas, ou a aplicao ajustada, de forma que a sute leve dez minutos
de novo.)
Testes frgeis Testes que quebram inesperadamente sugerem que uma
parte da aplicao est surpreendentemente afetando outra parte. Voc
precisa projetar at que o efeito seja eliminado, ou quebrando a conexo
ou juntando as duas partes.

Como TDD leva a frameworks?


Paradoxo: no considerando o futuro do seu cdigo, voc torna seu cdigo muito
mais provvel de ser adaptvel no futuro.
Eu aprendi exatamente o oposto dos livros: Codifique para hoje, projete
para amanh. TDD parece estar com esse conselho na cabea: Codifique para
amanh, projete para hoje. Aqui est o que acontece na prtica.

A primeira feature entra. Ela implementada simples e diretamente, logo,


est pronta rapidamente e com alguns defeitos.
A segunda feature, uma variao da primeira, entra. A duplicao entre
as duas features posta em um lugar, ao passo que as diferenas tendem
a entrar em lugares diferentes (mtodos diferentes ou mesmo classes dife-
rentes).
A terceira feature, uma variao da segunda, entra. A lgica comum
plausvel de ser reutilizada, talvez com alguns ajustes. A lgica nica ten-
de a ter um habitat bvio em um mtodo diferente ou em uma classe
diferente.

O Princpio Aberto/Fechado (objetos deveriam ser abertos para uso e fecha-


dos para outras modificaes) gradualmente satisfeito e, precisamente, para esses
tipos de variao que ocorrem na prtica. Desenvolvimento dirigido por testes dei-
xa voc com frameworks que so bons em expressar exatamente o tipo de variao
que ocorre, mesmo que os frameworks possam no ser bons em expressar o tipo
de variao que no ocorre (ou no ocorreu ainda).
Ento, o que acontece quando uma variao incomum surge trs anos de-
pois? O projeto passa por uma evoluo rpida exatamente nos pontos necessrios

* N. de R. T.: Este o valor constante aproximado da acelerao da gravidade na Terra. O autor


provavelmente quer relacionar esse valor constante sua sugesto de tempo (constante) mximo de
testes 10 minutos.
216 Parte III Padres para Desenvolvimento Guiado por Testes

para acomodar a variao. O Princpio Aberto/Fechado violado, apenas por um


momento, mas a violao no to custosa, pois voc tem todos aqueles testes
para dar a confiana de que no est estragando nada.
No limite, onde voc introduz as variaes muito rpido, TDD indistin-
guvel de projetar para frente. Eu ampliei um framework de notificao certa vez
no decorrer de umas poucas horas, e observadores estavam absolutamente certos
de que era um truque. Devo ter comeado com o framework resultante na ca-
bea. No, desculpe. Apenas tive desenvolvimento dirigido por teste o suficiente
para que pudesse recuperar a maioria dos meu erros mais rpido que voc possa
reconhec-los. Eu mesmo os fiz.

Quanto feedback voc precisa?


Quantos testes voc deve escrever? Aqui h um problema simples: dados trs intei-
ros representando o comprimento dos lados de um tringulo, retorne:

1 se o tringulo equiltero
2 se o tringulo issceles
3 se o tringulo escaleno

e lance uma exceo se o tringulo no bem formado.


V em frente e tente resolver o problema (minha soluo em Smalltalk est
listada no final dessa questo).
Eu escrevi seis testes (um tipo de Qual a Msica?: Eu posso codificar
aquele problema em quatro testes. Codifique aquele problema.). Bob Binder,
1
no seu abrangente livro Testing Object-Oriented Systems , escreveu 65 para o mes-
mo problema. Voc ter que decidir, da experincia e da reflexo, quantos testes
quer escrever.
Eu penso em Tempo Mdio Entre Falhas (TMEF) quando penso em quantos
testes escrever. Por exemplo, inteiros em Smalltalk agem como inteiros, no como
um contador de 32 bits, ento no faz sentidos testar MAXINT. Bem, h um tamanho
mximo para um inteiro, mas tem a ver com a quantidade de memria que voc
tem. Preciso escrever um teste que encha a memria com inteiros extremamente
grandes? Como isso afetar o TMEF do meu programa? Se nunca vou chegar to
perto desse tamanho de tringulo, meu programa no mensuravelmente mais
robusto com esse teste que sem esse teste.
Se um teste tem sentido em ser escrito depende de quo cuidadosamente voc
mede o TMEF. Se voc est tentando mudar um TMEF de 10 anos para um TMEF
de 100 anos em seu marca-passo, ento testes para condies extremamente im-
provveis e combinaes de condies fazem sentido, a menos que possa demons-
trar de outro jeito que as condies no surgem.

1
Binder, Bob. 1999. Testing Object-Oriented Systems: Models, Patterns, and Tools. Reading, MA:
Addison-Wesley. ISBN 0201809389. Esta a referncia abrangente sobre teste.
Captulo 32 Dominando TDD 217

A viso de teste de TDD pragmtica. Em TDD, os testes so um meio para


atingir um fim o ser cdigo final no qual temos grande confiana. Se nosso co-
nhecimento da implementao nos d confiana mesmo sem um teste, ento no
escreveremos aquele teste. Teste caixa preta, em que deliberadamente escolhemos
ignorar a implementao, tem algumas vantagens. Ao ignorar o cdigo, ele de-
monstra um sistema de valor diferente os testes so valiosos sozinhos. uma ati-
tude apropriada para ser tomada em algumas circunstncias, mas que diferente
de TDD.

TriangleTest
testEquilateral
self assert: (self evaluate: 2 side: 2 side: 2) = 1

testIsosceles
self assert: (self evaluate: 1 side: 2 side: 2) = 2

testScalene
self assert: (self evaluate: 2 side: 3 side: 4) = 3

testIrrational
[self evaluate: 1 side: 2 side: 3]
on: Exception
do: [:ex | ^self].
self fail

testNegative
[self evaluate: -1 side: 2 side: 2]
on: Exception
do: [:ex | ^self].
self fail

testStrings
[self evaluate: a side: b side: c]
on: Exception
do: [:ex | ^self].
self fail

evaluate: aNumber1 side: aNumber2 side: aNumber3


| sides |
sides := SortedCollection
with: aNumber1
with: aNumber2
with: aNumber3.
sides first <= 0 ifTrue: [self fail].
(sides at: 1) + (sides at: 2) <= (sides at: 3) ifTrue: [self fail].
^sides asSet size
218 Parte III Padres para Desenvolvimento Guiado por Testes

Quando voc deve apagar testes?


Quanto mais testes, melhor, mas se dois testes so redundantes um com respeito ao
outro, deveria manter ambos? Isso depende de dois critrios.

O primeiro critrio para seus testes confiana. Nunca apague um teste se


isso reduz sua confiana no comportamento do sistema.
O segundo critrio comunicao. Se voc tem dois testes que exercitam
o mesmo caminho de cdigo, mas falam de cenrios diferentes para um
leitor, deixe-os em paz.

Dito isso, se voc tem dois testes que so redundantes com respeito a confiana e
comunicao, apague o menos til dos dois.

Como a linguagem e o ambiente de programao


influenciam TDD?
Tente TDD em Smalltalk com o Refactoring Browser. Tente-o em C++ com vi.
Como sua experincia difere?
Em linguagens e ambientes de programao em que os ciclos de TDD (teste/
compile/rode/refatore) so mais difceis de fazer, voc provavelmente ser tentado
a dar passos maiores:

Cubra mais terreno com cada teste.


Refatore com menos passos intermedirios.

Isso faz voc ir mais rpido ou devagar?


Em linguagens e ambientes de programao onde os ciclos de TDD so abun-
dantes, voc provavelmente ser instigado a tentar muito mais experimentos. Isso
ajuda voc a ir mais rpido ou a alcanar solues melhores, ou voc estaria me-
lhor livre da institucionalizao de algum tipo de tempo para pura reflexo (revi-
ses ou uso de literate programming*)?

Voc pode guiar-se por testes em sistemas enormes?


TDD escalvel para sistemas extremamente grandes? Que novos testes voc teria
que escrever? Que novos tipos de refatorao voc precisaria?
O maior sistema totalmente dirigido por testes em que estive envolvido
no LifeWare (www.lifeware.ch). Depois de 4 anos e 40 pessoas/ano, o sistema
continha aproximadamente 250.000 linhas de cdigo funcional e 250.000 linhas
de cdigo de teste em Smalltalk. Existem 4.000 testes executando em cerca de 20

* N. de R. T.: Literate programming um enfoque de programao introduzido por Donald Knuth e


tem sido tipicamente usado para ensino de programao. O uso de literate programming leva a progra-
mas escritos como em um texto em linguagem natural, explicando a lgica de resoluo de um proble-
ma, mas que contm tambm macros e cdigo fonte tradicional, que pode ser compilado e executado.
Captulo 32 Dominando TDD 219

minutos. A sute completa rodada muitas vezes por dia. A quantidade de funcio-
nalidade no sistema parece no ter influncia sobre a efetividade de TDD. Elimi-
nando duplicao, voc tende a criar mais objetos menores, e esses objetos podem
ser testados em isolamento independente do tamanho da aplicao.

Voc pode guiar o desenvolvimento com testes em nvel de aplicao?


O problema com desenvolvimento dirigido por testes em pequena escala (eu os
chamo testes de unidade, mas eles no condizem muito bem com a definio
aceita de testes de unidade) que voc corre o risco de implementar o que acha que
os usurios querem, mas ter isso pode ser o caso de no ser absolutamente o que
eles queriam. E se escrevssemos os testes no nvel da aplicao? Ento os usurios
(com ajuda) poderiam escrever os prprios testes para exatamente o que querem
que o sistema faa em seguida.
H um problema tcnico fixturing. Como voc pode escrever e rodar um
teste para uma feature que ainda no existe? Sempre parece haver alguma sada
para esse problema, tipicamente introduzindo um interpretador que, educadamen-
te, sinaliza um erro quando ele se depara com um teste que ainda no sabia como
interpretar.
H tambm um problema social com desenvolvimento dirigido por testes
de aplicao (Application Test-Driven Development ATDD). Escrever testes
uma nova responsabilidade para usurios (aqui eu realmente quero dizer um time
que inclui usurios), e essa responsabilidade vem em um novo lugar no ciclo de
desenvolvimento nominalmente, antes da implementao comear. Organizaes
resistem a esse tipo de mudana de responsabilidade. Ela vai exigir um esforo
concentrado (que o esforo de muitas pessoas no time trabalhando juntas) para
obter testes de aplicao escritos primeiro.
TDD como descrita neste livro uma tcnica que est inteiramente sob seu
controle. Voc pode peg-la e comear a us-la hoje, se assim escolher. A mistura do
ritmo de vermelho/verde/refatore, das questes tcnicas das fixtures da aplicao e
das questes de mudana organizacional envolvendo testes escritos por usurios
improvvel que seja bem-sucedida. A regra do Teste de Um S Passo se aplica. Te-
nha vermelho/verde/refatore em sua prpria prtica, ento espalhe a mensagem.
Outro aspecto de ATDD o comprimento do ciclo entre teste e retorno. Se
um cliente escreveu um teste e, dez dias depois, ele finalmente funcionou, voc es-
taria vislumbrando a barra vermelha a maior parte do tempo. Eu acho que ainda
queria fazer TDD em nvel de programador, de modo que:

Eu tenha barras verdes imediatamente;


Eu simplifique o projeto interno.

Como voc muda para TDD no meio do caminho?


Voc tem um monte de cdigo que funciona mais ou menos. Voc quer dirigir por
testes o seu novo cdigo. O que voc faz como prximo passo?
220 Parte III Padres para Desenvolvimento Guiado por Testes

H um livro (ou livros) inteiro a ser escrito sobre mudana para TDD
quando voc tem montes de cdigo. O que segue necessariamente apenas uma
provocao.
O maior problema que o cdigo que no escrito com testes em mente tipi-
camente no muito testvel. As interfaces no so projetadas, ento fcil para
voc isolar um pequeno pedao de cdigo, rod-lo e verificar o resultado.
Corrija-o, voc diz. Sim, bem, mas possvel que a refatorao (sem fer-
ramentas automatizadas) resulte em erros, erros que voc no pegar, pois no
tem os testes. Galinhas e ovos. Ardil 22. Destruio mutuamente garantida. O que
voc faz?
O que voc no faz escrever testes para a coisa toda e refatorar a coisa toda.
Isso levaria meses, meses em que nenhuma nova funcionalidade apareceria. Gastar
dinheiro sem faz-lo geralmente falar de um processo no sustentvel.
Ento, primeiro, temos que decidir limitar o escopo de nossas mudanas. Se
vemos partes do sistema que poderiam ser dramaticamente simplificadas, mas no
exigem mudana no momento, ento as deixamos em paz. Derrame uma lgrima,
talvez, pelos pecados do passado, mas deixe-as em paz.
Segundo, temos que quebrar o impasse entre testes e refatorao. Podemos
ter feedback de outras formas alm de testes, como trabalhar muito cuidadosa-
mente e com um parceiro. Podemos ter feedback a grosso nvel, como testes em
nvel de sistema que sabem no ser adequados, mas que nos do alguma confiana.
Com esse feedback, podemos fazer as reas que temos que mudar mais receptivas
mudana.
No decorrer do tempo, as partes do sistema que mudam todo o tempo vo
parecer dirigidas por testes. Ocasionalmente, vamos passar por um beco sem ilu-
minao e ser atacados por nossos problemas, lembrando-nos de quo lentas as
coisas costumavam ser. Ento vamos desacelerar, quebrar o impasse e ir andando
de novo.

Para quem TDD destinado?


Cada prtica de programao codifica um sistema de valores, explcita ou im-
plicitamente. TDD no diferente. Se voc est feliz jogando algum cdigo que
funciona mais ou menos e est feliz em nunca olhar para o resultado de novo,
TDD no para voc. TDD repousa em uma encantadora e ingnua suposio
nrdica*: se voc escreve cdigo melhor, ser mais bem-sucedido. TDD o aju-
da a prestar ateno nas questes certas no tempo certo, assim voc pode fazer
seus projetos mais limpos; voc pode refinar seus projetos conforme aprende.
Eu digo ingnua, mas talvez seja exagero. O que ingnuo assumir que
cdigo limpo tudo o que existe para o sucesso. Boa engenharia talvez 20 por
cento do sucesso de um projeto. M engenharia certamente afunda projetos, mas
engenharia modesta pode permitir o sucesso de um projeto enquanto os outros 80
por cento funcionam direito.

* N. de R. T.: Relativa a nerds (no original era geekoid, relativa a geeks), em que nerds e geeks so
termos usados para pessoas aficcionadas por tecnologia e consideradas no muito sociveis.
Captulo 32 Dominando TDD 221

Nessa perspectiva, TDD fantstica. Ele deixa voc escrever cdigo com
muito menos defeitos e com um design muito mais limpo do que comum no
mercado. Contudo, aqueles cujas almas esto curadas pelo blsamo da elegncia
podem achar em TDD uma forma de ir bem por fazer o que bom.
TDD tambm boa para nerds que formam uma ligao sentimental com
o cdigo. Uma das grandes frustraes da minha vida de jovem engenheiro era
comear um projeto com grande excitao, ento observar o cdigo se degenerar
com o tempo. Um ano depois, eu no queria mais nada alm de jogar no lixo o
cdigo, agora fedorento, e comear o prximo projeto. TDD possibilita a voc
ganhar confiana no cdigo no decorrer do tempo. Conforme os testes se acumu-
lam (e seu teste melhora), voc ganha confiana no comportamento do sistema.
Conforme voc refina o projeto, mais e mais mudanas se tornam possveis. Meu
objetivo me sentir melhor com o cdigo depois de um ano do comeo deslumbra-
do, e TDD me ajuda a conseguir isso.

TDD sensvel a condies iniciais?


H uma certa ordem em que os testes parecem funcionar muito bem. Vermelho/
verde/refatore/vermelho/verde/refatore. Voc pode pegar os mesmos testes e im-
plement-los em uma ordem diferente, e parece que no existe jeito de avanar
em pequenos passos. realmente verdade que uma sequncia de testes uma
ordem de grandeza mais rpida/fcil de implementar que outra? s porque
minha tcnica de implementao no est altura do desafio? H alguma coisa
sobre os testes que deveria me dizer para enfrent-los em certa ordem? Se TDD
sensvel a condies iniciais num projeto pequeno, ento previsvel num pro-
jeto grande? (Da mesma forma que pequenos redemoinhos no Mississippi so
imprevisveis, mas voc pode contar com 56.000 metros cbicos por segundo,
mais ou menos, na foz do rio.)

Como TDD se relaciona com padres?


Todos os meus textos tcnicos tm sido sobre tentar encontrar regras fundamentais
que gerem comportamento similar ao dos especialistas. Em parte, porque como
eu aprendo eu acho um especialista para agir como ele, e, ento, gradualmente
descrevo o que est realmente acontecendo. Certamente no estou procurando por
regras a serem seguidas mecanicamente, embora seja assim que os que tm racioc-
nio mecnico as tenham interpretado.
Minha filha mais velha (Oi, Bethany! Eu disse que teria voc aqui fique
feliz por isso no ser mais constrangedor) passou muitos anos aprendendo a fazer
rapidamente uma multiplicao. Minha esposa e eu nos orgulhamos de fazer mul-
tiplicao rpida, e aprendemos muito rpido. O que estava acontecendo? Acon-
tece que toda a vez que Bethany se defrontava com 6 9, ela somaria 6 nove vezes
(ou 9 seis vezes, eu suponho). Longe de ser uma multiplicadora lenta, ela era uma
somadora realmente rpida.
O efeito que eu notei, e que espero que outros encontrem, que, reduzindo
comportamento repetitivo a regras, aplicar as regras se torna mecnico e um hbito.
222 Parte III Padres para Desenvolvimento Guiado por Testes

Isso mais rpido que rediscutir tudo sempre dos princpios. Quando aparece uma
exceo ou um problema que no se encaixa em nenhuma das regras, voc tem mais
tempo e energia para criar e aplicar critatividade.
Isso me acontecia quando escrevia os Padres das Melhores Prticas Small-
talk. Em algum ponto decidi apenas seguir as regras que eu estava escrevendo.
Era muito mais lento no comeo, ficar procurando as regras ou parando para
escrever uma nova regra. Depois de uma semana, contudo, descobri que o cdigo
estava arrancando as pontas dos meus dedos e que exigiria, antes, uma pausa
para reflexo. Isso me deu mais tempo e ateno para reflexes maiores sobre
projeto e anlise.
Outro relacionamento entre TDD e padres TDD como um mtodo de
implementao para projeto dirigido por padres. Digamos que decidimos que
queremos um Strategy* para algo. Ns escrevemos um teste para a primeira va-
riante e a implementamos como um mtodo. Ento, conscientemente, escrevemos
um teste para a segunda variante, esperando que a fase de refatorao nos guie
a um Strategy. Robert Martin e eu fizemos algumas pesquisas sobre esse estilo de
TDD. O problema que o projeto continua surpreendendo voc. Ideias de pro-
jeto perfeitamente sensatas revelam-se erradas. O melhor apenas pensar no que
voc quer que o sistema faa e deixar o projeto se resolver mais tarde.

Por que TDD funciona?


Prepare-se para deixar a galxia. Vamos assumir por um momento que TDD ajuda
as equipes a construir produtivamente sistemas fracamente acoplados e altamente
coesos com baixas taxas de defeito e baixo custo de perfil de manuteno. (Eu no
estou afirmando isso no geral, mas confio em voc para imaginar coisas imposs-
veis.) Como tal coisa poderia acontecer?
Parte do efeito certamente vem de reduzir defeitos. Quanto mais cedo en-
contrar e corrigir um defeito, mais barato , e, frequentemente, mais comovente
(basta perguntar a Mars Lander). Existem abundantes efeitos psicolgicos e sociais
na reduo de defeitos. Minha prpria prtica de programao se tornou muito
menos estressante quando comecei com TDD. Nunca mais precisei me preocupar
com tudo de uma vez. Eu poderia fazer esse teste rodar e depois todo o resto. O
relacionamento com minha equipe ficou mais positivo. Parei de estragar builds, e
as pessoas podiam confiar que meu software funciona. Clientes dos meus sistemas
ficaram mais positivos tambm. Uma nova liberao de sistema apenas significava
mais funcionalidade, no uma tropa de novos defeitos para identificar dentre to-
dos os seus antigos erros favoritos.
Reduo de defeitos. Onde posso sair afirmando isso? Eu tenho uma prova
cientfica?
No. Nenhum estudo demonstrou categoricamente a diferena entre TDD
e qualquer uma das muitas alternativas de qualidade, produtividade ou diver-

* N. de R. T.: Padro Gof Strategy. Strategy usado para encapsular um algoritmo dentro de uma
classe, de modo que o algoritmo possa variar independentemente dos clientes que o utilizam.
Captulo 32 Dominando TDD 223

so. Contudo, a evidncia dos casos que todos contam esmagadora e os efei-
tos secundrios so inconfundveis. Programadores realmente relaxam, times
realmente desenvolvem confiana e clientes realmente aprendem a olhar adiante
para as novas entregas (releases). De modo geral, eu direi, embora no tenha
visto o efeito oposto. Sua quilometragem pode variar, mas ter que tentar para
descobrir.
Outra vantagem de TDD que pode explicar esse seu efeito o modo como ele
encurta o ciclo de feedback nas decises de projeto. O ciclo de feedback para de-
cises de implementao obviamente curto segundos ou minutos, seguido por
reexecutar os testes dezenas ou milhares de vezes por dia. O ciclo para decises de
projeto vai entre a reflexo do projeto talvez a API devesse preferir isso, ou talvez
a metfora devesse ser essa e o primeiro exemplo, um teste que incorpora aquela
reflexo. Em vez de projetar e ento esperar semanas ou meses para alguma outra
pessoa sentir a dor ou a glria, feedback entra em segundos ou minutos conforme
voc tenta traduzir suas ideias em uma interface plausvel.
Uma resposta mais estranha para Por que TDD funciona? vem da imagina-
o febril de sistemas complexos. O inimitvel Phlip diz:

Adote prticas de programao que atraiam cdigo correto como uma funo limi-
te, no como um valor absoluto. Se voc escreve testes de unidade para cada feature, e
se voc refatora para simplificar o cdigo entre cada etapa, e se voc adiciona features
uma por vez e apenas depois de todos os testes de unidade passarem, voc criar o
que matemticos chamam de um atrator. Isso um ponto em um espao de estado
para o qual todos os fluxos convergem. Codificar mais como mudar para o melhor
ao longo do tempo do que mudar para o pior; o atrator aproxima a corretude como
uma funo limite.
Essa a corretude com que quase todos os programadores lidam com di-
ficuldade (exceto, claro, para software nas reas mdica ou aeroespacial). Mas,
melhor entender explicitamente o conceito de atrator que neg-lo ou desconsiderar
sua importncia.

E qual a do o nome?
Desenvolvimento A velha maneira de pensar em desenvolvimento de
software como fases est enfraquecida devido ao feedback entre as de-
cises ser difcil se elas esto separadas no tempo. Desenvolvimento nesse
sentido significa uma dana complexa de anlise, projeto lgico, projeto
fsico, implementao, teste, reviso, integrao e implantao.
Guiado Eu costumo chamar TDD de programao com teste primei-
ro. Contudo, o oposto de primeiro no final, e muitas pessoas tes-
tam depois que elas programam. H uma regra de nomeao em que o
oposto de um nome deve ser, no mnimo, vagamente insatisfatrio. (Parte
do apelo da programao estruturada que ningum quer que seja no
estruturada.) Se voc no desenvolve guiado por testes, pelo que voc se
guia? Especulao? Especificaes? (Nunca notou que essas duas palavras
vm da mesma raiz comum?)
224 Parte III Padres para Desenvolvimento Guiado por Testes

Teste Testes automatizados, materializados, concretos. Aperte um boto


e eles executam. Uma das ironias de TDD que no uma tcnica de
teste. uma tcnica de anlise, uma tcnica de projeto, realmente uma
tcnica para estruturar todas as atividades de desenvolvimento.

Como TDD se relaciona com as prticas da Programao Extrema?


Alguns revisores deste livro estavam preocupados que, por eu escrever um livro
exclusivamente sobre TDD, as pessoas tomariam isso como uma desculpa para
ignorar o resto dos conselhos em Programao Extrema (XP). Por exemplo, se
voc se guia por testes, voc ainda precisa programar em pares? Aqui est um
breve resumo de como o resto de XP melhora TDD, e como TDD melhorar o
resto de XP.

Programao em pares Os testes que voc escreve em TDD so exce-


lentes pedaos de conversa quando voc est programando em par. O
problema que voc evita de os parceiros no concordarem em qual dos
problemas esto resolvendo, mesmo que estejam tentando trabalhar no
mesmo cdigo. Isso soa meio maluco, mas acontece o tempo todo, es-
pecialmente quando est aprendendo a programar em par com algum.
Pares melhoram TDD dando a voc uma mente descansada para assumir
quando voc fica cansado. O ritmo de TDD pode dren-lo e faz-lo con-
tinuar a programar mesmo quando est cansado. Seu parceiro, contudo,
est preparado para pegar o teclado quando voc tombar.
Trabalhe descansado Em uma nota relacionada, XP aconselha voc a
trabalhar quando estiver descansado e parar quando estiver cansado.
Quando no puder fazer o prximo teste funcionar, ou aqueles dois tes-
tes funcionarem juntos, hora de um intervalo. O tio Bob Martin* e eu
estvamos trabalhando em um algoritmo de quebra de linha uma vez, e
no conseguamos faz-lo funcionar. Lutamos contra a frustrao por
alguns minutos, mas era bvio que no estvamos fazendo progresso,
ento paramos.
Integrao contnua Os testes so um recurso excelente, fazendo voc
capaz de integrar mais frequentemente. Voc obtm outro teste funcio-
nando e remove a duplicao, e voc faz o check-in. O ciclo pode ser
de 15 a 30 minutos em vez das 1 ou 2 horas que eu geralmente levava.
Isso pode ser parte da chave para ter times maiores de programadores
no mesmo cdigo base. Como Bill Wake diz um problema n2 no um
problema se n sempre 1.
Projeto simples Codificando apenas o que voc precisa para os testes e
removendo toda a duplicao, voc automaticamente tem um projeto que
perfeitamente adaptado aos requisitos atuais e igualmente preparado

* N. de R. T.: Robert Martin, vulgo Tio Bob (Uncle Bob), conhecido desenvolvedor de software,
consultor e autor de livros e, como Kent Beck, signatrio do Manifesto gil.
Captulo 32 Dominando TDD 225

para histrias futuras. A concepo que voc est procurando para ape-
nas projetar o suficiente para atingir a arquitetura perfeita para o sistema
atual tambm torna mais fcil a escrita de testes.
Refatorao A regra de Remoo de Duplicao outra forma de dizer
refatorao. Mas, os testes do a voc confiana de que suas refatora-
es maiores no mudaram o comportamento do sistema. Quanto maior
sua confiana, mais agressivo voc ser ao tentar refatoraes em larga
escala que estendem a vida do seu sistema. Refatorando, voc torna a es-
crita da prxima rodada de testes muito mais fcil.
Entrega contnua Se testes de TDD realmente aumentam o TMEF do
seu sistema (uma prova voc ter que verificar sozinho), ento voc pode
colocar cdigo em produo muito mais frequentemente sem perturbar
clientes. Gareth Reeves faz a analogia negociao diurna (day trading).
Em uma negociao diurna, voc fecha suas posies toda a noite, pois
no quer um risco que no esteja gerenciando. Em programao, voc
prefere todas as suas mudanas na produo, pois no quer cdigo que
no esteja recebendo feedback concreto.

Desafio de Darach
Darach Ennis fez um desafio para estender o alcance de TDD. Ele disse:

H um monte de falcias soprando em torno de vrias organizaes de engenharia


e entre vrios engenheiros que este livro poderia ajudar a desmitificar, e algumas
delas so:

Voc no pode testar GUIs automaticamente (por exemplo, Swing, CGI, JSP/Ser-
vlets/Struts).
Voc no pode fazer testes de unidade em objetos distribudos automaticamente
(por exemplo, RPC e estilo Mensagem, ou CORBA/EJB e JMS).
Voc no pode desenvolver testando primeiro seu esquema de banco de dados
(por exemplo, JDBC).
No h necessidade de testar cdigo de terceiros ou gerado por ferramentas ex-
ternas.
Voc no pode desenvolver testando primeiro um compilador/interpretador de
linguagem de BNF para implementao em qualidade de produo.

No tenho certeza de que ele esteja certo, mas eu tambm no tenho certeza
de que ele esteja errado. Ele me deu algo para mascar conforme penso em quo
longe empurrar TDD.
Apndice I
Diagramas de Influncia

Este livro contm muitos exemplos de diagramas de influncia. A ideia dos


diagramas de influncia obtida da excelente srie Quality Software Manage-
1
ment de Gerald Weinberg, particularmente do Livro 1: Systems Thinking . O
propsito de um diagrama de influncia ver como os elementos de um sistema
afetam um ao outro.
Diagramas de influncia tm trs elementos:

Atividades, anotadas como uma palavra ou frase curta.


Conexes positivas, anotadas como uma flecha direcionada entre duas
atividades, significando que mais da atividade origem tende a criar mais
da atividade destino, ou menos da atividade de origem tende a criar me-
nos da atividade destino.
Conexes negativas, anotadas como flechas direcionadas entre duas ati-
vidades com um crculo sobre ela, significando que mais da atividade de
origem tende a criar menos da atividade destino, ou menos da atividade
de origem tende a criar mais da atividade destino.

Um monte de palavras para um conceito simples. As Figuras A.1 a A.3 fornecem


alguns exemplos.
Quanto mais eu como, mais pesado eu fico. Quanto menos eu como, menos
pesado eu fico. Peso pessoal um sistema bem mais complicado que isso, certa-
mente. Diagramas de influncia so modelos para ajudar voc a entender alguns
aspectos do sistema, no entender e control-lo perfeitamente.

1
Weinberg, Gerald. 1992. Systems Thinking. Quality Software Management. New York: Dorset House.
ISBN: 0932633226.
228 Apndice I

Comparecer
ao Circo

Aparar a Cerca

Figura A.1 Duas atividades aparentemente no relacionadas.

Comer

Peso

Figura A.2 Atividades positivamente conectadas.

Exerccio

Peso

Figura A.3 Atividades negativamente conectadas.

Feedback
A influncia no funciona em apenas uma direo. Frequentemente o efeito de
uma atividade volta para mudar a prpria atividade, positivamente ou negativa-
mente, como mostrado na Figura A.4.
Se meu peso aumenta, ento minha autoestima cai, o que me faz querer co-
mer mais, o que faz meu peso aumentar, e assim por diante. Sempre que voc tem
um ciclo em um diagrama de influncia, voc tem feedback.
Apndice I 229

Peso

Autoestima
Comer

Figura A.4 Feedback.

H dois tipos de feedback positivo ou negativo. Feedback positivo causa


sistemas que encorajam mais e mais de uma atividade. Voc pode achar ciclos
de feedback positivos contando o nmero de conexes negativas em um ciclo. Se
h um nmero par de conexes negativas, ento voc tem um ciclo de feedback
positivo. O ciclo de feedback na Figura A.4 um ciclo de feedback positivo. Ele
o obrigar a ficar ganhando peso at que a influncia de alguma outra atividade
tenha efeito.
Feedback negativo desencoraja ou reduz uma atividade. Ciclos com um n-
mero mpar de conexes negativas so ciclos de feedback negativos.
As chaves para o projeto de sistema so:

Criar ciclos virtuosos cujos ciclos de feedback positivos encorajam o cres-


cimento de boas atividades.
Evitar espirais da morte cujos ciclos de feedback positivos encorajam o
crescimento de atividades improdutivas ou destrutivas.
Criar ciclos de feedback negativos para prevenir explorao de boas ati-
vidades.

Controle de sistema
Quando escolher um sistema de prticas de desenvolvimento de software, voc
gostaria que as prticas oferecessem suporte uma outra de modo que voc tende
a fazer a quantia certa de qualquer atividade, mesmo sob estresse. A Figura A.5
um exemplo de um sistema de prticas que conduzem a teste insuficiente.
Sob a presso do tempo, voc reduz a quantidade de teste, o que aumenta
o nmero de erros, o que aumenta a presso do tempo. Eventualmente, alguma
atividade de fora (como Pnico no Fluxo de Caixa) age para entregar o software
de qualquer maneira.
230 Apndice I

Presso

Teste
Erros

Figura A.5 A falta de tempo suficiente para teste reduz o tempo disponvel.

Quando voc tem um sistema que no est se comportando, voc tem


opes.

Direcione um ciclo de feedback positivo para outra direo. Se voc tem


um ciclo entre testes e confiana, e testes tm falhado de modo a reduzir a
confiana, ento voc pode fazer mais testes funcionarem para aumentar
a confiana em sua habilidade de ter mais testes funcionando.
Introduza um ciclo de feedback negativo para controlar uma atividade
que cresceu demais.
Crie ou quebre conexes para eliminar ciclos que no esto ajudando.
Apndice II
Fibonacci

Em resposta a uma questo de um dos revisores deste livro, disponibilizo um Fibo-


nacci dirigido por testes. Muitos revisores comentaram que esse exemplo ajudou a
cair a ficha de como TDD funciona. Contudo, ele no longo o suficiente, nem
demonstra tcnicas de TDD suficientes, para substituir os exemplos existentes.
Se suas luzes ainda esto apagadas depois de ler os exemplos principais, d uma
olhada aqui e veja.
O primeiro teste mostra que fib(0) = 0. A implementao retorna uma cons-
tante.

public void testFibonacci() {


assertEquals(0, fib(0));
}

int fib(int n) {
return 0;
}

(Eu apenas estou usando a classe TestCase como habitat para o cdigo, pois essa
somente uma nica funo.)
O segundo teste mostra que fib(1) = 1.

public void testFibonacci() {


assertEquals(0, fib(0));
assertEquals(1, fib(1));
}

Eu s pus a segunda assero no mesmo mtodo, porque l no parecia estar qual-


quer valor de comunicao substancial para escrever testFibonacciOfOneIsOne.
232 Apndice II

H muitos jeitos que eu poderia usar para fazer isso rodar. Escolherei tratar
0 como um caso especial:

int fib(int n) {
if (n == 0) return 0;
return 1;
}

A duplicao no caso de teste est comeando a me incomodar, e ela s vai ficar pior
conforme adicionarmos novos casos. Podemos fatorar a estrutura comum das asser-
es dirigindo o teste a partir de uma tabela de entrada e valores esperados.

public void testFibonacci() {


int cases[][]= {{0,0},{1,1}};
for (int i= 0; i < cases.length; i++)
assertEquals(cases[i][1], fib(cases[i][0]));
}

Agora adicionar o prximo caso requer seis teclas pressionadas e nenhuma


linha adicional:

public void testFibonacci() {


int cases[][]= {{0,0},{1,1},{2,1}};
for (int i= 0; i < cases.length; i++)
assertEquals(cases[i][1], fib(cases[i][0]));
}

Desconcertantemente, o teste funciona. Acontece que nossa constante 1 est certa


para esse caso tambm. No prximo teste:

public void testFibonacci() {


int cases[][]= {{0,0},{1,1},{2,1},{3,2}};
for (int i= 0; i < cases.length; i++)
assertEquals(cases[i][1], fib(cases[i][0]));
}

Ipi, ele falha. Aplicando a mesma estratgia de antes (tratando entradas menores
como casos especiais), escrevemos:

int fib(int n) {
if (n == 0) return 0;
if (n <= 2) return 1;
return 2;
}

Agora estamos prontos para generalizar. Escrevemos 2, mas no queremos


dizer realmente 2, ns queremos dizer 1 + 1.

int fib(int n) {
if (n == 0) return 0;
if (n <= 2) return 1;
return 1 + 1;
}
Apndice II 233

Aquele primeiro 1 um exemplo de fib(n-1):

int fib(int n) {
if (n == 0) return 0;
if (n <= 2) return 1;
return fib(n-1) + 1;
}

O segundo 1 um exemplo de fib(n-2):

int fib(int n) {
if (n == 0) return 0;
if (n <= 2) return 1;
return fib(n-1) + fib(n-2);
}

Fazendo agora a limpeza, a mesma estrutura deve funcionar para fib(2), logo
podemos estreitar a segunda condio:

int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n-1) + fib(n-2);
}

E a temos Fibonacci, derivado totalmente de testes.


Posfcio

Martin Fowler

Uma das coisas mais difceis de comunicar sobre o desenvolvimento guiado por
testes o estado mental em que ele o coloca. Eu me lembro de uma sesso no pro-
jeto C3 original com Ralph Beattie em que tnhamos de implementar um conjunto
complicado de condies de pagamento. Ralph os quebrou em um conjunto de
casos de teste, e ns os configuramos um a um para faz-los funcionar. O progres-
so foi uniforme e sem pressa; devido a no ter pressa, parecia lento, mas, olhando
para trs para o quanto tnhamos pronto, era claro que, a despeito do sentimento
de que no havia pressa, o progresso era realmente rpido.
Apesar de todas as ferramentas extravagantes que temos, programar ainda
difcil. Eu posso lembrar-me de muitas vezes em que estava programando e me sen-
tia como se estivesse tentando manter muitas bolas no ar de uma s vez; qualquer
lapso de concentrao e tudo viria abaixo. Desenvolvimento guiado por testes re-
duz esse sentimento e, como resultado, voc tem essa rpida reduo de presso.
Eu acho que a razo para isso que trabalhar em um estilo de desenvolvi-
mento guiado por testes d a voc essa sensao de manter apenas uma bola no
ar por vez, ento voc pode se concentrar adequadamente naquela bola e fazer
realmente um bom trabalho com ela. Quando estou tentando adicionar alguma
nova funcionalidade, no estou preocupado com o que realmente faz um bom
projeto para esse pedao de funo; estou apenas tentando fazer um teste passar
to facilmente quanto eu puder. Quando mudo para o modo de refatorao,
no estou preocupado em adicionar alguma nova funcionalidade, apenas estou
preocupado em obter o design certo. Com essas duas coisas, estou concentrado
em uma coisa por vez e, como resultado, posso me concentrar melhor naquela
nica coisa.
Adicionar features testando primeiro e refatorando depois so dois desses sa-
bores monolgicos de programao. Em uma recente breve passagem pelo teclado,
experimentei outro: o padro cpia. Eu estava escrevendo um pequeno script em
236 POSFCIO

Ruby que puxava alguns dados de um banco de dados. Conforme eu fazia isso, co-
mecei em uma classe que empacotava a tabela do banco de dados e pensei comigo
que, uma vez que recm tinha terminado um livro de padres de banco de dados,
eu deveria usar um padro. Embora o cdigo de exemplo fosse em Java, no era
difcil adapt-lo para Ruby. Enquanto programava isso, eu no pensei realmente
sobre o problema; apenas pensei em fazer uma adaptao fiel do padro para os
dados especficos e linguagem que estava manipulando.
O padro cpia em si no boa programao um fato que sempre friso
quando falo sobre padres. Padres so sempre meio crus e precisam ser adap-
tados no forno do seu prprio projeto. Mas, uma boa maneira de fazer isso
copiar o padro primeiro cegamente e ento usar algum mix de refatorao ou
teste-primeiro para fazer a adaptao. Desse jeito, quando voc est fazendo uso
do padro cpia, voc pode concentrar-se apenas no padro uma coisa por vez.
A comunidade XP batalhou para achar onde padres se encaixam nesse cen-
rio. Claramente, a comunidade XP a favor dos padres, afinal de contas h uma
enorme interseco entre os advogados de XP e os advogados de padres Ward e
Kent foram lderes de ambos. Talvez o padro cpia seja um terceiro modo mono-
lgico de seguir testando primeiro e refatorando, e, como esses dois, seja perigoso
em si, mas poderoso quando combinado.
Uma grande parte de fazer atividades sistemticas identificar tarefas cen-
trais e nos permitir concentrar em apenas uma de cada vez. Uma linha de monta-
gem um exemplo entorpecedor disso entorpecedor, pois voc apenas faz uma
coisa. Talvez o que desenvolvimento guiado por testes sugira uma forma de des-
membrar o ato de programar em modos elementais, mas evitando a monotonia,
trocando rapidamente entre esses modos. A combinao de modos monolgicos
e essas trocas d a voc os benefcios de concentrao e baixo estresse no crebro,
sem a monotonia da linha de montagem.
Eu admito que essas reflexes esto de alguma forma meio cruas. Confor-
me escrevo isto, estou ainda incerto se acredito no que digo, e sei que estarei
ruminando esses pensamentos por alguns ou muitos meses. Mas, achei que voc
poderia gostar deles de qualquer jeito, talvez para estimular suas reflexes no
cenrio maior em que desenvolvimento guiado por testes se encaixa. No
um cenrio que vemos claramente ainda, mas acho que lentamente ele est se
revelando.
ndice

A Teste de Um S Passo (One Step D


Acoplamento de teste, 118-119 Test), 153-155 Dados de Teste (Test Data),
Adio, escrevendo/testando cdigo Teste Inicial (Starter Test), 148-150
para o processo de adicionando 154-155 Dados Evidentes (Evident Data),
parmetros, 87-91 149-151
declaraes, fazendo outras mu- C Dados Realistas, 149-150
danas se propagarem, 93-100 Check-in Limpo (Clean Check-in), Defina uma Assero Primeiro (As-
eliminando duplicao de dados, 169-170 sert First), Teste, 147-149
87-91 Classes concretas, 66-67 Dependncia no cdigo, e duplica-
implementando/movendo cdigo, Classes mtricas de cdigo, o de cdigo, 27, 28
81-85 104-105 Desenvolvimento guiado por Testes.
metforas, 76-79 concretas, 66-67 Veja TDD
Assero (Assertion), padro de pro- definindo, 25-26 Desenvolvimento guiado por testes
jeto, 177-179 eliminando verificao explcita de aplicao (ATDD), versus TDD,
ATDD (desenvolvimento guiado por de classes com polimorfismo, 219
testes de aplicao), versus TDD, 81-85 Diagramas de influncia, 143-144,
219 subclasses, 47-51, 56-57 146-147
AutoDesvio (Self Shunt), Testes, subclasses, eliminao de, 59-63 elementos, 227-228
165-166 subclasses, substituindo refern- feedback, 228-229
cias por referncias de superclas- sistemas de prticas de desenvol-
B ses, 71-73 vimento, 229-230
Barra verde, padres de projeto de superclasses, 47-51 Dinheiro multi-moeda, criao do
faz de conta, 171-173 teste de igualdade, 35-38, objeto, 23-30
Implementao bvia (Obvious 47-51 Duplicao de cdigo
Implementation), 174-175 Cdigo funcionando, versus cdigo e dependncia de cdigo, 27, 28
Migrao de dados em Um para de teste, 28, 29 removendo, 44, 45
Muitos (One to Many), 203-204 Collecting Parameter, padro de
Triangulao, 173-174 projeto, 134-135, 186, 198-199 E
Um para Muitos (One to Many), Command, padro de projeto, Estresse em testes
174-176 185-187 diagramas de influncia, 143-144
Barra vermelha, padres de projeto Complexidade ciclomtica, mtricas versus TDD, 105-107
de de cdigo, 104-105 Excees
Teste de Aprendizado (Learning Composite, padro de projeto, manipulando excees, 125-127
Test), 156-157 133-137, 186, 196-198 Teste de Exceo (Exception
Teste de Explicao (Explanation e Imposters, 196 Test), 182-183
Test), 155-156 Conjuntos para mltiplos testes Extrair Interface (Extract Interface)
Teste de Regresso (Regression AllTests, 182-184 e Testes AutoDesvio (Self Shunt),
Test), 157-158 TestSuite, 133-137 166
238 NDICE

Extrair Mtodo (Extract Method), Imposter, padro de projeto, 186, implementaes redundantes, 51
115 195-196 implementaes redundantes, eli-
refatorao, 203-205 Isolar Mudana (Isolate Change) minando, 71-73
versus Objeto Mtodo (Method refatorao, 202-203 melhorando testes com funciona-
Object), 209 Um para Muitos (One to Many), lidade, 39-41
Extrair Objetos (Extract Object), padres de projeto, 174-176 mtodos fbrica, 56-57
203 refatorao de cdigo, 60-63
J teste de igualdade, 35-38, 47-51,
F JProbe, 105-106 53-54
Factory Methods, 56-57 JUnit, usando, 103
padres de projeto, 186, 194-195 JXUnit, 178-179 O
Falhas em testes Objeto Mtodo (Method Object),
framework xUnit, 129-131 L versus Extrair Mtodo (Extract
processo de multiplicao, 24-27 Lista de Testes (Test List), 146-147 Method), 209
Tempo Mdio Entre Falhas, Listas de Tarefas, 24 Objetos
TMEF (Mean Time Between criando, 55-57
Failures - MTBF), 216-217 M criando, dinheiro multi-moeda,
Fazer de conta implementaes de Mensagens de erro, 67-68 23-30
TDD, 33, 126, 158 Mtaforas, 76-79 criando, pela fora dos testes,
Modelo de Teste de Acidentes vantagens, 102-103 81-85
(Crash Test Dummy), 167 Mtodo em uma Linha (Inline Me- criando, restries causando con-
padres de projeto de barra ver- thod), 205-206 flitos, 117-120
de, 171-173 Metodologia do Chuveiro, 158 Null Object, padres de projeto,
Feedback Mtodos 186, 189-190
diagramas de influncia, 228-229 Extrair Mtodo (Extract Me- Null Object, padres de projeto, e
quantidade necessria em TDD, thod), refatorao, 203-205 Impostors, 196
216-217 Extrair Mtodo (Extract Me- Objeto Simulado (Mock Object),
Feedback negativo, diagramas de thod), versus Objeto Mtodo 164-165
influncia de, 228-229 (Method Object), 209 Objeto Simulado (Mock Object),
Feedback positivo, diagramas de Factory Methods, 56-57, 186, e extrao de interfaces, 207
influncia de, 228-229 194-195 Objeto Simulado (Mock Object),
Fibonacci, 231-233 Mtodo em uma Linha (Inline e Modelo de Teste de Acidentes
Flags de mtodos chamados, Method), 205-206 (Crash Test Dummy), 168
111-120 Mover Mtodo (Move Method|), Princpio Aberto/Fechado,
versus registros, 121 207-209 215-216
Funes, mtricas de cdigo, parmetros a mtodos, adicionan- Value Objects, 35-38
104-105 do, 210-211
parmetros de mtodos, mudan- P
I do, 210-211 Padro Arrange/Aja/Asseres (3A),
Iguadade, teste de, 35-38, 47-51, reconciliao, 65-66-69 117-118
53-54 setUp(), 117-120 Padres de projeto
Implementao de triangulao de tearDown(), 121-127 AutoDesvio (Self Shunt), testes,
TDD, 33, 36-38, 158 Template Method, 166, 186, 165-166
padres de projeto de barra ver- 190-192 Check-in Limpo (Clean
de, 173-174 testMethod(), 111-115 Check-in), 169-170
Implementao bvia (Obvious Mtricas de cdigo, 104-105 Collecting Parameters, 134-135,
Implementation) de TDD, 33, 123, Modelo de Teste de Acidentes 186, 198-199
158 (Crash Test Dummy), 167-168 Command, 185-187
padres de barra verde, 174-175 extraindo interfaces, 207 Composite, 133-137, 186,
Implementaes de TDD Multiplicao, escrevendo/testando 196-198
Faz de conta (Fake It), e Modelo cdigo para o processo de, 27, 31 Factory Methods, 186, 194-195
de Teste de Acidentes (Crash Test ciclo de teste, 21, 27, 31, 104-105 Imposters, 186, 195-196
Dummy), 167 ciclo de teste, decises nos passos Modelo de Teste de Acidentes
Faz de conta (Fake It), padres de iniciais, 24-26 (Crash Test Dummy), 167-168
projeto de barra verde, 171-173 ciclo de testes, escopo, 25-26, Null Objects, 186, 189-190
Faz de conta, 33, 123, 158 43-45 Objeto Simulado (Mock Object),
Implementao bvia (Obvious ciclo de testes, falhas, 24-27 207
Implementation), padres de escrevendo testes copiando/colan- panorama, 185-186
barra verde, 174-175 do cdigo, 43-45 Pluggable Objects, 186, 191-193
bvio, 33, 123, 158 implementaes, Triangulao, Pluggable Selectors, 186, 193-194
Triangulao, 33, 36-38, 158 33, 36-38 Singletons, 199
Triangulao, padres de projeto implementaes Faz de conta, 33 String de Registro (Log String),
de barra verde, 173-174 implementaes bvias, 33 166-167
NDICE 239

Template Method, 186, 190-192 Mtodo em uma Linha (Inline Tempo Mdio Entre Falhas,
Teste Filho (Child Test), 163 Method), 205-206 216-217
Teste Quebrado (Broken Test), migrao de dados, Migrar Dados teste, definio, 143
168-169 (Migrate Data) 203-204 usurios potenciais, 220-221
Value Objects, 185-189 Mover Mtodo (Move Method|), versus desenvolvimento dirigido
Padres de projeto, framework 207-209 por testes de aplicao, 219
xUnit Objeto Mtodo (Method Object), versus outros estilos de programa-
Asseres, 177-179 203, 209-211 o, 97-100
Fixtures, 178-180 parmetros a mtodos, adicionan- versus outros tipos de teste,
Fixtures Externas, 180-181 do, 210-211 105-107
Mtodo de Teste (Test Method), parmetros de mtodo, mudando, versus Programao Extrema,
181-183 210-211 224-225
Teste de Exceo (Exception Reconciliar Diferenas (Reconcile TDD, ciclo de teste, 21, 27, 31,
Test), 182-183 Differences), 201-202 104-105
Todos os Testes (All Tests), Registro de mtodos chamados, passos, tamanho dos, 213-215
182-184 121-127 passos, passos iniciais, 24-26
Padres de projeto, padres de barra versus flags, 121 escopo, 25-26, 43-45
verde falhas, 24-27
Faa de conta (Fake It), S TDD, diretrizes de
171-173 Scripts com o framework de testes comear de novo, Faa de Novo
Implementao bvia (Obvious xUnit, 140 (Do Over), 159-160
Implementation), 174-175 SetUp(), mtodo, 117-120 configurao fsica, 160-161
Triangulao, 173-174 Singleton, padres de projeto, 199 faa Pausa (Break), 158-159
Um para Muitos (One to Many), SmallLint para SmallTalk, 102-103 TDD, padres de projeto para,
174-176, 203-204 String de Registro (Log String), 221-222
Padres de projeto, padres de barra 166-167 bsico, 143-145
vermelha Subclasses, 47-51, 56-57 Check-in Limpo (Clean
Teste de Aprendizado (Learning eliminando, 59-63 Check-in), 169-170
Test), 156-157 substituindo referncias por refe- Collecting Parameter, 134-135
Teste de Explicao (Explanation rncias de superclasses, 71-73 Collecting Parameters, 186,
Test), 155-156 Sute de testes AllTests, 182-184 198-199
Teste de Regresso (Regression Superclasses, 47-51 Command, 185-187
Test), 157-158 substituindo referncias para sub- Composite, 133-137, 186,
Teste de Um S Passo (One Step classes, 71-73 196-198
Test), 153-155 Dados de Teste (Test Data),
Teste Inicial (Starter Test), T 148-150
154-155 TDD Dados Evidentes (Evident Data),
Pluggable Objects, padres de proje- anlise do termo, 223-225 149-151
to, 186, 191-193 apagando testes, 218 Dados Realistas, 149-150
Pluggable Selectors, 115 atributos de testes eficazes, Defina uma Assero Primeiro
padres de design, 186, 193-194 214-216 (Assert First), 147-149
Polimorfismo, eliminando verifica- aumentando o alcance de TDD, Factory Methods, 186, 194-195
o explcita de classe por, 81-85 225 Imposters, 186, 195-196
Princpio Aberto/Fechado (objetos), cdigo reusvel, 215-216 Lista de Testes (Test List),
215-216 comeando prticas de TDD no 146-147
Privadas, variveis de instncia, meio de projetos, 219-220 Modelo de Teste de Acidentes
40-41 decises do contedo para testar, (Crash Test Dummy), 167-168
Problemas de inicializao, mtodo 214-215 Modelo de Teste de Acidentes
de teste, 111-115 escalando para grandes sistemas, (Crash Test Dummy), extraindo
Programao em pares, configura- 218-219 interfaces, 207
o fsica da, 160-161 feedback necessrio, quantidade Null Objects, 186, 189-190
Programao Extrema (XP), versus de, 216-217 Objeto Simulado (Mock Object),
TDD, 224-225 Fibonacci, 231-233 164-165
influncia de linguagens e am- panorama, 185-186
R bientes de programao, 218 Pluggable Objects, 186, 191-193
Refatorao de cdigo, 60-63 JProbe, 105-106 Pluggable Selectors, 186,
extraindo interfaces, 207 Princpio Aberto/Fechado (obje- 193-194
Extrair Mtodo (Extract Me- tos), 215-216 Self Shunt, Testes, 165-166
thod), 203-205 qualidade de teste, 105-107 Singletons, 199
Extrair Objetos (Extract Object), razes para efetividade, 221-223 String de Registro (Log String),
203 sequncia de teste, 221 166-167
Isolar Mudana (Isolate Change), SmallLint para SmallTalk, Template Method, 186, 190-192
202-203 102-103 Teste Filho (Child Test), 163
240 NDICE

Teste Isolado (Isolated Test), parmetros de mtodos, mudan- Teste de usabilidade, versus TDD,
144-146 do, 210-211 105-107
Teste Primeiro (Test First), Reconciliar Diferenas (Reconcile Teste Filho (Child Test), 163
146-148 Differences), 201-202 Teste Inicial (Starter Test), 154-155
Teste Quebrado, 168-169 TearDown(), mtodo, 121-127 e Teste de Um S Passo (One Step
Value Objects, 185-189 Template Method, 166 Test), 155
TDD, padres de projeto para, fra- padres de projeto, 186, 190-192 Teste Isolado (Isolated Test), padres
mework xUnit Tempo Mdio Entre Falhas (TMEF), de projeto, 144-146
Asseres, 177-179 216-217 Teste Primeiro (Test First), 146-148
Fixtures, 178-180 Testando/escrevendo cdigo para o Teste Quebrado (Broken Test),
Fixtures Externas, 180-181 processo de adio 168-169
Mtodo de Teste (Test Method), adicionando parmetros, 87-91 TestMethod(), mtodo, 111-115
181-183 declaraes, fazendo outras mu- TestSuite, mltiplos testes, 133-137
Teste de Exceo (Exception danas se propagarem, 93-100 TMEF (Tempo Mdio Entre Falhas),
Test), 182-183 eliminando duplicao de dados, 216-217
Todos os Testes (All Tests), 87-91 3A (Arrange/Aja/Afirme), padro,
182-184 implementando/movendo cdigo, 117-118
TDD, padres de projeto para, pa- 81-85
dres de barra verde metforas, 76-79 U
Fazer de conta, 171-173 Testando/escrevendo cdigo para o Um para Muitos (One to Many),
Implementao bvia (Obvious processo de multiplicao padres de projeto, 174-176
Implementation), 174-175 ciclo de teste, 21, 27, 31, Migrar Dados (Migrate Data),
Triangular (Triangulate), Triangu- 104-105 203-204
lao 173-174 ciclo de teste, decises nos passos
Um para Muitos (One to Many), iniciais, 24 V
174-176 ciclo de testes, escopo, 25-26, Value Objects, 35-38
Um para Muitos (One to Many), 43-45 padres de projeto, 185-189
migrao de dados, 203-204 ciclo de testes, falhas, 24-27 Variveis
TDD, padres de projeto para, pa- escrevendo testes copiando/edi- substituindo constantes, 65-69
dres de barra vermelha tando cdigo, 43-45 variveis privadas de instncia,
Teste de Explicao (Explanation implementaes, Triangulao, 40-41
Test), 155-156 33, 36-38 Verificao explcita de classe, elimi-
Teste de Regresso (Regression implementaes Fazer de conta, nando com polimorfismo a, 81-85
Test), 157-158 33
Teste de Um S Passo (One Step implementaes bvias, 33 X
Test), 153-155 implementaes redundantes, 51
XP (Programao Extrema), versus
Teste Inicial (Starter Test), implementaes redundantes, eli-
TDD, 224-225
154-155 minando, 71-73
xUnit, framework de testes
Testes de Aprendizado, 156-157 melhorando testes com funciona-
falhas, 129-131
TDD, refatorao em lidade, 39-41
mtodos, setUp(), 117-120
extraindo interfaces, 207 mtodos fbrica, 56-57
mtodos, tearDown(), 121-127
Extrair Mtodo (Extract Me- refatorao de cdigo, 60-63
mtodos, testMethod(), 111-115
thod), 203-205 teste de igualdade, 35-38, 47-51,
razes para implementar, 139-140
Extrair Objetos (Extract Object), 53-54
TestSuite, rodando mltiplos tes-
203 versus cdigo que funciona, 28,
tes, 133-137
Isolar Mudana (Isolate Change), 29
xUnit, padres de projeto de TDD
202-203 Teste de Aprendizado (Learning
para o framework de testes,
Mtodo em uma Linha (Inline Test), 156-157
Asseres, 177-179
Method), 205-206 Teste de Explicao (Explanation
Fixtures, 178-180
Migrar Dados (Migrate Data), Test), 155-156
Fixtures Externas, 180-181
203-204 Teste de performance, versus TDD,
Mtodo de Teste (Test Method),
Mover Mtodo (Move Method|), 105-107
181-183
207-209 Teste de Regresso (Regression
Teste de Exceo (Exception
Objeto Mtodo (Method Object), Test), 157-158
Test), 182-183
203, 209-211 Teste de Um S Passo (One Step
Todos os Testes (All Tests),
parmetros a mtodos, adicionan- Test), 153-155
182-184
do, 210-211 e Teste Inicial (Starter Test), 155