Páginas

Showing posts with label designpattern. Show all posts
Showing posts with label designpattern. Show all posts

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.