{"id":14,"date":"2006-09-30T19:10:40","date_gmt":"2006-09-30T18:10:40","guid":{"rendered":"http:\/\/www.dtsato.com\/blog\/default\/Agile\/XP\/?permalink=TDD-em-Combate-Parte-3.wiki"},"modified":"2006-09-30T19:10:40","modified_gmt":"2006-09-30T18:10:40","slug":"tdd-em-combate-parte-3","status":"publish","type":"post","link":"https:\/\/www.dtsato.com\/blog\/2006\/09\/30\/tdd-em-combate-parte-3\/","title":{"rendered":"TDD em Combate &#8211; Parte 3"},"content":{"rendered":"<p>Apesar da demora, o desenvolvimento do jogo continuou. Neste post falaremos um pouco sobre a implementa\u00e7\u00e3o das regras de movimenta\u00e7\u00e3o, mostrando como novos conceitos surgem com a evolu\u00e7\u00e3o natural do design e com a constante preocupa\u00e7\u00e3o com a clareza e a simplicidade.<\/p>\n<h3 class=\"heading-1\">Refatorando um pouco antes de seguir em frente<\/h3>\n<p>Desde o <span class=\"nobr\"><a href=\"http:\/\/www.dtsato.com\/blog\/default\/XP\/?permalink=TDD-em-Combate-Parte-2.html&amp;smm=y\">\u00faltimo post<\/a><\/span>, muitas coisas aconteceram (dentre elas minha ida ao <span class=\"nobr\"><a href=\"http:\/\/www.dtsato.com\/blog\/default\/Agile\/?permalink=Agile-2006-Dia-0-Dia-1.wiki&amp;smm=y\">Agile 2006<\/a><\/span>) e aprendi muitas coisas. Uma das mais importantes descobertas foi a relev\u00e2ncia dos nomes dos testes. Ap\u00f3s ficar um tempo sem mexer no c\u00f3digo do jogo, olhei para os nomes dos m\u00e9todos de teste e n\u00e3o entendi o que eles faziam. Precisei olhar para o c\u00f3digo para entender. \u00c9 muito importante que o nome dos m\u00e9todos de teste evidenciem n\u00e3o apenas o cen\u00e1rio do teste, mas tamb\u00e9m as expectativas sobre o comportamento que est\u00e1 sendo testado. Quando um teste falhar no futuro, se o nome do teste for bom, voc\u00ea n\u00e3o precisar\u00e1 olhar o c\u00f3digo para entender o problema. Existe inclusive uma ferramenta Java muito simples por\u00e9m muito poderosa, o <span class=\"nobr\"><a href=\"http:\/\/agiledox.sourceforge.net\">Agiledox<\/a><\/span>, que transforma os nomes dos m\u00e9todos de uma classe de teste JUnit numa frase, criando uma documenta\u00e7\u00e3o simples a partir do c\u00f3digo.<\/p>\n<p>Al\u00e9m disso, pensando na legibilidade do c\u00f3digo de teste, resolvemos criar uma subclasse especial da pe\u00e7a, com m\u00e9todos auxiliares que verificam o comportamento esperado numa vit\u00f3ria, num empate e numa derrota. Por fim, descobrimos um pequeno erro nos testes com exce\u00e7\u00f5es esperadas: era preciso chamar self.fail() ao inv\u00e9s de fail() para utilizar o m\u00e9todo da superclasse unittest.TestCase (desculpem minha falta de conhecimento de Python, ainda estou aprendendo). O c\u00f3digo de teste refatorado ficou assim:<\/p>\n<pre lang=\"python\" highlight=\"8,9,10,11,12,13,14,47,51,53,55,57,59,61,68,75\">\n# StrategoTest.py\n\nimport unittest\nimport unittestgui\nimport Stratego\nfrom Stratego import InvalidPiece, InvalidAttack\n\nclass Piece(Stratego.Piece):\n    def winsAgainst(self, defendingPiece):\n        return self.attack(defendingPiece) > 0\n    def losesAgainst(self, defendingPiece):\n        return self.attack(defendingPiece) < 0\n    def tiesWith(self, defendingPiece):\n        return self.attack(defendingPiece) == 0\n\nclass CreatePieceTest(unittest.TestCase):\n    def testCreateSoldierWithRank2(self):\n        assert Piece(\"soldier\").rank == 2\n    def testCreateMinerWithRank3(self):\n        assert Piece(\"miner\").rank == 3\n    def testCreateSergeantWithRank4(self):\n        assert Piece(\"sergeant\").rank == 4\n    def testCreateLieutenantWithRank5(self):\n        assert Piece(\"lieutenant\").rank == 5\n    def testCreateCaptainWithRank6(self):\n        assert Piece(\"captain\").rank == 6\n    def testCreateMajorWithRank7(self):\n        assert Piece(\"major\").rank == 7\n    def testCreateColonelWithRank8(self):\n        assert Piece(\"colonel\").rank == 8\n    def testCreateGeneralWithRank9(self):\n        assert Piece(\"general\").rank == 9\n    def testCreateMarshalWithRank10(self):\n        assert Piece(\"marshal\").rank == 10\n    def testCreateSpyWithRank1(self):\n        assert Piece(\"spy\").rank == 1\n    def testCreateBombWithRank11(self):\n        assert Piece(\"bomb\").rank == 11\n    def testCreateFlagWithRank0(self):\n        assert Piece(\"flag\").rank == 0\n    def testCreateInvalidPieceRaises(self):\n        try:\n            Piece(\"invalid\")\n        except InvalidPiece:\n            pass\n        else:\n            self.fail(\"expected InvalidPiece exception\")\n\nclass AttackTest(unittest.TestCase):\n    def testHigherRankWins(self):\n        assert Piece(\"sergeant\").winsAgainst(Piece(\"soldier\"))\n    def testLowerRankLoses(self):\n        assert Piece(\"miner\").losesAgainst(Piece(\"colonel\"))\n    def testEqualRankTies(self):\n        assert Piece(\"major\").tiesWith(Piece(\"major\"))\n    def testSpyWinsAgainstMarshal(self):\n        assert Piece(\"spy\").winsAgainst(Piece(\"marshal\"))\n    def testMarshalWinsAgainstSpy(self):\n        assert Piece(\"marshal\").winsAgainst(Piece(\"spy\"))\n    def testMinerWinsAgainstBomb(self):\n        assert Piece(\"miner\").winsAgainst(Piece(\"bomb\"))\n    def testBombCannotAtack(self):\n        try:\n            Piece(\"bomb\").attack(Piece(\"miner\"))\n        except InvalidAttack:\n            pass\n        else:\n            self.fail(\"expected InvalidAttack exception\")\n    def testFlagCannotAtack(self):\n        try:\n            Piece(\"flag\").attack(Piece(\"lieutenant\"))\n        except InvalidAttack:\n            pass\n        else:\n            self.fail(\"expected InvalidAttack exception\")\n\n# (...) Main Method\n<\/pre>\n<p>\u00a0<br \/>\nComo uma li\u00e7\u00e3o final aprendida com o erro acima, devemos sempre olhar o motivo de um teste estar vermelho. Nesse caso, ele ficava vermelho devido ao erro de sintaxe, pois o interpretador Python n\u00e3o encontrava o m\u00e9todo fail() quando a exce\u00e7\u00e3o n\u00e3o era lan\u00e7ada. Ao fazer o teste passar lan\u00e7ando a exce\u00e7\u00e3o esperada, fazemos com que o teste nunca caia na chamada ao m\u00e9todo fail(). Por ser uma linguagem din\u00e2mica, o interpretador de Python n\u00e3o reclama pois nem precisa executar a linha errada.<\/p>\n<h3 class=\"heading-1\">Evoluindo o design para implementar a movimenta\u00e7\u00e3o<\/h3>\n<p>Conforme discutimos no <span class=\"nobr\"><a href=\"http:\/\/www.dtsato.com\/blog\/default\/XP\/?permalink=TDD-em-Combate-Parte-1.html&amp;smm=y\">primeiro post<\/a><\/span>, as regras de movimenta\u00e7\u00e3o s\u00e3o simples: todas as pe\u00e7as podem dar 1 passo em qualquer dire\u00e7\u00e3o vertical ou horizontal, exceto o Soldado (que pode andar mais que 1 passo), a Bomba e a Bandeira (que n\u00e3o podem andar).<\/p>\n<p>\u00c9 hora de pensarmos um pouco sobre o design para implementar a movimenta\u00e7\u00e3o. Uma primeira id\u00e9ia era fazer a pe\u00e7a guardar sua posi\u00e7\u00e3o atual no tabuleiro e representar um movimento com uma chamada a um m\u00e9todo que receberia a posi\u00e7\u00e3o final da pe\u00e7a. O problema com essa abordagem \u00e9 que a pe\u00e7a ficaria com muito mais responsabilidade que ela deveria. Uma pe\u00e7a precisa saber sobre coordenadas do tabuleiro? Como o tabuleiro saberia qual pe\u00e7a est\u00e1 em qual posi\u00e7\u00e3o na hora de verificar movimentos inv\u00e1lidos ou situa\u00e7\u00f5es de ataque? Pensando nas depend\u00eancias entre os objetos, nos parece muito mais sensato o tabuleiro conhecer as pe\u00e7as. Por\u00e9m, conforme j\u00e1 verificamos, algumas pe\u00e7as possuem regras especiais de movimenta\u00e7\u00e3o. Ent\u00e3o, apesar da pe\u00e7a n\u00e3o precisar saber sobre coordenadas, precisaremos de alguma forma perguntar para a pe\u00e7a se o \"tamanho\" do movimento que estamos tentando realizar \u00e9 v\u00e1lido.<\/p>\n<p>Foi nessa hora que tivemos um daqueles momentos \"Eureka\": \"Podemos representar o movimento como um vetor geom\u00e9trico aplicado \u00e0 um ponto num plano cartesiano\". Com isso, podemos calcular a magnitude do vetor e perguntar para a pe\u00e7a se aquele movimento \u00e9 de  \"tamanho\" v\u00e1lido. Infelizmente, TDD n\u00e3o ensina como ter esses momentos de <em class=\"italic\">insight<\/em>, mas todos n\u00f3s sabemos que programar \u00e9 uma atividade que requer criatividade e treino. Na minha opini\u00e3o, duas pr\u00e1ticas de XP s\u00e3o o segredo para facilitar o surgimento desses momentos: <strong>Trabalho Energizado<\/strong> (antigamente chamado de Ritmo Sustent\u00e1vel ou Semana de 40 Horas) e <strong>Programa\u00e7\u00e3o Pareada<\/strong>. Quantas vezes voc\u00ea j\u00e1 n\u00e3o ficou trabalhando at\u00e9 tarde em cima do mesmo problema e, ao chegar na manh\u00e3 seguinte, descobre a solu\u00e7\u00e3o trivial? Al\u00e9m disso, \u00e9 incr\u00edvel a quantidade de vezes que me surpreendi em discuss\u00f5es sobre um problema espec\u00edfico. Muitas vezes a discord\u00e2ncia entre os pares termina com o surgimento de uma nova solu\u00e7\u00e3o, muito melhor do que todas as outras pensadas anteriormente (e que originaram a discuss\u00e3o).<\/p>\n<p>Uma pequena revis\u00e3o da aula de geometria e vetores: um vetor num espa\u00e7o euclidiano de <em class=\"italic\">n<\/em> dimens\u00f5es pode ser representado como uma combina\u00e7\u00e3o linear de <em class=\"italic\">n<\/em> vetores unit\u00e1rios e ortonormais (tamb\u00e9m conhecidos como <em class=\"italic\">base<\/em>). Num espa\u00e7o bi-dimensional, essa base \u00e9 formada pelos vetores geradores dos eixos <em class=\"italic\">x<\/em> e <em class=\"italic\">y<\/em>. Sendo <strong>i<\/strong> e <strong>j<\/strong> os vetores unit\u00e1rios paralelos aos eixos <em class=\"italic\">x<\/em> e <em class=\"italic\">y<\/em> respectivamente, podemos representar qualquer outro vetor <em class=\"italic\">a<\/em> nesse espa\u00e7o 2D como uma tupla (a<sub>1<\/sub>, a<sub>2<\/sub>) onde <em class=\"italic\">a<\/em> = a<sub>1<\/sub><strong>i<\/strong> + a<sub>2<\/sub><strong>j<\/strong>.<\/p>\n<p>Como os movimentos no jogo s\u00e3o apenas na vertical ou na horizontal, criamos o que chamamos de vetores ortogonais. Vetores ortogonais devem ter pelo menos um componente nulo, seja na vertical ou na horizontal. Com isso j\u00e1 podemos continuar com os pr\u00f3ximos testes.<\/p>\n<h3 class=\"heading-1\">Vetores num tabuleiro 2D<\/h3>\n<p>Primeiramente vamos fazer um teste para verificar a cria\u00e7\u00e3o de um vetor horizontal. Para isso, criamos uma nova classe de teste e a inclu\u00edmos na suite de testes:<\/p>\n<pre lang=\"python\" highlight=\"9\">\n# StrategoTest.py\n\nclass OrthogonalVectorTest(unittest.TestCase):\n    def testCreateHorizontalVector(self):\n        assert OrthogonalVector(42, 0).y == 0\n\nsuite = unittest.TestSuite((unittest.makeSuite(CreatePieceTest),\n                            unittest.makeSuite(AttackTest),\n                            unittest.makeSuite(OrthogonalVectorTest)\n                          ))\n# (...) Main Method\n<\/pre>\n<p>Para fazer esse teste passar, basta criar uma nova classe OrthogonalVector e import\u00e1-la no m\u00f3dulo de teste:<\/p>\n<pre lang=\"python\">\n# Stratego.py\n\nclass OrthogonalVector:\n    def __init__(self, x, y):\n        self.y = 0\n<\/pre>\n<p>A implementa\u00e7\u00e3o para os pr\u00f3ximos dois testes segue o mesmo racioc\u00ednio, ent\u00e3o vou pular os passos intermedi\u00e1rios e mostrar os novos testes no verde:<\/p>\n<pre lang=\"python\" highlight=\"6,7,8,9,10,11,12,13,14,20,21,22\">\n# StrategoTest.py\n\nclass OrthogonalVectorTest(unittest.TestCase):\n    def testCreateHorizontalVector(self):\n        assert OrthogonalVector(42, 0).y == 0\n    def testCreateVerticalVector(self):\n        assert OrthogonalVector(0, 42).x == 0\n    def testNonOrthogonalVectorRaises(self):\n        try:\n            OrthogonalVector(1, 2)\n        except InvalidVector:\n            pass\n        else:\n            self.fail(\"expected InvalidVector exception\")\n\n# Stratego.py - (...)\n\nclass OrthogonalVector:\n    def __init__(self, x, y):\n        if 0 not in [x, y]:\n            raise InvalidVector\n        self.x = 0\n        self.y = 0\n# (...)\n\nclass InvalidVector(Exception):\n    pass\n<\/pre>\n<p>Nosso vetor ainda n\u00e3o armazena os valores esperados no construtor. Lembrem-se que a implementa\u00e7\u00e3o para deixar o teste verde deve ser r\u00e1pida. O pr\u00f3ximo teste que falha verifica o tamanho de um vetor com componentes positivas:<\/p>\n<pre lang=\"python\">\n# StrategoTest.py - (...) class OrthogonalVectorTest\n\n    def testCalculatePositiveMagnitude(self):\n        assert OrthogonalVector(3, 0).magnitude() == 3\n# (...)\n<\/pre>\n<p>A implementa\u00e7\u00e3o ainda n\u00e3o pede pelo armazenamento dos componentes do vetor, uma vez que podemos devolver uma constante no novo m\u00e9todo:<\/p>\n<pre lang=\"python\">\n# Stratego.py - (...) class OrthogonalVector\n\n    def magnitude(self):\n        return 3\n# (...)\n<\/pre>\n<p>O pr\u00f3ximo teste para calcular o tamanho de um vetor com componentes negativos finalmente pede pela implementa\u00e7\u00e3o que armazena os valores dos componentes no construtor:<\/p>\n<pre lang=\"python\" highlight=\"3,4,11,12,14\">\n# StrategoTest.py - (...) class OrthogonalVectorTest\n\n    def testCalculateNegativeMagnitude(self):\n        assert OrthogonalVector(0, -42).magnitude() == 42\n\n# Stratego.py - (...) class OrthogonalVector\n\n    def __init__(self, x, y):\n        if 0 not in [x, y]:\n            raise InvalidVector\n        self.x = x\n        self.y = y\n    def magnitude(self):\n        return abs(self.x + self.y)\n# (...)\n<\/pre>\n<p>Essa t\u00e9cnica de implementa\u00e7\u00e3o que acabamos de utilizar \u00e9 chamada de <strong>triangula\u00e7\u00e3o<\/strong>. Para aplic\u00e1-la, voc\u00ea deve escrever mais de um teste com exemplos diferentes a respeito do mesmo comportamento. Por exemplo, num m\u00e9todo que soma 2 + 2, para evitar que o resultado seja sempre 4, voc\u00ea pode escrever outro teste que verifica se soma(3, 4) \u00e9 igual a 7. Reparem tamb\u00e9m que nossa implementa\u00e7\u00e3o n\u00e3o \u00e9 completamente correta. Aqueles que estudaram geometria devem estar pensando: \"Por que estamos calculando a norma do vetor como a soma de seus componentes?\", quando na verdade deveria ser a ra\u00edz da soma dos quadrados dos componentes. Lembrem-se da import\u00e2ncia do <span class=\"nobr\"><a href=\"http:\/\/www.dtsato.com\/blog\/default\/XP\/?permalink=Design-Incremental-em-XP.wiki&amp;smm=y\">design simples<\/a><\/span> e evolutivo. N\u00e3o tentem incluir complexidade al\u00e9m da necess\u00e1ria. No nosso caso particular, como um dos componentes \u00e9 sempre nulo por constru\u00e7\u00e3o, podemos considerar que a norma do vetor \u00e9 a soma dos componentes.<br \/>\n\u00a0<\/p>\n<h3 class=\"heading-1\">As pe\u00e7as aprendem o quanto podem se movimentar<\/h3>\n<p>Agora que j\u00e1 temos uma implementa\u00e7\u00e3o simples de vetores, podemos come\u00e7ar a pensar nas regras que verificam o quanto cada pe\u00e7a pode se mover. Para isso, escrevemos o nosso pr\u00f3ximo teste que verifica a regra geral de movimenta\u00e7\u00e3o, implementando o m\u00ednimo para faz\u00ea-lo passar. Por padr\u00e3o, todas as pe\u00e7as podem dar 1 passo em qualquer dire\u00e7\u00e3o:<\/p>\n<pre lang=\"python\" highlight=\"3,4,5,10,16,17\">\n# StrategoTest.py\n\nclass MovementTest(unittest.TestCase):\n    def testSergeantCanMoveOneStep(self):\n        assert Piece(\"sergeant\").canMove(1)\n\nsuite = unittest.TestSuite((unittest.makeSuite(CreatePieceTest),\n                            unittest.makeSuite(AttackTest),\n                            unittest.makeSuite(OrthogonalVectorTest),\n                            unittest.makeSuite(MovementTest)\n                           ))\n# (...) Main Method\n\n# Stratego.py - (...) class Piece\n\n    def canMove(self, steps):\n        return True\n\n# (...) OrthogonalVector\/Exceptions\n<\/pre>\n<p>Os pr\u00f3ximos dois testes garantem que a bomba e a bandeira n\u00e3o podem andar. Mais uma vez vou mostrar os testes e a implementa\u00e7\u00e3o, agora que voc\u00eas j\u00e1 entenderam o ritmo de TDD. Primeiro vemos o teste falhar, depois implementamos o m\u00ednimo para faz\u00ea-lo passar para depois refatorar e melhorar o <em class=\"italic\">design<\/em>:<\/p>\n<pre lang=\"python\" highlight=\"3,4,5,6,12,13\">\n# StrategoTest.py - (...) class MovementTest\n\n   def testBombCannotMove(self):\n        assert not Piece(\"bomb\").canMove(1)\n    def testFlagCannotMove(self):\n        assert not Piece(\"flag\").canMove(1)\n# (...)\n\n# Stratego.py - (...) class Piece\n\n    def canMove(self, steps):\n        if self.rank in [11, 0]:\n            return False\n        return True\n# (...) OrthogonalVector\/Exceptions\n<\/pre>\n<p>Por enquanto a implementa\u00e7\u00e3o ainda n\u00e3o est\u00e1 completa. Precisamos de um teste para garantir que uma pe\u00e7a normal n\u00e3o pode dar mais que 1 passo:<\/p>\n<pre lang=\"python\" highlight=\"3,4\">\n# StrategoTest.py - (...) class MovementTest\n\n    def testMajorCannotMoveMoreThanOneStep(self):\n        assert not Piece(\"major\").canMove(2)\n# (...)\n<\/pre>\n<p>A implementa\u00e7\u00e3o para fazer o teste passar pode ser feia a princ\u00edpio...<\/p>\n<pre lang=\"python\" highlight=\"4\">\n# Stratego.py - (...) class Piece\n\n    def canMove(self, steps):\n        if self.rank in [11, 0] or (self.rank == 7 and steps > 1):\n            return False\n        return True\n# (...)\n<\/pre>\n<p>\u2026 contanto que a refatora\u00e7\u00e3o seja feita depois que a barra de testes est\u00e1 verde. Nesse caso, resolvemos criar uma classe auxiliar para armazenar as propriedades de uma pe\u00e7a (por enquanto o rank e o n\u00famero m\u00e1ximo de passos que ela pode dar), modificando o dicion\u00e1rio de inicializa\u00e7\u00e3o, o construtor e o m\u00e9todo canMove():<\/p>\n<pre lang=\"python\" highlight=\"3,4,5,6,9,10,11,12,13,14,15,16,17,18,19,20,26,27,33,34\">\n# Stratego.py\n\nclass PieceProperties:\n    def __init__(self, rank, maxSteps=1):\n        self.rank = rank\n        self.maxSteps = maxSteps\n\nclass Piece:\n    __pieces = {\"flag\": PieceProperties(0, 0),\n                \"spy\": PieceProperties(1),\n                \"soldier\": PieceProperties(2),\n                \"miner\": PieceProperties(3),\n                \"sergeant\": PieceProperties(4),\n                \"lieutenant\": PieceProperties(5),\n                \"captain\": PieceProperties(6),\n                \"major\": PieceProperties(7),\n                \"colonel\": PieceProperties(8),\n                \"general\": PieceProperties(9),\n                \"marshal\": PieceProperties(10),\n                \"bomb\": PieceProperties(11, 0)}\n    __winsAttacking = [(1,10), (3,11)]\n    __cannotAttack = [0,11]\n\n    def __init__(self, name):\n        try:\n            self.rank = self.__pieces[name].rank\n            self.maxSteps = self.__pieces[name].maxSteps\n        except KeyError:\n            raise InvalidPiece()\n\n    # (...) attack() Method\n\n    def canMove(self, steps):\n        return steps <= self.maxSteps\n\n# (...) OrthogonalVector\/Exceptions\n<\/pre>\n<p>O pr\u00f3ximo teste verifica a \u00faltima exce\u00e7\u00e3o: o soldado pode dar quantos passos quiser, numa mesma dire\u00e7\u00e3o. Para a implementa\u00e7\u00e3o, resolvemos utilizar um valor negativo, que significa \"n\u00e3o h\u00e1 n\u00famero m\u00e1ximo de passos para essa pe\u00e7a\":<\/p>\n<pre lang=\"python\" highlight=\"3,4,9,13\">\n# StrategoTest.py - (...) class MovementTest\n\n    def testSoldierCanMoveMoreThanOneStep(self):\n        assert Piece(\"soldier\").canMove(42)\n# Stratego.py - (...) class Piece\n\n    __pieces = {\"flag\": PieceProperties(0, 0),\n                \"spy\": PieceProperties(1),\n                \"soldier\": PieceProperties(2,-1),\n                \"miner\": PieceProperties(3), # (...)\n\n    def canMove(self, steps):\n        return steps <= self.maxSteps or self.maxSteps == -1\n\n# (...) OrthogonalVector\/Exceptions\n<\/pre>\n<p>O \u00faltimo teste verifica o cen\u00e1rio de erro numa tentativa de andar passos negativos. A implementa\u00e7\u00e3o \u00e9 simples: criamos uma nova exce\u00e7\u00e3o e verificamos o erro no m\u00e9todo canMove():<\/p>\n<pre lang=\"python\" highlight=\"3,4,5,6,7,8,9,14,15,20,21\">\n# StrategoTest.py - (...) class MovementTest\n\n    def testMoveNegativeStepsRaises(self):\n        try:\n            Piece(\"lieutenant\").canMove(-1)\n        except IllegalStep:\n            pass\n        else:\n            self.fail(\"expected IllegalStep exception\")\n\n# Stratego.py - (...) class Piece\n\n    def canMove(self, steps):\n        if steps <= 0:\n            raise IllegalStep\n        return steps <= self.maxSteps or self.maxSteps == -1\n\n# (...) OrthogonalVector\/Exceptions\n\nclass IllegalStep(Exception):\n    pass\n<\/pre>\n<h3 class=\"heading-1\">Conclus\u00f5es e pr\u00f3ximos passos<\/h3>\n<p>Terminamos o terceiro post com 123 linhas de c\u00f3digo de teste e 63 linhas de c\u00f3digo de produ\u00e7\u00e3o. Uma verifica\u00e7\u00e3o de cobertura de c\u00f3digo nos mostra que estamos 100% cobertos. Esse \u00e9 um dos \"efeitos colaterais\" de TDD: al\u00e9m de ganhar confian\u00e7a no c\u00f3digo que est\u00e1 escrevendo, voc\u00ea ainda termina com uma bateria de testes que cobre 100% do seu c\u00f3digo. No pr\u00f3ximo post iremos evoluir nossos vetores para implementar as regras de movimenta\u00e7\u00e3o no tabuleiro. A seguir, um resumo do que aprendemos nesse post e o c\u00f3digo final:<br \/>\n\u00a0<\/p>\n<ul class=\"star\">\n<li>Conhecemos a import\u00e2ncia dos nomes que damos aos testes<\/li>\n<li>Aprendemos a verificar o que est\u00e1 errado quando estamos no vermelho<\/li>\n<li>Conversamos um pouco sobre depend\u00eancias e divis\u00e3o de responsabilidades entre objetos<\/li>\n<li>Discutimos outras pr\u00e1ticas de XP, como Trabalho Energizado e Programa\u00e7\u00e3o Pareada<\/li>\n<li>Implementamos uma classe auxiliar para representar vetores e facilitar nossa implementa\u00e7\u00e3o das regras de movimenta\u00e7\u00e3o<\/li>\n<\/ul>\n<p><em class=\"italic\">Atualiza\u00e7\u00e3o 03-Out-06<\/em>: Conforme sugest\u00f5es, estou disponibilizando para download o c\u00f3digo fonte final dos <span class=\"nobr\"><a href=\"http:\/\/www.dtsato.com\/resources\/default\/code_sample\/tdd_3\/StrategoTest.py\">testes<\/a><\/span> e das <span class=\"nobr\"><a href=\"http:\/\/www.dtsato.com\/resources\/default\/code_sample\/tdd_3\/Stratego.py\">classes de produ\u00e7\u00e3o<\/a><\/span> para os interessados n\u00e3o precisarem copiar\/colar\/formatar tudo novamente.<\/p>\n<pre lang=\"python\">\n# StrategoTest.py\n\nimport unittest\nimport unittestgui\nimport Stratego\nfrom Stratego import InvalidPiece, InvalidAttack, InvalidVector, IllegalStep\nfrom Stratego import OrthogonalVector\n\nclass Piece(Stratego.Piece):\n    def winsAgainst(self, defendingPiece):\n        return self.attack(defendingPiece) > 0\n    def losesAgainst(self, defendingPiece):\n        return self.attack(defendingPiece) < 0\n    def tiesWith(self, defendingPiece):\n        return self.attack(defendingPiece) == 0\n\nclass CreatePieceTest(unittest.TestCase):\n    def testCreateSoldierWithRank2(self):\n        assert Piece(\"soldier\").rank == 2\n    def testCreateMinerWithRank3(self):\n        assert Piece(\"miner\").rank == 3\n    def testCreateSergeantWithRank4(self):\n        assert Piece(\"sergeant\").rank == 4\n    def testCreateLieutenantWithRank5(self):\n        assert Piece(\"lieutenant\").rank == 5\n    def testCreateCaptainWithRank6(self):\n        assert Piece(\"captain\").rank == 6\n    def testCreateMajorWithRank7(self):\n        assert Piece(\"major\").rank == 7\n    def testCreateColonelWithRank8(self):\n        assert Piece(\"colonel\").rank == 8\n    def testCreateGeneralWithRank9(self):\n        assert Piece(\"general\").rank == 9\n    def testCreateMarshalWithRank10(self):\n        assert Piece(\"marshal\").rank == 10\n    def testCreateSpyWithRank1(self):\n        assert Piece(\"spy\").rank == 1\n    def testCreateBombWithRank11(self):\n        assert Piece(\"bomb\").rank == 11\n    def testCreateFlagWithRank0(self):\n        assert Piece(\"flag\").rank == 0\n    def testCreateInvalidPieceRaises(self):\n        try:\n            Piece(\"invalid\")\n        except InvalidPiece:\n            pass\n        else:\n            self.fail(\"expected InvalidPiece exception\")\n\nclass AttackTest(unittest.TestCase):\n    def testHigherRankWins(self):\n        assert Piece(\"sergeant\").winsAgainst(Piece(\"soldier\"))\n    def testLowerRankLoses(self):\n        assert Piece(\"miner\").losesAgainst(Piece(\"colonel\"))\n    def testEqualRankTies(self):\n        assert Piece(\"major\").tiesWith(Piece(\"major\"))\n    def testSpyWinsAgainstMarshal(self):\n        assert Piece(\"spy\").winsAgainst(Piece(\"marshal\"))\n    def testMarshalWinsAgainstSpy(self):\n        assert Piece(\"marshal\").winsAgainst(Piece(\"spy\"))\n    def testMinerWinsAgainstBomb(self):\n        assert Piece(\"miner\").winsAgainst(Piece(\"bomb\"))\n    def testBombCannotAtack(self):\n        try:\n            Piece(\"bomb\").attack(Piece(\"miner\"))\n        except InvalidAttack:\n            pass\n        else:\n            self.fail(\"expected InvalidAttack exception\")\n    def testFlagCannotAtack(self):\n        try:\n            Piece(\"flag\").attack(Piece(\"lieutenant\"))\n        except InvalidAttack:\n            pass\n        else:\n            self.fail(\"expected InvalidAttack exception\")\n\nclass OrthogonalVectorTest(unittest.TestCase):\n    def testCreateHorizontalVector(self):\n        assert OrthogonalVector(42, 0).y == 0\n    def testCreateVerticalVector(self):\n        assert OrthogonalVector(0, 42).x == 0\n    def testNonOrthogonalVectorRaises(self):\n        try:\n            OrthogonalVector(1, 2)\n        except InvalidVector:\n            pass\n        else:\n            self.fail(\"expected InvalidVector exception\")\n    def testCalculatePositiveMagnitude(self):\n        assert OrthogonalVector(3, 0).magnitude() == 3\n    def testCalculateNegativeMagnitude(self):\n        assert OrthogonalVector(0, -42).magnitude() == 42\n\nclass MovementTest(unittest.TestCase):\n    def testSergeantCanMoveOneStep(self):\n        assert Piece(\"sergeant\").canMove(1)\n    def testBombCannotMove(self):\n        assert not Piece(\"bomb\").canMove(1)\n    def testFlagCannotMove(self):\n        assert not Piece(\"flag\").canMove(1)\n    def testMajorCannotMoveMoreThanOneStep(self):\n        assert not Piece(\"major\").canMove(2)\n    def testSoldierCanMoveMoreThanOneStep(self):\n        assert Piece(\"soldier\").canMove(42)\n    def testMoveNegativeStepsRaises(self):\n        try:\n            Piece(\"lieutenant\").canMove(-1)\n        except IllegalStep:\n            pass\n        else:\n            self.fail(\"expected IllegalStep exception\")\n\nsuite = unittest.TestSuite((unittest.makeSuite(CreatePieceTest),\n                            unittest.makeSuite(AttackTest),\n                            unittest.makeSuite(OrthogonalVectorTest),\n                            unittest.makeSuite(MovementTest)\n                           ))\n\nif __name__ == \"__main__\":\n    unittestgui.main(\"StrategoTest.suite\")\n\n# Stratego.py\n\nclass PieceProperties:\n    def __init__(self, rank, maxSteps=1):\n        self.rank = rank\n        self.maxSteps = maxSteps\n\nclass Piece:\n    __pieces = {\"flag\": PieceProperties(0, 0),\n                \"spy\": PieceProperties(1),\n                \"soldier\": PieceProperties(2,-1),\n                \"miner\": PieceProperties(3),\n                \"sergeant\": PieceProperties(4),\n                \"lieutenant\": PieceProperties(5),\n                \"captain\": PieceProperties(6),\n                \"major\": PieceProperties(7),\n                \"colonel\": PieceProperties(8),\n                \"general\": PieceProperties(9),\n                \"marshal\": PieceProperties(10),\n                \"bomb\": PieceProperties(11, 0)}\n    __winsAttacking = [(1,10), (3,11)]\n    __cannotAttack = [0,11]\n\n    def __init__(self, name):\n        try:\n            self.rank = self.__pieces[name].rank\n            self.maxSteps = self.__pieces[name].maxSteps\n        except KeyError:\n            raise InvalidPiece()\n\n    def attack(self, defender):\n        if self.rank in self.__cannotAttack:\n            raise InvalidAttack()\n        if (self.rank,defender.rank) in self.__winsAttacking:\n            return 1\n        else:\n            return self.rank - defender.rank\n\n    def canMove(self, steps):\n        if steps <= 0:\n            raise IllegalStep\n        return steps <= self.maxSteps or self.maxSteps == -1\n\nclass OrthogonalVector:\n    def __init__(self, x, y):\n        if 0 not in [x, y]:\n            raise InvalidVector\n        self.x = x\n        self.y = y\n    def magnitude(self):\n        return abs(self.x + self.y)\n\nclass InvalidPiece(Exception):\n    pass\n\nclass InvalidAttack(Exception):\n    pass\n\nclass InvalidVector(Exception):\n    pass\n\nclass IllegalStep(Exception):\n    pass\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Apesar da demora, o desenvolvimento do jogo continuou. Neste post falaremos um pouco sobre a implementa\u00e7\u00e3o das regras de movimenta\u00e7\u00e3o, mostrando como novos conceitos surgem com a evolu\u00e7\u00e3o natural do design e com a constante preocupa\u00e7\u00e3o com a clareza e a simplicidade. Refatorando um pouco antes de seguir em frente Desde o \u00faltimo post, muitas [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[58,80,82],"class_list":["post-14","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-python","tag-stratego","tag-tdd"],"_links":{"self":[{"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/posts\/14","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/comments?post=14"}],"version-history":[{"count":1,"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/posts\/14\/revisions"}],"predecessor-version":[{"id":238,"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/posts\/14\/revisions\/238"}],"wp:attachment":[{"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/media?parent=14"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/categories?post=14"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dtsato.com\/blog\/wp-json\/wp\/v2\/tags?post=14"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}