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

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

Embed Size (px)

DESCRIPTION

Palestra apresentada no primeiro ENCATEC

Citation preview

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

   

Testes unitários e de integração

Quando e Porque

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

   

Resumo

Introdução

Testes unitários

Testes de integração

Desenvolvimento outside-in

Conclusão

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

   

O porque inicial...

Tudo muda o tempo todo

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

   

Testes unitários

Testando a menor unidade de códigopossível da forma mais isolada possível

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

   

Unitários: Exemplo inicial

describe 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

end

end

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

   

Unitários: Código testado

class Jogador def initialize(objetivo) @objetivo = objetivo end def venceu? @objetivo.completo? endend

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

   

Unitários: Exemplo + completo

describe 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

end

end

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

   

Unitários: Código testado

class Jogador def initialize(partida, objetivo) @objetivo = objetivo @partida = partida end def venceu? @objetivo.completo? self, @partida endend

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

   

Unitários: Ciclo do TDD

Crie um teste

Implemente a soluçãoRefatore

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

   

Unitários: Prós

Rápido de fazer e executar

Incentiva o baixo acoplamento

Facilita resolução de algoritmos

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

   

Unitários: Contras

Falsa sensação de terminado

Insegurança ao trocar contratos

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

   

Unitários: Exemplo de erro

class Objetivo def completo?(jogador, partida) def terminado?(jogador, partida) #... endend

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

   

Testes de integração

Testando para garantir que as classese componentes estejam se integrando

corretamente

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

   

Integração: Exemplo

describe 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 end

end

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

   

Integração: Modulo de Objetivos

module Objetivos

class Conquistar24Territorios def self.completo?(jogador, partida) jogador.territorios.count >= 24 end end

end

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

   

Integração: End to end

Comportamento do ponto de vista do usuário

Exemplo:

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

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

   

Integração: Prós

Garante o funcionamento do sistema

Sensação de tarefa concluída

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

   

Integração: Contras

Testes mais lentos

Dificuldade de entender origem de erros

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

   

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

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

   

Desenvolvimento Outside-in

Definir caso de aceitação

Preparar teste end-to-end

Desenvolver funcionalidade com TDD

Repetir isso infinitamente

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

   

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

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

   

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

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

   

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

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

   

Acesso - funcionando

models/partida.rb:class Partida < ActiveRecord::Baseend

config/routes.rb:War::Application.routes.draw do resources :partidasend

controllers/partida_controller.rb:class PartidasController < ApplicationController def show endend

Partidas/show.html.erb:<h1>Ok</h1>

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

   

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

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

   

Primeira interação - funcionando

views/partidas.html.erb:<a href="#">brasil</a>

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

   

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

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

   

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>

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

   

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

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

   

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>

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

   

O que temos até agora?

Teste View Controller Model

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

   

Teste end-to-end ok

Hora de ir mais fundo

TDD a todo momento

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

   

Teste unitário do controller

describe 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" end

end

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

   

Controller de ataque

class AtaquesController < ApplicationController def create pais = params[:ataque][:pais_atacado] flash[:mensagem] = "Territorio #{pais}' conquistado" redirect_to partida_path(params[:partida_id]) endend

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

   

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 %>

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

   

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(); });

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

   

O que temos até agora? (2)

Teste View Controller Model

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

   

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

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

   

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) endend

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

   

Teste end-to-end falha

$ rake spec:acceptanceFailures:

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!

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

   

Método não encontrado

class Partida < ActiveRecord::Base def executar_ataque(attrs) endend

$ tail -f log/test.logCompleted 500 Internal Server Error in 3msundefined method `executar_ataque' for #<Partida:0xa5549c0>

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

   

Um pouco de ousadia

Que tal começar um outro casode aceitação antes de terminar

este?

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

   

Caso da derrota

scenario "territorio vizinho com 3 dados e conquistar", js: true do dado_que_dados_vermelhos_estao_sortudos #... end scenario "territorio vizinho com 3 dados e nao conquistar", js: true do dado_que_dados_vermelhos_estao_azarentos #... end def dado_que_dados_vermelhos_estao_azarentos ENV["forcar_vitoria"] = "defesa" end def dados_que_dados_vermelhos_estao_sortudos ENV["forcar_vitoria"] = "ataque" end

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

   

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

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

   

Controller de ataque

class 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

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

   

Metodo burro pro teste passar

class Partida < ActiveRecord::Base def executar_ataque(attrs) ENV["forcar_vitoria"] != 'defesa' endend

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

   

O que temos até agora? (3)

Teste View Controller Model

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

   

Integração com os models

Dever de casa

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

   

Ciclo do outside-in

Crie um testeend-to-end

Refatore

Crie um teste

Implemente a soluçãoRefatore

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

   

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

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

   

Bibliografia

Test Driven Development: By Example

Kent Beck

Working Effectively with Legacy Code

Michael Feathers

Growing Object-Oriented Software, Guided by Tests

Steve Freeman

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

   

Mantenha contato

[email protected]

@timotta

http://programandosemcafeina.blogspot.com