Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

2,820 views

Published on

Palestra apresentada no primeiro ENCATEC

Published in: Technology

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   

×