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:
Notem que eu adicionei o objeto
Agora vamos criar uma interface para disparar e capturar eventos. Primeiro vou definir um
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
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:
E finalmente estamos com tudo passando!
Tem mais um detalhe, o que acontece se quem berrar for a
Para ficar mais claro, não vou fazer a
Note que eu utilizei uma nova forma de adicionar seres a uma vila, e com isso precisamos refatorar:
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
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...
E ajustamos para passar
Testes passando!
Podemos então refatorar. O que me incomoda bastante é a duplicação de código — as classes
Então:
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.
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.
No comments:
Post a Comment