5. Aprenda BDD Jogando Dados!
SEU COMPORTAMENTO É SE DIVERTIR
Primeira Edição
por Valério Farias de Carvalho
6. Por Valério Farias de Carvalho. Disponibilizado como Creative Commons Atribuição 2.5
Primeira edição: Julho de 2010
Valério Farias
http://www.valeriofarias.com
Twitter: @valeriofarias
Origem da imagem da capa: http://www.flickr.com/photos/missturner/
7. Aprenda BDD Jogando Dados!
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 um
projeto muito interessante: Simulação dos dados do jogo War!. Eu escrevi esse pequeno livro para aprender
BDD e RSpec, por isso eu tinha que falar sobre algo simples e de preferência que fosse divertido. Então eu
pensei: Por que não jogar dados! Porque não jogar dados do jogo War! Então aqui está: Aprenda BDD
jogando dados!
A aplicação que nós criaremos juntos usa duas classes: Dice e WarDice. O primeiro capítulo eu começo
construindo o arquivo RSpec da classe Dice e a classe Dice simultaneamente e passo a passo até todos os
testes tornarem-se verdes.
No segundo capítulo eu continuo o desenvolvimento com a classe WarDice, aquela que fará a simulação de
cada 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 eu
espero 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 de
outras. 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 :).
7
8. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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!
8
9. Capítulo 1: Fazendo a classe Dice usando BDD
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 mensagens
para softwares como o Growl, LibNotify, and Snarl, mostrando uma janela com os resultados. Leia o arquivo
README do projeto para saber como instalar: http://github.com/carlosbrando/autotest-notification/. Com
essas três gems, nossa jornada se torna divertida!
9
10. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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 o
ebook 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 da
pasta spec. Bem! O que está esperando! Vamos escrever o primeiro requisito nesse arquivo. Agora começa
a diversão!
require "rubygems"
require "spec"
require "lib/dice"
describe Dice do
it "deve ter números entre 1 e 6"
end
Para executar esse teste abra o terminal, entre na pasta dice, depois digite o comando de teste:
cd dice
spec spec/dice_spec
/
Em nosso exemplo com autotest, apenas digite o seguinte comando:
10
11. Capítulo 1: Fazendo a classe Dice usando BDD
autospec
Agora o teste executa automaticamente toda vez que salvarmos o arquivo. Voltando para nosso exemplo. A
saída do primeiro teste quebra porque precisamos criar a classe Dice no arquivo lib/dice.rb.
spec --autospec spec/dice_spec.rb
/ rb
./spec/dice_spec.rb
/ / rb:6: uninitialized constant Dice (NameError)
ESCREVA A CLASSE DICE
Para resolver o erro inicial, apenas crie a classe Dice:
class Dice
end
A saída mostra o requisito pendente:
Pending:
Dice deve ter números entre 1 e 6 (Not Yet Implemented)
./spec/dice_spec.rb
/ / rb:7
Finished in 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:
11
12. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
describe Dice do
it "deve ter números entre 1 e 6" do
dice = Dice new
Dice.new
(1..6).should include dice.play )
should include( play
end
end
Saída:
F
1)
NoMethodError in '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:
class Dice
def play
end
end
Saída: Ainda vai falhar pois o método play no exemplo anterior retorna nulo.
12
13. Capítulo 1: Fazendo a classe Dice usando BDD
F
1)
'Dice deve ter números entre 1 e 6' FAILED
expected 1..6 to include nil
./spec/dice_spec.rb
/ / rb:9:
Finished in 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:
class Dice
def play
10
end
end
Output: continua falhando, pois está fora do conjunto.
F
1)
'Dice deve ter números entre 1 e 6' FAILED
expected 1..6 to include 10
./spec/dice_spec.rb
/ / rb:9:
Finished in 0.03021 seconds
13
14. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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.
class Dice
def play
6
end
end
Output:
.
Finished in 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 alguns
outros 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 ;).
require "rubygems"
require "spec"
14
15. Capítulo 1: Fazendo a classe Dice usando BDD
require "lib/dice"
describe Dice do
it "deve ter números entre 1 e 6" do
dice = Dice new
Dice.new
(1..6).should include dice.play )
should include( play
end
# Três grupos de 1000 números aleatórios devem ser diferentes um do outro
it "deve mostrar números de forma aleatória" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
xit "deve armazenar número da última jogada."
xit "deve jogar o dado no momento que o objeto for inicializado"
end
Output:
.F
1)
'Dice deve mostrar números de forma aleatória' FAILED
expected false got true
false,
./spec/dice_spec.rb
/ / rb:18:
Finished in 0.093321 seconds
15
16. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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 a
saída entre os números 1 a 6. Esse requisito deve ser testado com uma variedade de números ao invés de
somente um como está agora. Para brincar um pouco com Ruby também eu vou modificar o código do
require para diminuir o número de linhas.
class Dice
def play
rand
rand(6)
end
end
%w{ rubygems spec lib/dice }.each
each{|lib| require lib }
describe Dice do
it "deve ter somente números entre 1 e 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should_not be_nil
should_not
group.should_not be_empty
should_not
should_not include('-') # Números negativos não são permitidos
group.should_not include
group.should_not include
should_not include('0')
group.should_not include
should_not include('7')
group.should_not include
should_not include('8')
group.should_not include
should_not include('9')
end
16
17. Capítulo 1: Fazendo a classe Dice usando BDD
# Três grupos de 1000 números aleatórios devem ser diferentes um do outro
it "deve mostrar números de forma aleatória" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
xit "deve armazenar número da última jogada."
xit "deve jogar o dado no momento que o objeto for inicializado"
end
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' FAILED
expected "4025132222510540002142312555235104432520522022430445143425254
51533145101530430012510120055232441422435123332040350424441302405340420
50050324205500223120330524430331015422350203015044053545205524012055023
10100333140520435320541010244153022003403143022550340451124322335450431
5335402445045511" not to include "0"
./spec/dice_spec.rb
/ / rb:10:
Finished in 0.046589 seconds
2 examples, 1 failure
17
18. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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. Vamos
aproveitar para refatorar o primeiro requisito para usar expressões regulares. Agora o teste passa :).
class Dice
def play
rand
rand(6) + 1
end
end
%w{ rubygems spec lib/dice }.each {|lib| require lib}
each
describe Dice do
it "deve ter somente números entre 1 e 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Três grupos de 1000 números aleatórios devem ser diferentes um do outro
it "deve mostrar números de forma aleatória" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
xit "deve armazenar número da última jogada."
18
19. Capítulo 1: Fazendo a classe Dice usando BDD
xit "deve jogar o dado no momento que o objeto for inicializado"
end
ARMAZENANDO O NÚMERO DO DADO
Vamos trabalhar no próximo requisito: "deve armazenar número da última jogada.". Eu criarei o método
show_number e colocarei uma constante para o teste passar.
%w{rubygems spec lib/dice}.each {|lib| require lib}
each
describe Dice do
it "deve ter somente números entre 1 e 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Três grupos de 1000 números aleatórios devem ser diferentes um do outro
it "deve mostrar números de forma aleatória" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
it "deve armazenar número da última jogada." do
dice = Dice new
Dice.new
dice.play
play
19
20. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
dice.show_number to_s.should match
show_number.to_s should match(/^[1-6]*[1-6]$/)
end
xit "deve jogar o dado no momento que o objeto for inicializado"
end
class Dice
def play
rand
rand(6) + 1
end
def show_number
3
end
end
TROCANDO CONSTANTE POR VARIÁVEL
Essa é uma regra importante no BDD. Agora você pode mudar a constante por uma variável de instância na
classe dados. O teste continua passando.
class Dice
def play
@number = rand
rand(6) + 1
end
def show_number
@number
end
end
20
21. Capítulo 1: Fazendo a classe Dice usando BDD
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}.each {|lib| require lib}
each
describe Dice do
it "deve ter somente números entre 1 e 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Três grupos de 1000 números aleatórios devem ser diferentes um do outro
it "deve mostrar números de forma aleatória" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
it "deve armazenar número da última jogada." do
dice = Dice new
Dice.new
dice.play
play
dice.show_number to_s.should match
show_number.to_s should match(/^[1-6]*[1-6]$/)
end
it "deve jogar o dado na inicialização do objeto." do
Dice new.show_number to_s.should match
Dice.new show_number.to_s should match(/^[1-6]*[1-6]$/)
21
22. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
end
end
Output:
...F
1)
'Dice deve jogar o dado na inicialização do objeto.' FAILED
expected "" to match /^[1-6]*[1-6]$/
./spec/dice_spec.rb
/ / rb:29:
Finished in 0.12371 seconds
4 examples, 1 failure
USANDO O MÉTODO INITIALIZE
Agora eu finalizo a classe Dice colocando o método initialize com uma mensagem para o método play. O
teste passa.
class Dice
def initialize
play
end
def play
@number = rand
rand(6) + 1
end
22
23. Capítulo 1: Fazendo a classe Dice usando BDD
def show_number
@number
end
end
VAMOS REFATORAR
Agora eu posso fazer uma pequena refatoração no arquivo dice_spec.rb. Eu colocarei um bloco
before(:each) para simplificar o código. Farei também uma refatoração no terceiro requisito: "deve
armazenar 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}.each {|lib| require lib}
each
describe Dice do
before :each do
before(:each)
@dice = Dice new
Dice.new
end
it "deve ter somente números entre 1 e 6" do
group = (1..1_000).collect @dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Três grupos de 1000 números aleatórios devem ser diferentes um do outro
it "deve mostrar números de forma aleatória" do
group1 = (1..1_000).collect @dice.play }
collect{ play
group2 = (1..1_000).collect @dice.play }
collect{ play
group3 = (1..1_000).collect @dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
23
24. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
(group2 == group3).should be_false
should
end
it "deve armazenar número da última jogada." do
group1 = (1..100).collect do
collect
@dice.play
play
@dice.show_number
show_number
end
group2 = (1..100).collect do
collect
@dice.play
play
@dice.show_number
show_number
end
(group1 == group2).should be_false
should
end
it "deve jogar o dado na inicialização do objeto." do
@dice.show_number to_s.should match
show_number.to_s should match(/^[1-6]*[1-6]$/)
end
end
BRINCANDO COM A CLASSE DICE
Agora que a classe foi finalizada vamos brincar com ela no irb!
>> require 'dice'
=> true
>> dice = Dice new
Dice.new
=> #<Dice:0xb7a64188 @number=5>
>> dice.show_number
show_number
=> 5
24
25. Capítulo 1: Fazendo a classe Dice usando BDD
>> dice.class
class
=> Dice
Jogando o dado somente uma vez e deixando ele de lado:
>> Dice new.show_number
Dice.new show_number
=> 2
Jogar o dado várias vezes:
>> 20.times print dice.play }
times{ play
21636456135236136236=> 20
Três dados:
>> yellowdice = [Dice new, Dice new, Dice new]
Dice.new Dice.new Dice.new
=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]
>> yellowdice.each |dice| puts dice.show_number }
each{ show_number
3
5
5
=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]
Jogar novamente o mesmo dado
>> yellowdice.each |dice| puts dice.play }
each{ play
5
2
5
=> [#<Dice:0xb7a3e3d4 @number=5>, #<Dice:0xb7a3e3ac @number=2>, #<Dice:0xb7a3e384 @number=5>]
Qual o maior valor entre os três dados da última jogada?
25
26. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
>> puts yellowdice.collect |item| item.show_number }.max
collect{ show_number max
5
=> nil
E qual o menor valor?
>> puts yellowdice.collect |item| item.show_number }.min
collect{ show_number min
2
=> nil
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.
26
27. Capítulo 2: Reproduzindo os dados do jogo War
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 classe
Ruby os dados do jogo War. Isso mesmo! Aqueles 6 dados. 3 vermelhos e 3 amarelos que representam
ataque e defesa respectivamente.
Eu usarei dois arrays: reddice e yellowdice para armazenar os valores dos dados. Lembrei agora que o uso
dos dados depende do número de exércitos do atacante e do defensor. Mas nesse experimento eu pensarei
somente na manipulação dos dados no jogo. Eles podem ser manuseados com 1, 2 ou 3 dados vermelhos
contra 1, 2 ou 3 dados amarelos. Eu terei que comparar o maior valor dos dados vermelhos com o maior
valor 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.
27
28. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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 a
isso me lembrei que é imprescindível o uso de comparação de arrays na classe WarDice. Vamos incluir um
teste de comparação de arrays no arquivo dice_spec.rb e verificar o comportamento:
describe Array do
it "deve ser comparável" do
([8,9,10] > [1,2,3]).should be_true
should
end
end
Output:
....F
1)
NoMethodError in '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
28
29. Capítulo 2: Reproduzindo os dados do jogo War
INCLUINDO O MÓDULO COMPARABLE
Para solucionar o problema anterior, apenas inclua o módulo comparable no arquivo dice.rb. Eu farei isso
usando uma forma simplificada para brincar com as possibilidades do Ruby :).
class Array; include Comparable; end
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 ou
defesa.. Na classe WarDice eu vou colocar de propósito um número diferente de 1, 2, 3 para gerar um erro.
describe Wardice do
it "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." do
wardice = Wardice new(0, 3)
Wardice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
end
it "compara os valores do maior para o menor e salva em um array"
end
class Wardice
attr_reader :red :yellow
red,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
29
30. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
end
end
Output:
'Wardice permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa.' FAILED
expected "0" to match /^[1-3]$/
./spec/dice_spec.rb
/ / rb:50:
Finished in 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 preenchidas
com um número aleatório variando de 1 a 3. Eu farei também uma pequena refatoração no arquivo spec
para que ele fique preparado para a situação em que a quantidade de dados for correta. Você pode ver a
solução abaixo:
class WarDice
attr_reader :red :yellow
red,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
end
end
describe WarDice do
30
31. Capítulo 2: Reproduzindo os dados do jogo War
it "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." do
wardice = Wardice new(0, 7)
Wardice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
wardice2 = Wardice new(2, 3)
Wardice.new
wardice2.red should == 2
red.should
wardice2.yellow should == 3
yellow.should
end
it "compara os valores do maior para o menor e salva em um array"
end
ARRAY EM ORDEM DECRESCENTE
Esse requisito é muito importante, mas só lembrei dele agora: wardice deve fornecer os resultados dos
dados vermelho e amarelo em arrays em ordem decrescente. Você pode ver a solução abaixo:
describe WarDice do
it "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." do
wardice = WarDice new(0, 7)
WarDice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
wardice2 = WarDice new(2, 3)
WarDice.new
wardice2.red should == 2
red.should
wardice2.yellow should == 3
yellow.should
end
it "deve fornecer os resultados dos dados vermelho e amarelo em arrays em ordem decrescente" do
wardice = Wardice new(3, 3)
Wardice.new
wardice.reddice is_a?(Array).should be_true
reddice.is_a? should
31
32. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
wardice.yellowdice is_a?(Array).should be_true
yellowdice.is_a? should
wardice.reddice sort{|x, y| y <=> x }.should == wardice.reddice
reddice.sort should reddice
wardice.yellowdice sort{|x, y| y <=> x }.should == wardice.yellowdice
yellowdice.sort should yellowdice
end
it "compara os valores do maior para o menor e salva em um array"
end
class wardice
attr_reader :red :yellow :reddice :yellowdice
red, yellow, reddice,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@reddice = []
@yellowdice = []
@dice = Dice new
Dice.new
@red.times
times{|row| @reddice[row] = [@dice.play }
play]
@yellow.times |row| @yellowdice[row] = [@dice.play }
times{ play]
end
end
Output:
'wardice deve fornecer os resultados dos dados vermelho e amarelo em arrays em ordem decrescente' FAILE
expected: [[5], [2], [4]],
got: [[5], [4], [2]] (using ==
==)
./spec/dice_spec.rb
/ / rb:63:
32
33. Capítulo 2: Reproduzindo os dados do jogo War
Finished in 0.035218 seconds
9 examples, 1 failure, 2 pending
SOLUCIONANDO O PROBLEMA DO ARRAY EM ORDEM DECRESCENTE
class WarDice
attr_reader :red :yellow :reddice :yellowdice
red, yellow, reddice,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@reddice = []
@yellowdice = []
@dice = Dice new
Dice.new
@red.times
times{|row| @reddice[row] = [@dice.play }
play]
@yellow.times |row| @yellowdice[row] = [@dice.play }
times{ play]
@reddice.sort!
sort!{|x,y| y <=> x }
@yellowdice.sort!
sort!{|x,y| y <=> x }
end
end
Agora o teste passa.
33
34. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
COMPARANDO OS VALORES
Vamos trabalhar no último requisito: compara os valores do maior para o menor e salva em um array.
describe Wardice do
it "permite a utilização de 1, 2 or 3 dados, para o ataque ou defesa." do
wardice = Wardice new(0, 7)
Wardice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
wardice2 = Wardice new(2, 3)
Wardice.new
wardice2.red should == 2
red.should
wardice2.yellow should == 3
yellow.should
end
it "deve fornecer os resultados dos dados vermelho e amarelo em arrays em ordem decrescente" do
wardice = Wardice new(3, 3)
Wardice.new
wardice.reddice is_a?(Array).should be_true
reddice.is_a? should
wardice.yellowdice is_a?(Array).should be_true
yellowdice.is_a? should
wardice.reddice sort{|x, y| y <=> x }.should == wardice.reddice
reddice.sort should reddice
wardice.yellowdice sort{|x, y| y <=> x }.should == wardice.yellowdice
yellowdice.sort should yellowdice
end
it "compara os valores do maior para o menor e salva em um array" do
wardice = Wardice new(3, 2)
Wardice.new
wardice.reddice first.should > wardice.reddice last
reddice.first should reddice.last
wardice.attack
attack
wardice.result
result[0].should == "Red Win" if wardice.reddice
should reddice[0] > wardice.yellowdice
yellowdice[0]
wardice.result
result[0].should == "Yellow Win" if wardice.reddice
should reddice[0] <= wardice.yellowdice
yellowdice[0]
34
35. Capítulo 2: Reproduzindo os dados do jogo War
end
end
A classe WarDice com os testes passando por completo:
class Wardice
attr_reader :red :yellow :reddice :yellowdice :result
red, yellow, reddice, yellowdice,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@reddice = []
@yellowdice = []
@result = []
@dice = Dice new
Dice.new
@red.times
times{|row| @reddice[row] = [@dice.play }
play]
@yellow.times |row| @yellowdice[row] = [@dice.play }
times{ play]
@reddice.sort!
sort!{|x, y| y <=> x }
@yellowdice.sort!
sort!{|x, y| y <=> x }
end
def attack
@reddice.each_with_index do |item, index|
each_with_index
next if @yellowdice[index].nil?
nil?
reddice = item
yellowdice = @yellowdice[index]
if reddice > yellowdice
@result << "Vermelho Venceu!"
else
35
36. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
@result << "Amarelo Venceu!"
end
end
end
end
BRINCANDO COM A CLASSE WARDICE
Abra o irb e digite a sequência abaixo:
>> require 'dice'
=> true
Inicialize a classe WarDice. O primeiro parâmetro é o número de dados vermelhos e o segundo é o número
de dados amarelos:
>> wardice = WarDice new(2, 3)
WarDice.new
=> #<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.reddice
reddice
3
1
=> nil
>> puts wardice.yellowdice
yellowdice
6
1
1
=> nil
36
37. Capítulo 2: Reproduzindo os dados do jogo War
Mostra o resultado:
>> puts wardice.result
result
Yellow Win
Yellow Win
=> nil
A saída completa em um único comando:
>> war.reddice each_with_index{ |item, index| puts "Red:#{item} Yellow:#{war.yellowdice
reddice.each_with_index yellowdice[index]}
- #{war.result
result[index]}"}
Red:3 Yellow:6 - Yellow Win
Red: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 para
fazer uma bela aplicação gráfica usando Shoes.
37
38. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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 vamos
fazer um simples teste com a classe Dice. Você deve criar o arquivo diceshoes.rb, então cole o código
abaixo dentro dele e salve dentro do mesmo diretório do arquivo dice.rb. Finalmente, execute ele com o
Shoes para ver o resultado.
Observe que eu apenas inclui o arquivo dice.rb com um require no começo do código abaixo:
require 'dice'
Shoes app :title => "Teste com a classe Dice", :width => 500, :height => 500 do
Shoes.app
background aliceblue
para "Bem Vindo! Esse é um exemplo de uso da classe Dice.", :weight => "bold"
38
39. Capítulo 3: Jogando dados com Shoes
dice = Dice new
Dice.new
# Imprime na tela os números dos dados jogados com cores aleatórias
1_000.times do
times
r, g, b = rand, rand, rand
para dice.play :stroke => rgb **3, g**
play, rgb(r** **3, b**
**3), :size => "large"
end
end
Saída:
39
40. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
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 nome
wardiceshoes.rb na pasta lib. Eu fiz alguns comentários através do código para facilitar a compreensão.
Faça um bom experimento.
require 'dice'
Shoes app :title => "Dados do Jogo War", :width => 500, :height => 500 do
Shoes.app
background gradient black, teal )
gradient(
# Listbox com a quantidade de dados vermelhos: 1, 2 or 3
para "Red", :stroke => tomato
@numberreddice = list_box :items => ["1", "2", "3"],
:width => 70, :choose => "3" do |list|
end
para " X ", :stroke => snow
# Listbox com a quantidade de dados amarelos: 1, 2 or 3
@numberyellowdice = list_box :items => ["1", "2", "3"],
:width => 70, :choose => "3" do |list|
end
para "Yellow", :stroke => yellow
# Define uma posição aleatória
@a = @b = @c = []
(40..200).step
step(10){ |x| @a << x }
(230..450).step
step(10){ |y| @b << y }
(80..450).step
step(10){ |z| @c << z }
|
# Variáveis que vão armazenar os valores do objeto wardice.
40
41. Capítulo 3: Jogando dados com Shoes
@reddice = @yellowdice = @resulttext = []
button "Attack", :width => 80 do
# Limpa a tela
@dice.each |d| d.remove }
each{ remove
@resulttext.each |a| a.remove }
each{ remove
# O objeto wardice is inicializado
wardice = WarDice new( @numberreddice.text to_i, @numberyellowdice.text to_i )
WarDice.new text.to_i text.to_i
@reddice = wardice.reddice
reddice
@yellowdice = wardice.yellowdice
yellowdice
@result = wardice.result
result
# Cada dado é desenhado em uma posição aleatória
@reddice.each |item| draw @a[rand
each{ draw( rand(@a.length
length)], @c[rand
rand(@c.length
length)], item.to_s to_i, 1, true ) }
to_s.to_i
@yellowdice.each |item| draw @b[rand
each{ draw( rand(@b.length
length)], @c[rand
rand(@c.length
length)], item.to_s to_i, 2, true )}
to_s.to_i
end
button "Verify", :width => 80 do
# Limpa a tela
@dice.each |d| d.remove }
each{ remove
@resulttext.each |a| a.remove }
each{ remove
# posição inicial dos dados
leftyellow = 250
leftred = 150
topred = topyellow = 100
# Cada dado é desenhado em uma posição definida
@reddice.each do |item|
each
draw leftred, topred, item.to_s to_i, 1, false )
draw( to_s.to_i
41
42. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
topred += 100
end
@yellowdice.each do |item|
each
draw leftyellow, topyellow, item.to_s to_i, 2, false )
draw( to_s.to_i
topyellow += 100
end
# Posição inicial para a lista com o resultado
leftresult = 300
topresult = 80
# Os resultados são impressos na tela em uma posição definida
@result.each_with_index do |item, index|
each_with_index
@resulttext[index] = para item.to_s :stroke => snow, :top => topresult, :left => leftresult
to_s,
topresult += 100
end
end
# O método draw foi baseado no projeto Pretty Dice, escrito por Ed Heil
@dice = []
def draw left, top, number, color, rotate )
draw(
imagewidth = 60
imageheight = 60
i = image imagewidth, imageheight,
image(
:top => top - imagewidth / 2,
:left => left - imagewidth / 2,
:shadow => 10, :center => true ) do
42
43. Capítulo 3: Jogando dados com Shoes
if color == 1
strokecolor = red
fillrectanglecolor = tomato
filldotscolor = darkred
else
strokecolor = yellow
fillrectanglecolor = lightgoldenrodyellow
filldotscolor = chocolate
end
sw = 1
strokewidth sw
stroke strokecolor
fill fillrectanglecolor
inset = 2
inset2 = 8
rect inset, inset, imagewidth-inset-sw, imageheight-inset-sw, 10 )
rect( - - - -
fill filldotscolor
ovalradius = 10
low = inset2
high = imagewidth - inset2 - ovalradius
mid = ( imagewidth - ovalradius ) / 2
oval mid, mid, ovalradius ) if number % 2 == 1
oval(
if number > 1
oval low, low, ovalradius )
oval(
oval high, high, ovalradius )
oval(
end
43
44. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir
if number > 3
oval low, high, ovalradius )
oval(
oval high, low, ovalradius )
oval(
end
if number > 5
oval mid, low, ovalradius )
oval(
oval mid, high, ovalradius )
oval(
end
end # fim do bloco `image`
i.rotate rand 359 ) ) if rotate
rotate( rand(
@dice << i
end
end
BRINCANDO COM O APLICATIVO
Abaixo você pode ver o aplicativo em ação. Você deve escolher o número de dados vermelhos e amarelos
no respectivo listbox. Depois clique no botão ataque para jogar os dados. Agora é só se divertir!
44
45. Capítulo 3: Jogando dados com Shoes
Agora clique no botão verificar para saber quem venceu!
45