Páginas

Showing posts with label tdd. Show all posts
Showing posts with label tdd. Show all posts

Monday, January 30, 2017

Does TDD work?! Baby steps?!

I believe one good quality of thinkers, engineers, programmers, you name it, is skepticism.
Not taking things for granted allows one to dig deeper, try to understand pros and cons, dive into a detailed level of understanding.

TDD is old enough that you can find online (and physical books even) a myriad of related material, both favoring it and bashing it. I'd say that questioning it is great, whether you are in favor, against or indifferent towards it.

I'd like to share some links I've (re)visited recently:

TDD


The first one is a question, "why does TDD work?", or perhaps it should've been "does TDD work?!?"

I sympathize with this comment (emphasis mine):

TDD is, in my opinion, mainly about ways to make sure you can check parts rather than a big 'all or nothing' at the end. But the adagium of TDD, 'build a test first' is not meant to be 'make a test before you think about what you want to accomplish'. Because thinking of a test IS part of designing. Specifying what you want that exact part to do, is designing. Before you ever start to type, you have already done some designing. (In that way I think the term 'test driven design' is misleadingly implying a oneway path, where it is really a feedback loop).


Good design can come either through picking tests intelligently, or by any other technique... how do you learn and discover good designs? You either learn from existing designs, or you try things out.
I think the former is more effective, and immensely prepares you for the latter.

To a mathematician's mind, I'm speaking to you now, though I see programming are much more than maths, as programming projects are also related to arts, social science, communication, and more...
The way how I learned many topics in maths (and physics, etc): I was taught some theory, and then I did hundreds, perhaps thousands, of exercises. Often an exercise builds up on ideas you've learned in previous challenges. Often a problem is hard enough that you need to be presented with a solution... then you understand it, it makes the problem seem much easier than it originally was. And them you feel empowered to solve similarly tough exercises, and to innovate when faced with the unseen.


What if TDD doesn't work as in this quote from Peter Norvig:

“Well, the first thing you do is write a test that says I get the right answer at the end,” and then you run it and see that it fails, and then you say, “What do I need next?”—that doesn’t seem like the right way to design something to me. It seems like only if it was so simple that the solution was preordained would that make sense. I think you have to think about it first. You have to say, “What are the pieces? How can I write tests for pieces until I know what some of them are?” And then, once you’ve done that, then it is good discipline to have tests for each of those pieces and to understand well how they interact with each other and the boundary cases and so on. Those should all have tests. But I don’t think you drive the whole design by saying, “This test has failed.”


Indeed. Writing arbitrary tests and making them pass don't take you anywhere.

The second link I share is a good commented example of how it goes when the order of tests in unfavorable and we get stuck. One needs to think. You need to build a baggage, a toolbox, and it takes time ;-)


At all times, let's keep in mind that who drives the show is YOU (by writing tests or anything else).

Like other techniques, TDD doesn't always work, for it depend on individual and collective "talent" to get things done at a certain quality level.

(...) how come it does not always work?

Because testing requires a VERY different mindset than building does. Not every one is able to switch back and from, in fact some people will not be able to build proper tests simply because they cannot set their mind to destroy their creation. This will yield projects with too few tests or tests just enough to reach a target metrics (code coverage comes to mind). They will happy path tests and exception tests but will forget about the corner cases and boundary conditions.

Others will just rely on tests forgoing design partly or altogether. (...)


One positive thing about practicing collectively in a coding dojo is the ability to criticize design decisions, and to share that "baggage" on things that work, and things that doesn't, most often being able to show why it does or doesn't. And, looking from a different perspective, there's space to learn things one would not otherwise think in isolation.

Baby steps


This is notably quite long already... but closing with the third link, here goes something about the "size" of baby steps, something that people being introduced to TDD and baby steps are often asking, when they start to think that one ought to write senseless code when you know an obvious implementation. No, you do not need to write mindless code nor are you supposed to do that.

If you know how to do something, if it is obvious for you and you are comfortable, go ahead and do it. Stay in the flow. Now, when your instincts fail, there is the "fake until you make it" technique to keep you up in the game. Faking endlessly and thoughtlessly will not magically solve the problem for you, but gives you time to observe.
When overconfidence fails you, you can learn to know when to step back.

A whiteboard or a piece a paper, or an interactive interpreter, all might be equally good tools for fostering thinking.



We practice TDD and Baby Steps in coding dojos, though those are not magical techniques that solve all problems. The breath of domains we write programs for is so vast that no single technique could possibly be a silver bullet. Those two are certainly not sufficient in the toolbox of one aspiring to be a good programmer.

What are other useful techniques for you?

Tuesday, October 25, 2011

Saturday, January 17, 2009

TDD em cantigas de roda - parte 2

Na semana passada comecei a falar sobre abordar outros tipos de situação, não diretamente relacionadas com programação, através de testes, fazendo uma espécie de exercício de abstração.

Na última quarta, no Dojo Rio, eu conversei com o pessoal sobre o assunto. Ao invés de ter sido o aquecimento, como proposto pelo Falcão, foi apenas um bate-papo após a nossa retrospectiva. Como eu imaginava, estranharam um pouco, porém acho que consegui passar a idéia num tom sério o bastante para não me ignorarem :D

Voltando ao problema iniciado na parte 1, parei justamente após escrever um teste exercitando a presença de objetos em namespaces diferentes. Por enquanto temos o seguinte, com o último teste falhando:
# -*- coding: utf-8 -*-
u"""
paunogato.py - Implementação usando TDD da cantiga de roda "Atirei o Pau
no Gato", seguindo a idéia de Dojo Lúdico proposta por
Jorge Falcão.
Referências:
* http://lifeatmymind.blogspot.com/2009/01/tdd-em-cantigas-de-roda-parte-1.html
* http://lifeatmymind.blogspot.com/2009/01/tdd-em-cantigas-de-roda-parte-2.html

by Rodolfo Carvalho
2009/01/08
"""
import unittest

class DonaChica(object):
admirada = False

class Gato(object):
def berrar(self):
DonaChica.admirada = True

class Vila(object):
def adicionar(self, obj):
pass

class TestAtireiOPauNoGato(unittest.TestCase):
def test_admiracao(self):
dona_chica = DonaChica()
gato = Gato()

self.assertFalse(dona_chica.admirada)
gato.berrar()
self.assertTrue(dona_chica.admirada,
"A dona Chica nao se admirou com o berro do gato!")

def test_admiracao_com_outro_gato(self):
dona_chica = DonaChica()
gato = Gato()
outro_gato = Gato()
vila = Vila()
vila.adicionar(dona_chica)
vila.adicionar(gato)

self.assertFalse(dona_chica.admirada)
outro_gato.berrar()
self.assertFalse(dona_chica.admirada,
"A dona Chica admirou-se com o berro de outro gato!")

if __name__ == "__main__":
unittest.main()


Notem que eu adicionei o objeto Vila e seu método adicionar (vazio).

Agora vamos criar uma interface para disparar e capturar eventos. Primeiro vou definir um Ser, que seria uma abstração acima de DonaChica e Gato, e ajustar as heranças:

class Ser(object):
def __init__(self):
self._vila = None

def definir_vila(self, vila):
self._vila = vila

def notificar(self, evento):
print self, "recebeu o evento <%s>" % (evento,)

class DonaChica(Ser):
def __init__(self):
super(DonaChica, self).__init__()
self.__admirada = False

@property
def admirada(self):
return self.__admirada

def notificar(self, evento):
super(DonaChica, self).notificar(evento)
if evento == "berro":
self.__admirada = True

class Gato(Ser):
def berrar(self):
if self._vila is not None:
self._vila.notificar("berro")

class Vila(object):
def __init__(self):
self._seres = []

def adicionar(self, ser):
ser.definir_vila(self)
self._seres.append(ser)

def notificar(self, evento):
for ser in self._seres:
ser.notificar(evento)


Foi um passo bem longo. Tirei a idéia central daqui, porém no caso em questão todos os seres tem que ouvir os eventos de todos os outros, enquanto no exemplo da Wikipedia diversos Listeners ouvem eventos de apenas um único Subject.

Basicamente, o que aconteceu agora é que ao adicionar um ser (Gato, Dona Chica, etc) a uma vila este ser sabe a que vila ele pertence.
Assim sendo, quando um ser disparar um evento, ele avisa a vila, que por sua vez propaga o evento para todos os seres nela contidos.

Rodando os testes, temos uma supresa: nosso segundo teste test_admiracao_com_outro_gato passa, mas sofremos uma regressão — o test_admiracao falhou!
Por que será? Bem, uma coisa é certa, usando testes automatizados nós ganhamos a segurança de caminhar sem medo, pois estamos protegidos das regressões. O teste protege o código, e o que muitos não percebem: o código protege o teste!

O que aconteceu não é que o código está errado, e sim o teste. Desde que inventamos o conceito de vila, um gato e uma dona chica precisam estar na mesma vila para que um ouça o outro. Logo, precisamos consertar o primeiro teste para ficar bem parecido com o segundo:

    def test_admiracao(self):
dona_chica = DonaChica()
gato = Gato()
vila = Vila()
vila.adicionar(dona_chica)
vila.adicionar(gato)

self.assertFalse(dona_chica.admirada)
gato.berrar()
self.assertTrue(dona_chica.admirada,
"A dona Chica nao se admirou com o berro do gato!")


E finalmente estamos com tudo passando!

Tem mais um detalhe, o que acontece se quem berrar for a dona_chica? Ela não pode ser admirar com o próprio berro, mas sim com o berro de um gato!
Para ficar mais claro, não vou fazer a dona_chica berrar, mas sim adicionar um novo ser na brincadeira, um cachorro. Se um cachorro berrar, bem, a dona_chica continua no mesmo estado de não-admiração...


class Cachorro(Ser):
def berrar(self):
if self._vila is not None:
self._vila.notificar("berro")
...

def test_admiracao_com_um_cachorro(self):
dona_chica = DonaChica()
gato = Gato()
cachorro = Cachorro()
vila = Vila()
vila.adicionar(dona_chica, gato, cachorro)

self.assertFalse(dona_chica.admirada)
cachorro.berrar()
self.assertFalse(dona_chica.admirada,
"A dona Chica admirou-se com o berro do cachorro!")

Note que eu utilizei uma nova forma de adicionar seres a uma vila, e com isso precisamos refatorar:

class Vila(object):
def __init__(self):
self._seres = []

def adicionar(self, ser, *outros_seres):
ser.definir_vila(self)
self._seres.append(ser)
if outros_seres:
self._seres.extend(outros_seres)
...


E, para minha surpresa, os testes passaram! Isso quer dizer que tem algo de errado, pois eu esperava que o teste falhasse. Bem, não demorou muito para ver que eu não estava definindo a vila outros_seres. O refactoring ficou assim:

    def adicionar(self, *seres):
for ser in seres:
ser.definir_vila(self)
self._seres.append(ser)


Agora sim o teste falhou, menos mal :D
Vamos melhorar nossa comunicação de eventos, podemos passar a informação de quem está disparando o evento. Desta forma, podemos distinguir entre berros de gatos e cachorros...

class DonaChica(Ser):
...
def notificar(self, evento, origem):
super(DonaChica, self).notificar(evento, origem)
if evento == "berro" and isinstance(origem, Gato):
self.__admirada = True

E ajustamos para passar self quando cachorros e gatos berram:
class Gato(Ser):
def berrar(self):
if self._vila is not None:
self._vila.notificar("berro", self)

class Cachorro(Ser):
def berrar(self):
if self._vila is not None:
self._vila.notificar("berro", self)


Testes passando!
Podemos então refatorar. O que me incomoda bastante é a duplicação de código — as classes Cachorro e Gato tem o mesmo código! Não vejo razão para um ser qualquer não poder gritar... a não ser que seja de uma subclasse Mudo(Ser)...
Então:

class Ser(object):
...
def berrar(self):
if self._vila is not None:
self._vila.notificar("berro", self)

class Gato(Ser):
pass

class Cachorro(Ser):
pass


Testes OK.
Já estou bastante satisfeito em saber que, quando o gato berrar, dona Chica irá certamente se admirar...
Mas ainda quero poder atirar o pau no gato, sem causar sua morte...

Este post já está demasiadamente longo, então é melhor continuar depois.

Wednesday, January 7, 2009

TDD em cantigas de roda - parte 1

Bem, resolvi implementar minha própria cantiga usando TDD.
Pode parecer estranho, mas é apenas uma variação do que o Falcão fez e que eu comentei no post anterior.

Vamos então começar com a cantiga, "Atirei o Pau no Gato":

Atirei o pau no ga-to-to
mas o ga-to-to
não morreu-rreu-rreu
dona chica-ca
adimirou-se-se com o berro
que o gato deu
miiiiaaaaaaauuuuuuuu.


Olhada com os olhos certos, essa cantiga infantil de aparência simples, que vem sendo ensinada de forma alterada para evitar a extinção dos gatos domésticos, pode ser uma bela fonte para análise e discussão. Possivelmente minha abordagem não será a melhor, porém essa não é a idéia. É apenas a minha expressão de como encarei as coisa no ônibus voltando para casa...

O primeiro teste:
class TestAtireiOPauNoGato(unittest.TestCase):
def test_admiracao(self):
dona_chica = DonaChica()
gato = Gato()

self.assertFalse(dona_chica.admirada)
gato.berrar()
self.assertTrue(dona_chica.admirada)


E... hmm... precisamos de uma implementação para fazer os testes passarem! Então surge o primeiro "problema". Eu já escrevi os outros testes que acredito serem pertinentes a cantiga, mas não tinha escrito nada de implementação.
E o primeiro estalo foi: "o berro do gato tem que disparar um evento e a DonaChica é listener... quando o gato berra ela admira-se".

Como fazer essa comunicação? Veja que legal, chegamos num ponto — ainda bem no início da discussão — em que podemos aplicar um padrão de projeto (design pattern), o Observer pattern. Mais uma vez deixo claro que está é minha implementação, e faço o que quiser com ela: é minha!!!

Ah, mas... ok, antes de fazer algo super bonito, que tal fazermos o teste passar?

class DonaChica(object):
admirada = False

class Gato(object):
def berrar(self):
DonaChica.admirada = True


E rodando os testes: verde!
Eu não gosto desse código, alterar um atributo de classe não faz sentido, já que só uma instância de DonaChica, no caso dona_chica, ficou admirada.
Podemos explicitar isso fazendo mais um teste.

Queremos que possam existir diversas Donas Chicas espalhadas pelo mundo, ou em diferentes cidades, ou mesmo em dimensões distintas. O mesmo vale para os gatos... nada de singletons. Logo, para possibilitar diversos "namespaces" pensei em, ao invés de criar mais uma nível de objeto, e enfiar o gato e a dona_chica lá dentro, seria interessante ter uma Vila (vamos supor que nossa Dona Chica mora em uma, e lá existe ao menos um gato). A Vila nada mais é que o gerenciador de eventos, fazendo então a junção do gato e da dona Chica em um ambiente em que um pode interagir com o outro, e outros gatos, outras donas chicas, simplesmente não afetam/ são afetados.

Então vamos adicionar a dona_chica e o gato a uma Vila e descrever o que acontece caso um gato externo berre:

    def test_admiracao_com_outro_gato(self):
dona_chica = DonaChica()
gato = Gato()
outro_gato = Gato()
vila = Vila()
vila.adicionar(dona_chica)
vila.adicionar(gato)

self.assertFalse(dona_chica.admirada)
outro_gato.berrar()
self.assertFalse(dona_chica.admirada,
"A dona Chica admirou-se com o berro de outro gato!")


E como esperávamos, vermelho nos testes!
Vamos implementar:

Mas não agora, em breve!
Hora de dormir.

Dojo Lúdico

O Falcão falou ontem num tom muito engraçado sobre novas abordagens para o Coding Dojo.
Uma das melhores coisas do Dojo são as discussões geradas, debates sobre modelagem, sobre como abordar uma certa situação, entre outras.
O que ele inventou foi implementar cantigas populares usando TDD! É, pode até parece louco, mas faz sentido depois que acostumamos com a idéia. Ele postou sobre o assunto aqui.

O grande barato é que estamos trabalhando com elementos da língua falada, componentes sintáticos que exprimem condicionais, existência, conteúdo, etc. Não sou especialista em linguística, porém, dá pra notar que ter a presença de alguns nesse tipo de dojo seria bastante válido - ninguém precisa escrever uma linha de código!

Trabalhar exercícios que fogem totalmente do nosso cotidiano ajuda a treinar nosso poder de abstração. Podemos tentar fazer isso antes das sessões regulares do dojo, como uma forma de aquecimento.

Por hora, os exemplos do Falcão já mostram bem qual é a idéia, quem sabe um pouco mais descansado consigo pensar em coisas igualmente doidas e legais. Hora de pegar o ônibus pra casa, ótima oportunidade de dar uma viajada...

Thursday, December 4, 2008

Primeira sessão do Dojo Rio

Ontem tivemos a nossa primeira reunião do Dojo Rio [1], no CEFET [2].

Depois de várias semanas mobilizando pessoas, tentando arrumar um lugar adequado para o encontro, e muito papo via lista de discussão, finalmente conseguimos tirar o projeto do papel!

Apesar do horário marcado ter sido 18:30, ficamos aguardando até 19:00 antes de começar -- um atraso de certa forma previsto para um primeiro dia, já que as pessoas não se conheciam direito.
Colocamos um pequeno cartaz (uma folha A4 :P) indicando onde estávamos e avisamos os seguranças da entrada do CEFET para que direcionassem os dojeiros para o lugar certo. Espero que ninguém tenha se perdido por falta de informação.
Para ficar registrado, estávamos no Pavilhão 1 (Informática), na sala 1. Para chegar lá, basta, após entrar no CEFET, seguir reto até o fim do corredor para chegar no Pavillhão 1, que fica à esquerda, e subir a escada metálica logo na entrada do pavilhão.

Antes de começar usamos alguns minutos para nos conhecermos um pouquinho e então, às 19:00, começamos com uma introdução ao Coding Dojo, proferida por mim. Os slides estão aqui.
Os presentes:
  • Edino Moniz
  • Lucas Teixeira
  • Raphael Almeida
  • Rodolfo Carvalho
  • Valdir Monteiro
Finda a introdução, apresentei três problemas sugeridos para a sessão. Discutimos e resolvemos atacar o problema do Amigo Oculto, usando Python. A escolha da linguagem ficou por conta da simpatia que todos demonstraram em relação a ela e a minha familiaridade para poder ajudar o pessoal com os testes :D

Tivemos em torno de uma hora e quinze minutos para programar, fazendo turnos de sete minutos com cada par. Ao fim de um turno o co-piloto assumia o teclado, o piloto voltava para a platéia e alguém da platéia completava o par.

Nós usamos um pequeno script, o pydojo.py [3], para ajudar com os turnos. Encontrei esse programa no github do Dojo@SP, feito pelo pessoal da Async [4]. Trata-se de um ícone na bandeja do sistema com um contador. Ao término do turno ele dispara um alerta para trocar o par, e assim que a troca é feita a contagem reinicia. Fiz uma pequena modificação nele para aceitar o tempo de cada turno como parâmetro na linha de comando. Isso funcionou muito bem para a gente, e foi bem melhor que ficar controlando com cronômetro do relógio.

Estava previsto pararmos de programar 20:30, mas esticamos até 20:40, e depois tivemos nossa retrospectiva e discutimos alguns assuntos, como o próximo encontro (a ser confirmado para a próxima semana no mesmo local), e a seleção de linguagens e problemas.
Chegamos a um consenso de que funciona se as pessoas não tiverem acesso ao problema antes da hora, evitando que já cheguemos na sessão "treinados" para resolver. Então, por enquanto posso me comprometer em levar sugestões, e ficou também a idéia de montar um "banco de problemas". Bem, parece que o Dojo@SP já tem algo nesse sentido [5], que pode e deve servir de base para nós!

Voltando a retrospectiva, nossas idéias convergiram para:

:-)
  • Conhecer pessoas
  • Participação de todos
  • Nenhum imprevisto!
  • Python +
  • TDD

:-(
  • Nem todos dominavam a linguagem +
  • Atraso para começar (previsto...) +
  • Poucos presentes +
  • Teclado e mouse de notebook

Agora precisamos criar um blog para o DojoRio, e um repositório para nosso código.
Continuando com o esquema de votação que temos adotado, escolheremos as ferramentas de acordo com a pesquisa [6].

Já estamos listados no codingdojo.org [7], faltando escrever um texto de verdade para nossa página lá. Também estamos no mapa dos Dojos no mundo [8].

Ah, com fome, antes de nos separarmos e irmos para casa, saboreamos o delicioso espetinho de frango próximo a saída do CEFET.

E assim foi nossa noite. Semana que vem tem mais!


[1] http://groups.google.com/group/dojo-rio
[2] CEFET-RJ, Maracanã - mapa http://tinyurl.com/5pnhyk
[3] http://github.com/dojosp/participant-s-projects/tree/master/Python-Tests/pydojo.py
[4] http://www.async.com.br
[5] http://groups.google.com/group/dojo_sp/web/fontes-de-problemas
[6] http://spreadsheets.google.com/viewform?key=p_t4vVH1AGOyPJtorZ9gpJA
[7] http://codingdojo.org/cgi-bin/wiki.pl?CodingDojos
[8] http://maps.google.com/maps/ms?ie=UTF&msa=0&msid=116400871369678060090.000453a8d6ee3a6d3b8fe

A apresentação / introdução ao dojo:

Intro Dojo Rio
View SlideShare presentation or Upload your own. (tags: codingdojo tdd)

Sunday, October 5, 2008

Atividades introdutórias de XP em treinamentos

Depois de um post do Adam Wildavsky na lista de XP [1], resolvi guardar uma coleção de links para atividades que podem ser utilizadas para introduzir equipes ao Extreme Programming e Métodos Ágeis em geral, além de também servirem para treinar alguns conceitos e princípios.
Ótimo para "mexer no queijo" das pessoas, colocar uma pulga atrás da orelha e trazer mais gente para a reflexão sobre como desenvolvemos software.

Lá no mestrado do NCE já participei aplicando uma versão personalizada do XPGame, foi uma experência muito interessante. Espero que possamos fazer mais!

[1] extremeprogramming@yahoogroups.com

Update 2008/11/25: outra atividade interessante é a Dinâmica dos aviões
Update 2008/11/26: mais uma, Jogos Estatísticos

Saturday, October 4, 2008

Coding Dojo no Rio

Desde a PyCon Brasil 2008 (realizada aqui no Rio), no mês passado, que fiquei super empolgado em fazermos um Coding Dojo no Rio.
Como bem disse o Cláudio Berrondo, o Dojo é a "a peladinha semanal para programadores"!
Então por que não seguir os passos de nossos amigos paulistas e de outras regiões [5]?
Apesar de já ter lido sobre o assunto antes, minha primeira prática foi na PyCon, sob comando do Hugo Corbucci, que mandou muito bem por sinal.
Nosso Dojo da PyCon não foi gravado, mas tem um vídeo em que o Hugo explica direitinho qual é a idéia [1]. Outros links interessantes em [2] e [3].

Para conseguir juntar um pessoal, espalhei a notícia na PythOnRio, e na XPRio, além de divulgar para amigos da UFRJ. Alguns interessados se apresentaram, e agora só falta marcamos uma "sede".
Pensei a princípio na UFRJ, mas o problema lá é a dificuldade de transporte. Um local que se mostrou muito bom é a Universidade Veiga de Almeida, campus Tijuca, onde foi realizado a PyCon. O pessoal lá foi muito receptivo, e será muito bom se eles nos adotarem ;)

Para começar, além do local também precisamos de uma data. Para agilizar as coisas, criei o grupo Dojo Rio [4]. Todos os interessados são bem-vindos!
Vamos juntar forças com Pythonistas, Agilistas, XPeiros, e demais tribos, todos interessados em boas práticas de desenvolvimento de software, e porque não boas horas de diversão!

[1] http://video.google.com/videoplay?docid=-9174018775255486687
[2] http://www.agilbits.com.br/material/DojoIntroPyconBrasil2008.pdf
[3] http://codingdojo.org/
[4] http://groups.google.com/group/dojo-rio
[5] http://codingdojo.org/cgi-bin/wiki.pl?CodingDojos

Wednesday, August 13, 2008

Sobre Test-Driven Development

Ontem, no grupo de emails de XP, começou uma conversa sobre dados que suportem o uso de TDD nas grandes empresas. Os detalhes podem ser vistos em:
http://tech.groups.yahoo.com/group/extremeprogramming/message/144501

O que gostaria de deixar aqui são os links postados por Dave Nicolette, listando alguns estudos sobre o tema.

http://delicious.com/rhcarvalho/paper+tdd