Testes unitários e de integração: Quando e Porque

2.378 visualizações

Publicada em

Palestra apresentada no primeiro ENCATEC

Publicada em: Tecnologia
0 comentários
4 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
2.378
No SlideShare
0
A partir de incorporações
0
Número de incorporações
6
Ações
Compartilhamentos
0
Downloads
19
Comentários
0
Gostaram
4
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Testes unitários e de integração: Quando e Porque

  1. 1. Testes unitários e de integração Quando e Porque   
  2. 2. Resumo Introdução Testes unitários Testes de integração Desenvolvimento outside-in Conclusão   
  3. 3. O porque inicial... Tudo muda o tempo todo   
  4. 4. Testes unitários Testando a menor unidade de código possível da forma mais isolada possível   
  5. 5. Unitários: Exemplo inicialdescribe Jogador do let(:objetivo) { double } subject { Jogador.new(objetivo) } describe #venceu? do it "deveria ser true se completou objetivo" do objetivo.stub!(:completo?) { true } subject.venceu?.should be end endend    
  6. 6. Unitários: Código testadoclass Jogador def initialize(objetivo) @objetivo = objetivo end def venceu? @objetivo.completo? endend   
  7. 7. Unitários: Exemplo + completodescribe Jogador do let(:objetivo) { double } let(:partida) { double } subject { Jogador.new(partida, objetivo) } describe #venceu? do #... it "deveria informar partida e jogador ao objetivo" do objetivo.should_receive(:completo?).with( subject, partida ) subject.venceu? end endend    
  8. 8. Unitários: Código testadoclass Jogador def initialize(partida, objetivo) @objetivo = objetivo @partida = partida end def venceu? @objetivo.completo? self, @partida endend   
  9. 9. Unitários: Ciclo do TDD Crie um teste Refatore Implemente a solução   
  10. 10. Unitários: Prós Rápido de fazer e executar Incentiva o baixo acoplamento Facilita resolução de algoritmos   
  11. 11. Unitários: Contras Falsa sensação de terminado Insegurança ao trocar contratos   
  12. 12. Unitários: Exemplo de erroclass Objetivo def completo?(jogador, partida) def terminado?(jogador, partida) #... endend   
  13. 13. Testes de integração Testando para garantir que as classes e componentes estejam se integrando corretamente   
  14. 14. Integração: Exemplodescribe Jogador, "com objetivo de conquistar 24 territorios" do let(:objetivo) { Objetivos::Conquistar24Territorios } let(:partida) { Partida.new } subject { Jogador.create(partida: partida, objetivo: objetivo) } it "deveria vencer se tiver 24 territorios" do 24.times { subject.territorios << Territorio.new } subject.venceu?.should be endend    
  15. 15. Integração: Modulo de Objetivos module Objetivos class Conquistar24Territorios def self.completo?(jogador, partida) jogador.territorios.count >= 24 end end end   
  16. 16. Integração: End to endComportamento do ponto de vista do usuárioExemplo: Dado que o jogador tem 23 territórios E o objetivo dele é conquistar 24 territórios Quando o jogador conquistar 1 território E finalizar a rodada Então Jogador vence a partida   
  17. 17. Integração: Prós Garante o funcionamento do sistema Sensação de tarefa concluída   
  18. 18. Integração: Contras Testes mais lentos Dificuldade de entender origem de erros   
  19. 19. Como unir? Sensação de finalizado e segurança dos testes de integração end to end Facilidade, rapidez e desacoplamento proporcionados pelos testes unitários   
  20. 20. Desenvolvimento Outside-in Definir caso de aceitação Preparar teste end-to-end Desenvolver funcionalidade com TDD Repetir isso infinitamente   
  21. 21. Outside-in: Caso de aceitação Jogador com 4 exércitos em um território ataca território vizinho que possui apenas 1 exército e o conquista   
  22. 22. Setup – inicialmente fake feature "Atacar" do scenario "territorio vizinho com 3 dados e conquistar" do dado_jogador_com_exercitos_no_pais(4, :brasil) dado_jogador_com_exercitos_no_pais(1, :argentina) end private def dado_jogador_com_exercitos_no_pais(exercitos, pais) end end   
  23. 23. Acesso - falhando feature "Atacar" do before :each do @partida = Partida.create end scenario "territorio vizinho com 3 dados e conquistar" do jogador1 = dado_jogador_com_exercitos_no_pais(4, :brasil) jogador2 = dado_jogador_com_exercitos_no_pais(1, :argentina) dado_que_jogador_esta_logado(jogador1) end #... def dado_que_jogador_esta_logado(jogador) visit partida_path(@partida) end end   
  24. 24. Acesso - funcionando models/partida.rb: class Partida < ActiveRecord::Base end config/routes.rb: War::Application.routes.draw do resources :partidas end controllers/partida_controller.rb: class PartidasController < ApplicationController def show end end Partidas/show.html.erb: <h1>Ok</h1>   
  25. 25. Primeira interação - falhando scenario "territorio vizinho com 3 dados e conquistar" do #... dado_que_jogador_esta_logado(jogador1) quando_selecionar_territorio_que_vai_atacar(:brasil) end private #... def quando_selecionar_territorio_que_vai_atacar(pais) click_link pais.to_s end   
  26. 26. Primeira interação - funcionando views/partidas.html.erb: <a href="#">brasil</a>   
  27. 27. Mais interações - falhando scenario "territorio vizinho com 3 dados e conquistar" do #... quando_selecionar_territorio_que_vai_atacar(:brasil) quando_selecionar_territorio_atacado(:argentina) quando_confirmar_ataque end #... def quando_selecionar_territorio_atacado(pais) click_link pais.to_s end def quando_confirmar_ataque click_button Atacar end   
  28. 28. Mais interações - funcionando views/partidas.html.erb: <form> <a href="#">brasil</a> <a href="#">argentina</a> <input name="Atacar" value="Atacar" type="submit"/> </form>   
  29. 29. Verficação - falhando scenario "territorio vizinho com 3 dados e conquistar" do #... quando_confirmar_ataque entao_territorio_eh_conquistado(:argentina) end #... def entao_territorio_eh_conquistado(pais) find(#mensagem).text.strip .should == "Territorio #{pais} conquistado" end   
  30. 30. Verificação - funcionando views/partidas.html.erb: <form> <a href="#">brasil</a> <a href="#">argentina</a> <input name="Atacar" value="Atacar" type="submit"/> <div id="mensagem"> Territorio argentina conquistado </div> </form>   
  31. 31. O que temos até agora? Teste View Controller Model   
  32. 32. Teste end-to-end ok Hora de ir mais fundo TDD a todo momento   
  33. 33. Teste unitário do controllerdescribe AtaquesController, POST do it deveria redirecionar para a partida do post :create, partida_id: 1, ataque: {} response.should redirect_to(partida_path(1)) end it deveria colocar mensagem de sucesso do post :create, partida_id: 1, ataque: { pais_atacado: argentina } flash[:mensagem]. should == "Territorio argentina conquistado" endend    
  34. 34. Controller de ataqueclass AtaquesController < ApplicationController def create pais = params[:ataque][:pais_atacado] flash[:mensagem] = "Territorio #{pais} conquistado" redirect_to partida_path(params[:partida_id]) endend   
  35. 35. View tem que mudar <div id="mensagem"><%= flash[:mensagem] %></div> <%= form_for :ataque, url: partida_ataques_url(@partida), method: :post do |f| %> <a href="#">brasil</a> <a href="#">argentina</a> <%= f.hidden_field pais_que_ataca %> <%= f.hidden_field pais_atacado %> <%= f.submit value: Atacar %>  <% end %>  
  36. 36. Javascript incluído var paisQueAtaca = $(#ataque_pais_que_ataca); var paisAtacado = $(#ataque_pais_atacado); var paisAtual = paisQueAtaca; var marcar = function(texto) { paisAtual.val(texto); } var trocaPaisAtual = function() { paisAtual = paisAtual == paisQueAtaca ? paisAtacado : paisQueAtaca; } $(a).click(function(e){ e.preventDefault(); marcar($(this).text()); trocaPaisAtual(); });   
  37. 37. O que temos até agora? (2) Teste View Controller Model   
  38. 38. Teste unitário do controller describe AtaquesController, POST do let(:partida) { double(id: 1, executar_ataque: true) } before :each do Partida.stub!(:find) { partida } end #... it deveria executar ataque na partida do params_ataque = {"meu_ataque" => true} partida.should_receive(:executar_ataque).with(params_ataque) post :create, partida_id: 1, ataque: params_ataque end end   
  39. 39. Controller de ataque class AtaquesController < ApplicationController def create ataque = params[:ataque] partida = Partida.find params[:partida_id] partida.executar_ataque(ataque) pais = ataque[:pais_atacado] flash[:mensagem] = "Territorio #{pais} conquistado" redirect_to partida_path(partida) end end   
  40. 40. Teste end-to-end falha $ rake spec:acceptance Failures: 1) Atacar territorio vizinho com 3 dados e conquistar Failure/Error: find(#mensagem).text.strip.should == "Territorio... Capybara::ElementNotFound: Unable to find css "#mensagem" Erro dificil de encontrar a origem!   
  41. 41. Método não encontrado $ tail -f log/test.log Completed 500 Internal Server Error in 3ms undefined method `executar_ataque for #<Partida:0xa5549c0> class Partida < ActiveRecord::Base def executar_ataque(attrs) end end   
  42. 42. Um pouco de ousadia Que tal começar um outro caso de aceitação antes de terminar este?   
  43. 43. Caso da derrotascenario "territorio vizinho com 3 dados e conquistar", js: true do dado_que_dados_vermelhos_estao_sortudos #...endscenario "territorio vizinho com 3 dados e nao conquistar", js: true do dado_que_dados_vermelhos_estao_azarentos #...enddef dado_que_dados_vermelhos_estao_azarentos ENV["forcar_vitoria"] = "defesa"enddef dados_que_dados_vermelhos_estao_sortudos ENV["forcar_vitoria"] = "ataque"end   
  44. 44. Teste do controller context conquistando do before :each do partida.stub!(:executar_ataque) { true } end it deveria colocar mensagem de sucesso do post :create, partida_id: 1, ataque: { pais_atacado: argentina } flash[:mensagem].should == "Territorio argentina conquistado" end end context nao conquistando do before :each do partida.stub!(:executar_ataque) { false } end it deveria colocar mensagem de insucesso do post :create, partida_id: 1, ataque: { pais_atacado: argentina } flash[:mensagem].should == "Territorio argentina nao foi conquistado" end end   
  45. 45. Controller de ataqueclass AtaquesController < ApplicationController def create ataque = params[:ataque] partida = Partida.find params[:partida_id] pais = ataque[:pais_atacado] if partida.executar_ataque(ataque) flash[:mensagem] = "Territorio #{pais} conquistado" else flash[:mensagem] = "Territorio #{pais} nao foi conquistado" end redirect_to partida_path(partida) endend   
  46. 46. Metodo burro pro teste passar class Partida < ActiveRecord::Base def executar_ataque(attrs) ENV["forcar_vitoria"] != defesa end end   
  47. 47. O que temos até agora? (3) Teste View Controller Model   
  48. 48. Integração com os models Dever de casa   
  49. 49. Ciclo do outside-in Crie um teste end-to-end Crie um teste Refatore Refatore Implemente a solução   
  50. 50. Conclusão Sempre guie o desenvolvimento por testes Tanto por testes de integração como unitários Combine-os em uma estratégia outside-in Siga sempre o mantra dos pequenos passos   
  51. 51. Bibliografia Test Driven Development: By Example Kent Beck Working Effectively with Legacy Code Michael Feathers Growing Object-Oriented Software, Guided by Tests Steve Freeman   
  52. 52. Mantenha contatotimotta@gmail.com@timottahttp://programandosemcafeina.blogspot.com   

×