Aprenda bdd-jogando-dados-ebook

3.526 visualizações

Publicada em

Um livro para você aprender BDD, Ruby, Rspec e Shoes enquanto se diverte.

Publicada em: Educação
3 comentários
10 gostaram
Estatísticas
Notas
Sem downloads
Visualizações
Visualizações totais
3.526
No SlideShare
0
A partir de incorporações
0
Número de incorporações
1.584
Ações
Compartilhamentos
0
Downloads
172
Comentários
3
Gostaram
10
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Aprenda bdd-jogando-dados-ebook

  1. 1. Aprenda BDD Jogando Dados! SEU COMPORTAMENTO É SE DIVERTIR Primeira Edição
  2. 2. Aprenda BDD Jogando Dados! SEU COMPORTAMENTO É SE DIVERTIR Primeira Edição por Valério Farias de Carvalho
  3. 3. 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/
  4. 4. 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
  5. 5. 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
  6. 6. 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
  7. 7. 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
  8. 8. 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
  9. 9. 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
  10. 10. 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
  11. 11. 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
  12. 12. 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
  13. 13. 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
  14. 14. 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
  15. 15. 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
  16. 16. 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
  17. 17. 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
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. 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
  26. 26. 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
  27. 27. 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
  28. 28. 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
  29. 29. 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
  30. 30. 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
  31. 31. 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
  32. 32. 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
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. 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
  37. 37. 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
  38. 38. 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
  39. 39. 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
  40. 40. 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
  41. 41. 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
  42. 42. Capítulo 3: Jogando dados com Shoes Agora clique no botão verificar para saber quem venceu! 45
  43. 43. Aprenda BDD Jogando Dados! - Seu comportamento é se divertir 46

×