Download - Aprenda bdd-jogando-dados-ebook
1
Aprenda BDD Jogando Dados!
SEU COMPORTAMENTO É SE DIVERTIR
Primeira Edição
Aprenda BDD Jogando Dados!
SEU COMPORTAMENTO É SE DIVERTIR
Primeira Edição
por Valério Farias de Carvalho
Por Valério Farias de Carvalho. Disponibilizado como Creative Commons Atribuição 2.5
Primeira edição: Julho de 2010
Valério Fariashttp://www.valeriofarias.comTwitter: @valeriofarias
Origem da imagem da capa: http://www.flickr.com/photos/missturner/
INTRODUÇÃO
Olá entusiasta do Ruby! Olá entusiasta de BDD! Bem vindo a essa jornada linha a linha, teste por teste,utilizando a técnica do Desenvolvimento Orientado a Comportamento (Behavior Driven Development) em umprojeto muito interessante: Simulação dos dados do jogo War!. Eu escrevi esse pequeno livro para aprenderBDD e RSpec, por isso eu tinha que falar sobre algo simples e de preferência que fosse divertido. Então eupensei: Por que não jogar dados! Porque não jogar dados do jogo War! Então aqui está: Aprenda BDDjogando dados!
A aplicação que nós criaremos juntos usa duas classes: Dice e WarDice. O primeiro capítulo eu começoconstruindo o arquivo RSpec da classe Dice e a classe Dice simultaneamente e passo a passo até todos ostestes tornarem-se verdes.
No segundo capítulo eu continuo o desenvolvimento com a classe WarDice, aquela que fará a simulação decada dado do jogo war!
Finalmente, no terceiro capítulo eu uso as classes criadas em uma divertida aplicação feita em shoes.
A filosofia desse livro é aprender fazendo coisas divertidas. Em uma palavra: Experimentação. Então euespero que você aproveite esse livro simples mas também muito instrutivo.
Os testes usados nesse livro não são uma solução definitiva. Eles são somente possibilidades no meio deoutras. Como eu disse, eu o escrevi para aprender RSpec. Você pode enviar sugestões, clonar o projeto,modificá-lo e codificá-lo de outra forma. Quem sabe não faremos juntos a versão 2.0 desse livro :).
Aprenda BDD Jogando Dados!
7
O código fonte completo da aplicação você pode encontrar no seguinte endereço: https://github.com/valeriofarias/shoes-war-dice/tree
Agora, apenas leia, codifique, teste, compartilhe e se divirta!
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
8
Capítulo 1
Fazendo a classe Dice usando BDD
INICIANDO
Antes de começarmos a programar vamos instalar os pacotes necessários. Primeiro instale o rspec:
gem install rspec
Se você quiser mais usabilidade, instale o pacote zentest:
gem install ZenTest
Agora instale o pacote autotest-notification. Essa gem configura o autotest (ZenTest) para enviar mensagenspara softwares como o Growl, LibNotify, and Snarl, mostrando uma janela com os resultados. Leia o arquivoREADME do projeto para saber como instalar: http://github.com/carlosbrando/autotest-notification/. Comessas três gems, nossa jornada se torna divertida!
Capítulo 1: Fazendo a classe Dice usando BDD
9
Para completar a bagagem, visite a página do Shoes e leia como instalar em seu sistema operacional. Sim,Shoes é muito divertido! Nós finalizaremos nossa brincadeira com ele. Acesse http://github.com/shoes/shoes/downloads ou http://github.com/shoes/shoes. Para aprender como usar e executar o Shoes leia oebook Nobody knows Shoes disponível em http://github.com/shoes/shoes/downloads.
Agora vamos começar nossa viagem teste a teste no mundo BDD!
CRIANDO O ARQUIVO RSPEC
Primeiro crie a pasta dice e dentro dela crie as pastas lib e spec. Agora crie o arquivo dice_spec.rb dentro dapasta spec. Bem! O que está esperando! Vamos escrever o primeiro requisito nesse arquivo. Agora começaa diversão!
requirerequire "rubygems"requirerequire "spec"
requirerequire "lib/dice"
describe Dice dodoit "deve ter números entre 1 e 6"
endend
Para executar esse teste abra o terminal, entre na pasta dice, depois digite o comando de teste:
cd dicespec spec//dice_spec
Em nosso exemplo com autotest, apenas digite o seguinte comando:
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
10
autospec
Agora o teste executa automaticamente toda vez que salvarmos o arquivo. Voltando para nosso exemplo. Asaída do primeiro teste quebra porque precisamos criar a classe Dice no arquivo lib/dice.rb.
spec ----autospec spec//dice_spec.rbrb.//spec//dice_spec.rbrb:6: uninitialized constant Dice (NameError)
ESCREVA A CLASSE DICE
Para resolver o erro inicial, apenas crie a classe Dice:
classclass Diceendend
A saída mostra o requisito pendente:
Pending:
Dice deve ter números entre 1 e 6 (Not Yet Implemented).//spec//dice_spec.rbrb:7
Finished inin 0.04099 seconds
1 example, 0 failures, 1 pending
OS NÚMEROS NO DADO
O primeiro requisito é delimitar a quantidade de números do dado: 1 a 6:
Capítulo 1: Fazendo a classe Dice usando BDD
11
describe Dice dodoit "deve ter números entre 1 e 6" dodo
dice == DiceDice.newnew(1..6).shouldshould includeinclude( dice.playplay )
endendendend
Saída:
F
1)NoMethodError inin 'Dice deve ter números entre 1 e 6'undefined method `play' for #<Dice:0xb7b6986c>./spec/dice_spec.rb:9:
Finished in 0.073369 seconds
1 example, 1 failure
CRIANDO O MÉTODO PLAY ARQUIVO DICE.RB
Para começar a resolver o problema anterior vamos escrever o método play:
classclass Dicedefdef playplayendend
endend
Saída: Ainda vai falhar pois o método play no exemplo anterior retorna nulo.
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
12
F
1)'Dice deve ter números entre 1 e 6' FAILEDexpected 1..6 to includeinclude nilnil.//spec//dice_spec.rbrb:9:
Finished inin 0.031104 seconds
1 example, 1 failure
NÚMERO FORA DO CONJUNTO 1-6
Por experimentação vamos colocar um número fora do conjunto 1-6 para ver o que acontece:
classclass Dicedefdef playplay
10endend
endend
Output: continua falhando, pois está fora do conjunto.
F
1)'Dice deve ter números entre 1 e 6' FAILEDexpected 1..6 to includeinclude 10.//spec//dice_spec.rbrb:9:
Finished inin 0.03021 seconds
Capítulo 1: Fazendo a classe Dice usando BDD
13
1 example, 1 failure
NÚMERO DENTRO DO CONJUNTO 1-6
Agora, vamos colocar um número entre 1-6 e finalmente a classe Dice passa no teste.
classclass Dicedefdef playplay
6endend
endend
Output:
.
Finished inin 0.027648 seconds
1 example, 0 failures
NÚMEROS ALEATÓRIOS
Por enquanto está Ok. Agora vamos trabalhar no requisito dos números aleatórios. Eu vou colocar algunsoutros requisitos que lembrei, mas não muitos. Vou colocar também a letra x no começo da declaração'it'para o rspec ignorá-la. Isso é apenas um truque ;).
requirerequire "rubygems"requirerequire "spec"
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
14
requirerequire "lib/dice"
describe Dice dodoit "deve ter números entre 1 e 6" dodo
dice == DiceDice.newnew(1..6).shouldshould includeinclude( dice.playplay )
endend
# Três grupos de 1000 números aleatórios devem ser diferentes um do outroit "deve mostrar números de forma aleatória" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
xit "deve armazenar número da última jogada."xit "deve jogar o dado no momento que o objeto for inicializado"
endend
Output:
.F
1)'Dice deve mostrar números de forma aleatória' FAILEDexpected falsefalse, got truetrue.//spec//dice_spec.rbrb:18:
Finished inin 0.093321 seconds
Capítulo 1: Fazendo a classe Dice usando BDD
15
2 examples, 1 failure
GERANDO NÚMERO ALEATÓRIO E REFATORANDO
Agora temos quer gerar números aleatórios e também temos que refatorar o primeiro requisito que delimita asaída entre os números 1 a 6. Esse requisito deve ser testado com uma variedade de números ao invés desomente um como está agora. Para brincar um pouco com Ruby também eu vou modificar o código dorequire para diminuir o número de linhas.
classclass Dicedefdef playplay
randrand(6)endend
endend
%w{ rubygems spec lib/dice }.eacheach{|lib| requirerequire lib }
describe Dice dodoit "deve ter somente números entre 1 e 6" dodo
dice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.should_notshould_not be_nilgroup.should_notshould_not be_emptygroup.should_notshould_not includeinclude('-') # Números negativos não são permitidosgroup.should_notshould_not includeinclude('0')group.should_notshould_not includeinclude('7')group.should_notshould_not includeinclude('8')group.should_notshould_not includeinclude('9')
endend
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
16
# Três grupos de 1000 números aleatórios devem ser diferentes um do outroit "deve mostrar números de forma aleatória" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
xit "deve armazenar número da última jogada."xit "deve jogar o dado no momento que o objeto for inicializado"
endend
Output: rand(6) gera também o número zero, portanto o teste falha.
F.
1)'Dice deve ter somente números entre 1 e 6' FAILEDexpected "40251322225105400021423125552351044325205220224304451434252545153314510153043001251012005523244142243512333204035042444130240534042050050324205500223120330524430331015422350203015044053545205524012055023101003331405204353205410102441530220034031430225503404511243223354504315335402445045511" notnot to includeinclude "0".//spec//dice_spec.rbrb:10:
Finished inin 0.046589 seconds
2 examples, 1 failure
Capítulo 1: Fazendo a classe Dice usando BDD
17
RESOLVENDO A FALHA DO NÚMERO ALEATÓRIO
Finalmente vamos ajeitar o comando para rand(6) + 1. Dessa forma limita a saída entre 1 e 6. Vamosaproveitar para refatorar o primeiro requisito para usar expressões regulares. Agora o teste passa :).
classclass Dicedefdef playplay
randrand(6) ++ 1endend
endend
%w{ rubygems spec lib/dice }.eacheach {|lib| requirerequire lib}
describe Dice dodoit "deve ter somente números entre 1 e 6" dodo
dice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Três grupos de 1000 números aleatórios devem ser diferentes um do outroit "deve mostrar números de forma aleatória" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
xit "deve armazenar número da última jogada."
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
18
xit "deve jogar o dado no momento que o objeto for inicializado"endend
ARMAZENANDO O NÚMERO DO DADO
Vamos trabalhar no próximo requisito: "deve armazenar número da última jogada.". Eu criarei o métodoshow_number e colocarei uma constante para o teste passar.
%w{rubygems spec lib/dice}.eacheach {|lib| requirerequire lib}
describe Dice dodo
it "deve ter somente números entre 1 e 6" dododice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Três grupos de 1000 números aleatórios devem ser diferentes um do outroit "deve mostrar números de forma aleatória" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
it "deve armazenar número da última jogada." dododice == DiceDice.newnewdice.playplay
Capítulo 1: Fazendo a classe Dice usando BDD
19
dice.show_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)endend
xit "deve jogar o dado no momento que o objeto for inicializado"endend
classclass Dicedefdef playplay
randrand(6) ++ 1endend
defdef show_numbershow_number3
endendendend
TROCANDO CONSTANTE POR VARIÁVEL
Essa é uma regra importante no BDD. Agora você pode mudar a constante por uma variável de instância naclasse dados. O teste continua passando.
classclass Dicedefdef playplay
@number == randrand(6) ++ 1endend
defdef show_numbershow_number@number
endendendend
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
20
JOGAR O DADO NA INICIALIZAÇÃO DO OBJETO
Agora eu quero que o dado seja jogado quando ele for inicializado. O próximo teste quebrará.
%w{rubygems spec lib/dice}.eacheach {|lib| requirerequire lib}
describe Dice dodo
it "deve ter somente números entre 1 e 6" dododice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Três grupos de 1000 números aleatórios devem ser diferentes um do outroit "deve mostrar números de forma aleatória" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
it "deve armazenar número da última jogada." dododice == DiceDice.newnewdice.playplaydice.show_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
it "deve jogar o dado na inicialização do objeto." dodoDiceDice.newnew.show_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)
Capítulo 1: Fazendo a classe Dice usando BDD
21
endend
endend
Output:
...F
1)'Dice deve jogar o dado na inicialização do objeto.' FAILEDexpected "" to match /^[1-6]*[1-6]$/.//spec//dice_spec.rbrb:29:
Finished inin 0.12371 seconds
4 examples, 1 failure
USANDO O MÉTODO INITIALIZEINITIALIZE
Agora eu finalizo a classe Dice colocando o método initialize com uma mensagem para o método play. Oteste passa.
classclass Dicedefdef initializeinitialize
playendend
defdef playplay@number == randrand(6) ++ 1
endend
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
22
defdef show_numbershow_number@number
endendendend
VAMOS REFATORAR
Agora eu posso fazer uma pequena refatoração no arquivo dice_spec.rb. Eu colocarei um blocobefore(:each) para simplificar o código. Farei também uma refatoração no terceiro requisito: "devearmazenar número da última jogada". Agora ele funciona com uma grande quantidade de números. A lógicaé se o último número é armazenado então dois grupos de 100 números são diferentes entre si.
%w{rubygems spec lib/dice}.eacheach {|lib| requirerequire lib}
describe Dice dodobeforebefore(::eacheach) dodo
@dice == DiceDice.newnewendend
it "deve ter somente números entre 1 e 6" dodogroup == (1..1_000).collectcollect{ @dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Três grupos de 1000 números aleatórios devem ser diferentes um do outroit "deve mostrar números de forma aleatória" dodo
group1 == (1..1_000).collectcollect{ @dice.playplay }group2 == (1..1_000).collectcollect{ @dice.playplay }group3 == (1..1_000).collectcollect{ @dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false
Capítulo 1: Fazendo a classe Dice usando BDD
23
(group2 ==== group3).shouldshould be_falseendend
it "deve armazenar número da última jogada." dodogroup1 == (1..100).collectcollect dodo
@[email protected]_numbershow_number
endendgroup2 == (1..100).collectcollect dodo
@[email protected]_numbershow_number
endend(group1 ==== group2).shouldshould be_false
endend
it "deve jogar o dado na inicialização do objeto." [email protected]_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
endend
BRINCANDO COM A CLASSE DICE
Agora que a classe foi finalizada vamos brincar com ela no irb!
>>>> requirerequire 'dice'=> truetrue>>>> dice == DiceDice.newnew=> #<Dice:0xb7a64188 @number=5>>>>> dice.show_numbershow_number=> 5
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
24
>>>> dice.classclass=> Dice
Jogando o dado somente uma vez e deixando ele de lado:
>>>> DiceDice.newnew.show_numbershow_number=> 2
Jogar o dado várias vezes:
>>>> 20.timestimes{ print dice.playplay }21636456135236136236=> 20
Três dados:
>>>> yellowdice == [DiceDice.newnew, DiceDice.newnew, DiceDice.newnew]=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]>>>> yellowdice.eacheach{ |dice| puts dice.show_numbershow_number }355=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]
Jogar novamente o mesmo dado
>>>> yellowdice.eacheach{ |dice| puts dice.playplay }525=> [#<Dice:0xb7a3e3d4 @number=5>, #<Dice:0xb7a3e3ac @number=2>, #<Dice:0xb7a3e384 @number=5>]
Qual o maior valor entre os três dados da última jogada?
Capítulo 1: Fazendo a classe Dice usando BDD
25
>>>> puts yellowdice.collectcollect{ |item| item.show_numbershow_number }.maxmax5=> nilnil
E qual o menor valor?
>>>> puts yellowdice.collectcollect{ |item| item.show_numbershow_number }.minmin2=> nilnil
Espere um pouco. No último exemplo eu usei 3 dados?!? Ahaaa! Isso parece com os dados do jogo War.Nós criaremos os dados do jogo war no próximo capítulo.
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
26
Capítulo 2
Reproduzindo os dados do jogo War
DESCRIÇÃO DO JOGO WAR
Finalmente terminei a classe Dice! Mas eu quero algo mais excitante! Eu quero reproduzir em uma classeRuby os dados do jogo War. Isso mesmo! Aqueles 6 dados. 3 vermelhos e 3 amarelos que representamataque e defesa respectivamente.
Eu usarei dois arrays: reddice e yellowdice para armazenar os valores dos dados. Lembrei agora que o usodos dados depende do número de exércitos do atacante e do defensor. Mas nesse experimento eu pensareisomente na manipulação dos dados no jogo. Eles podem ser manuseados com 1, 2 ou 3 dados vermelhoscontra 1, 2 ou 3 dados amarelos. Eu terei que comparar o maior valor dos dados vermelhos com o maiorvalor entre os dados amarelos e assim sucessivamente com os demais dados (do maior para o menor valor).Em caso de empate o amarelo vence. O vermelho só vence quando o número for maior que o amarelo.
Capítulo 2: Reproduzindo os dados do jogo War
27
Bem, eu acho que é só. Vamos agir agora!
TESTANDO COMPARAÇÃO DE ARRAYS
Nos primeiros testes que eu fiz em meu computador, tive problemas com comparação de arrays. Paralelo aisso me lembrei que é imprescindível o uso de comparação de arrays na classe WarDice. Vamos incluir umteste de comparação de arrays no arquivo dice_spec.rb e verificar o comportamento:
describe Array dodoit "deve ser comparável" dodo
([8,9,10] >> [1,2,3]).shouldshould be_trueendend
endend
Output:
....F
1)NoMethodError inin 'Array deve ser comparável'undefined method `>' for [8, 9, 10]:Array./spec/dice_spec.rb:43:
Finished in 0.039464 seconds
5 examples, 1 failure
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
28
INCLUINDO O MÓDULO COMPARABLECOMPARABLE
Para solucionar o problema anterior, apenas inclua o módulo comparable no arquivo dice.rb. Eu farei issousando uma forma simplificada para brincar com as possibilidades do Ruby :).
classclass Array; includeinclude Comparable; endend
Agora o teste passa.
NÚMERO DE DADOS
Agora vou trabalhar no primeiro requisito: permite a utilização de 1, 2 or 3 dados, para o ataque oudefesa.. Na classe WarDice eu vou colocar de propósito um número diferente de 1, 2, 3 para gerar um erro.
describe Wardice dodoit "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." dodo
wardice == WardiceWardice.newnew(0, 3)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
endend
it "compara os valores do maior para o menor e salva em um array"endend
classclass Wardiceattr_readerattr_reader ::redred, ::yellowyellow
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow
Capítulo 2: Reproduzindo os dados do jogo War
29
endendendend
Output:
'Wardice permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa.' FAILEDexpected "0" to match /^[1-3]$/.//spec//dice_spec.rbrb:50:
Finished inin 0.032203 seconds
8 examples, 1 failure, 2 pending
RESOLVENDO O PROBLEMA DOS NÚMEROS
Lógica: Se a classe for inicializada com números diferentes da lista 1-3 então as variáveis serão preenchidascom um número aleatório variando de 1 a 3. Eu farei também uma pequena refatoração no arquivo specpara que ele fique preparado para a situação em que a quantidade de dados for correta. Você pode ver asolução abaixo:
classclass WarDiceattr_readerattr_reader ::redred, ::yellowyellow
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
endendendend
describe WarDice dodo
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
30
it "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." dodowardice == WardiceWardice.newnew(0, 7)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
wardice2 == WardiceWardice.newnew(2, 3)wardice2.redred.shouldshould ==== 2wardice2.yellowyellow.shouldshould ==== 3
endend
it "compara os valores do maior para o menor e salva em um array"endend
ARRAY EM ORDEM DECRESCENTE
Esse requisito é muito importante, mas só lembrei dele agora: wardice deve fornecer os resultados dosdados vermelho e amarelo em arrays em ordem decrescente. Você pode ver a solução abaixo:
describe WarDice dodoit "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." dodo
wardice == WarDiceWarDice.newnew(0, 7)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
wardice2 == WarDiceWarDice.newnew(2, 3)wardice2.redred.shouldshould ==== 2wardice2.yellowyellow.shouldshould ==== 3
endend
it "deve fornecer os resultados dos dados vermelho e amarelo em arrays em ordem decrescente" dodowardice == WardiceWardice.newnew(3, 3)wardice.reddicereddice.is_a?is_a?(Array).shouldshould be_true
Capítulo 2: Reproduzindo os dados do jogo War
31
wardice.yellowdiceyellowdice.is_a?is_a?(Array).shouldshould be_true
wardice.reddicereddice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.reddicereddicewardice.yellowdiceyellowdice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.yellowdiceyellowdice
endend
it "compara os valores do maior para o menor e salva em um array"
endend
classclass wardiceattr_readerattr_reader ::redred, ::yellowyellow, ::reddicereddice, ::yellowdiceyellowdice
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
@reddice == []@yellowdice == []
@dice == [email protected]{|row| @reddice[row] == [@dice.playplay] }@yellow.timestimes{ |row| @yellowdice[row] == [@dice.playplay] }
endendendend
Output:
'wardice deve fornecer os resultados dos dados vermelho e amarelo em arrays em ordem decrescente' FAILEDexpected: [[5], [2], [4]],
got: [[5], [4], [2]] (using ====).//spec//dice_spec.rbrb:63:
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
32
Finished inin 0.035218 seconds
9 examples, 1 failure, 2 pending
SOLUCIONANDO O PROBLEMA DO ARRAY EM ORDEM DECRESCENTEclassclass WarDice
attr_readerattr_reader ::redred, ::yellowyellow, ::reddicereddice, ::yellowdiceyellowdice
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
@reddice == []@yellowdice == []
@dice == [email protected]{|row| @reddice[row] == [@dice.playplay] }@yellow.timestimes{ |row| @yellowdice[row] == [@dice.playplay] }
@reddice.sort!sort!{|x,y| y <=><=> x }@yellowdice.sort!sort!{|x,y| y <=><=> x }
endendendend
Agora o teste passa.
Capítulo 2: Reproduzindo os dados do jogo War
33
COMPARANDO OS VALORES
Vamos trabalhar no último requisito: compara os valores do maior para o menor e salva em um array.
describe Wardice dodoit "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." dodo
wardice == WardiceWardice.newnew(0, 7)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
wardice2 == WardiceWardice.newnew(2, 3)wardice2.redred.shouldshould ==== 2wardice2.yellowyellow.shouldshould ==== 3
endend
it "deve fornecer os resultados dos dados vermelho e amarelo em arrays em ordem decrescente" dodowardice == WardiceWardice.newnew(3, 3)wardice.reddicereddice.is_a?is_a?(Array).shouldshould be_truewardice.yellowdiceyellowdice.is_a?is_a?(Array).shouldshould be_true
wardice.reddicereddice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.reddicereddicewardice.yellowdiceyellowdice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.yellowdiceyellowdice
endend
it "compara os valores do maior para o menor e salva em um array" dodowardice == WardiceWardice.newnew(3, 2)wardice.reddicereddice.firstfirst.shouldshould >> wardice.reddicereddice.lastlastwardice.attackattack
wardice.resultresult[0].shouldshould ==== "Red Win" ifif wardice.reddicereddice[0] >> wardice.yellowdiceyellowdice[0]wardice.resultresult[0].shouldshould ==== "Yellow Win" ifif wardice.reddicereddice[0] <=<= wardice.yellowdiceyellowdice[0]
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
34
endendendend
A classe WarDice com os testes passando por completo:
classclass Wardiceattr_readerattr_reader ::redred, ::yellowyellow, ::reddicereddice, ::yellowdiceyellowdice, ::resultresult
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
@reddice == []@yellowdice == []@result == []
@dice == [email protected]{|row| @reddice[row] == [@dice.playplay] }@yellow.timestimes{ |row| @yellowdice[row] == [@dice.playplay] }
@reddice.sort!sort!{|x, y| y <=><=> x }@yellowdice.sort!sort!{|x, y| y <=><=> x }
endend
defdef [email protected]_with_indexeach_with_index dodo |item, index|
nextnext ifif @yellowdice[index].nil?nil?reddice == itemyellowdice == @yellowdice[index]
ifif reddice >> yellowdice@result <<<< "Vermelho Venceu!"
elseelse
Capítulo 2: Reproduzindo os dados do jogo War
35
@result <<<< "Amarelo Venceu!"endend
endendendend
endend
BRINCANDO COM A CLASSE WARDICE
Abra o irb e digite a sequência abaixo:
>>>> requirerequire 'dice'=> truetrue
Inicialize a classe WarDice. O primeiro parâmetro é o número de dados vermelhos e o segundo é o númerode dados amarelos:
>>>> wardice == WarDiceWarDice.newnew(2, 3)=> #<WarDice:0xb7b0b410 @yellowdice=[[6], [1], [1]], @reddice=[[3], [1]], @dice=#<Dice:0xb7b0ae0c@number==6>>, yellow3, result["Yellow Win", "Yellow Win"], red2
Mostra o números nos dados amarelos e vermelhos:
>>>> puts wardice.reddicereddice31=> nilnil>>>> puts wardice.yellowdiceyellowdice611=> nilnil
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
36
Mostra o resultado:
>>>> puts wardice.resultresultYellow WinYellow Win=> nilnil
A saída completa em um único comando:
>>>> war.reddicereddice.each_with_indexeach_with_index{ |item, index| puts "Red:#{item} Yellow:#{war.yellowdiceyellowdice[index]}- #{war.resultresult[index]}"}Red:3 Yellow:6 -- Yellow WinRed:1 Yellow:1 -- Yellow Win=> [[3], [1]]
Agora já temos as ferramentas para brincar. Então no próximo capítulo nós usaremos essas classes parafazer uma bela aplicação gráfica usando Shoes.
Capítulo 2: Reproduzindo os dados do jogo War
37
Capítulo 3
Jogando dados com Shoes
UM PEQUENO TESTE COM A CLASSE DICE E SHOES
Nesse capítulo vamos criar uma interface gráfica usando Shoes e a classe WarDice, mas primeiro vamosfazer um simples teste com a classe Dice. Você deve criar o arquivo diceshoes.rb, então cole o códigoabaixo dentro dele e salve dentro do mesmo diretório do arquivo dice.rb. Finalmente, execute ele com oShoes para ver o resultado.
Observe que eu apenas inclui o arquivo dice.rb com um require no começo do código abaixo:
requirerequire 'dice'ShoesShoes.appapp ::titletitle => "Teste com a classe Dice", ::widthwidth => 500, ::heightheight => 500 dodo
background aliceblue
para "Bem Vindo! Esse é um exemplo de uso da classe Dice.", ::weightweight => "bold"
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
38
dice == DiceDice.newnew
# Imprime na tela os números dos dados jogados com cores aleatórias1_000.timestimes dodo
r, g, b == rand, rand, randpara dice.playplay, ::strokestroke => rgbrgb(r****3, g****3, b****3), ::sizesize => "large"
endendendend
Saída:
Capítulo 3: Jogando dados com Shoes
39
O CÓDIGO SHOES DO APLICATIVO QUE USA A CLASSE WARDICE
Finalmente a última aplicação. copie o código seguinte em um novo arquivo e salve com o nomewardiceshoes.rb na pasta lib. Eu fiz alguns comentários através do código para facilitar a compreensão.Faça um bom experimento.
requirerequire 'dice'
ShoesShoes.appapp ::titletitle => "Dados do Jogo War", ::widthwidth => 500, ::heightheight => 500 dodobackground gradientgradient( black, teal )
# Listbox com a quantidade de dados vermelhos: 1, 2 or 3para "Red", ::strokestroke => tomato@numberreddice == list_box ::itemsitems => ["1", "2", "3"],
::widthwidth => 70, ::choosechoose => "3" dodo |list|endend
para " X ", ::strokestroke => snow
# Listbox com a quantidade de dados amarelos: 1, 2 or 3@numberyellowdice == list_box ::itemsitems => ["1", "2", "3"],
::widthwidth => 70, ::choosechoose => "3" dodo |list|endendpara "Yellow", ::strokestroke => yellow
# Define uma posição aleatória@a == @b == @c == []
(40..200).stepstep(10){ |x| @a <<<< x }(230..450).stepstep(10){ |y| @b <<<< y }(80..450).stepstep(10){ ||z|| @c <<<< z }
# Variáveis que vão armazenar os valores do objeto wardice.
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
40
@reddice == @yellowdice == @resulttext == []
button "Attack", ::widthwidth => 80 dodo
# Limpa a [email protected]{ |d| d.removeremove }@resulttext.eacheach{ |a| a.removeremove }
# O objeto wardice is inicializadowardice == WarDiceWarDice.newnew( @numberreddice.texttext.to_ito_i, @numberyellowdice.texttext.to_ito_i )@reddice == wardice.reddicereddice@yellowdice == wardice.yellowdiceyellowdice@result == wardice.resultresult
# Cada dado é desenhado em uma posição aleató[email protected]{ |item| drawdraw( @a[randrand(@a.lengthlength)], @c[randrand(@c.lengthlength)], item.to_sto_s.to_ito_i, 1, truetrue ) }@yellowdice.eacheach{ |item| drawdraw( @b[randrand(@b.lengthlength)], @c[randrand(@c.lengthlength)], item.to_sto_s.to_ito_i, 2, truetrue )}
endend
button "Verify", ::widthwidth => 80 dodo
# Limpa a [email protected]{ |d| d.removeremove }@resulttext.eacheach{ |a| a.removeremove }
# posição inicial dos dadosleftyellow == 250leftred == 150topred == topyellow == 100
# Cada dado é desenhado em uma posição [email protected] dodo |item|
drawdraw( leftred, topred, item.to_sto_s.to_ito_i, 1, falsefalse )
Capítulo 3: Jogando dados com Shoes
41
topred +=+= 100endend
@yellowdice.eacheach dodo |item|drawdraw( leftyellow, topyellow, item.to_sto_s.to_ito_i, 2, falsefalse )topyellow +=+= 100
endend
# Posição inicial para a lista com o resultadoleftresult == 300topresult == 80
# Os resultados são impressos na tela em uma posição [email protected]_with_indexeach_with_index dodo |item, index|
@resulttext[index] == para item.to_sto_s, ::strokestroke => snow, ::toptop => topresult, ::leftleft => leftresulttopresult +=+= 100
endend
endend
# O método draw foi baseado no projeto Pretty Dice, escrito por Ed Heil@dice == []
defdef drawdraw( left, top, number, color, rotate )
imagewidth == 60imageheight == 60
i == imageimage( imagewidth, imageheight,::toptop => top -- imagewidth // 2,::leftleft => left -- imagewidth // 2,::shadowshadow => 10, ::centercenter => truetrue ) dodo
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
42
ifif color ==== 1strokecolor == redfillrectanglecolor == tomatofilldotscolor == darkred
elseelsestrokecolor == yellowfillrectanglecolor == lightgoldenrodyellowfilldotscolor == chocolate
endend
sw == 1strokewidth swstroke strokecolor
fill fillrectanglecolor
inset == 2inset2 == 8
rectrect( inset, inset, imagewidth--inset--sw, imageheight--inset--sw, 10 )
fill filldotscolor
ovalradius == 10low == inset2high == imagewidth -- inset2 -- ovalradiusmid == ( imagewidth -- ovalradius ) // 2
ovaloval( mid, mid, ovalradius ) ifif number %% 2 ==== 1
ifif number >> 1ovaloval( low, low, ovalradius )ovaloval( high, high, ovalradius )
endend
Capítulo 3: Jogando dados com Shoes
43
ifif number >> 3ovaloval( low, high, ovalradius )ovaloval( high, low, ovalradius )
endend
ifif number >> 5ovaloval( mid, low, ovalradius )ovaloval( mid, high, ovalradius )
endend
endend # fim do bloco `image`
i.rotaterotate( randrand( 359 ) ) ifif rotate
@dice <<<< i
endend
endend
BRINCANDO COM O APLICATIVO
Abaixo você pode ver o aplicativo em ação. Você deve escolher o número de dados vermelhos e amarelosno respectivo listbox. Depois clique no botão ataque para jogar os dados. Agora é só se divertir!
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
44
Agora clique no botão verificar para saber quem venceu!
Capítulo 3: Jogando dados com Shoes
45
Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
46