Upload
others
View
3
Download
0
Embed Size (px)
Citation preview
Lições Aprendidas sobre Testes
Danilo Sato
Rails Summit Latin America
www.dtsato.com
16/Out/2008
© ThoughtWorks 2008
Um pouco sobre vocês
Será que estou na palestra certa?
© ThoughtWorks 2008
Minha história com testes
automatizados
© ThoughtWorks 2008
Era uma vez…
© ThoughtWorks 2008
Era uma vez…
© ThoughtWorks 2008
printf("Passou 1\n");!printf("valor: %s\n", res);!…!
Era uma vez…
© ThoughtWorks 2008
printf("Passou 1\n");!printf("valor: %s\n", res);!…!
•! XP – Kent Beck
•! Automatizar passos manuais
•! Você não quebra o que já está funcionando
xUnit
© ThoughtWorks 2008
•! Classe que estende Test::Unit::TestCase
•! Métodos devem começar com "test"
•! Asserções:
–!assert(bool)!
–!assert_equal(expected, actual)!
–!assert_raise(args, blk)!
–!assert_nil(actual)!
–!…!
Test::Unit
© ThoughtWorks 2008
© ThoughtWorks 2008
require 'rubygems' "require 'test/unit' "
require 'lib/card' "
class CardTest < Test::Unit::TestCase " def test_equal " assert Card.new('QS') == Card.new('QC')" end "
def test_greater " assert Card.new('AS') > Card.new('KH')" end "
def test_lower " assert Card.new('TD') < Card.new('JH')" end "end
•! Gravador de testes!
•! Gera as classes de teste pra você!
StrutsTestCase + AspectJ
© ThoughtWorks 2008
•! Testes imensos
•! Várias asserções no mesmo cenário
•! Erros não ajudam muito
•! Debuggar o teste
Primeiros Erros
© ThoughtWorks 2008
•! Testes grandes são difíceis de manter
•! Mas é bom cobrir a funcionalidade do ponto
de vista externo
Código de teste também é Código
Primeiras Lições
© ThoughtWorks 2008
•! RCov
•! Quais linhas estão sendo executadas pelos
testes?
•! Objetivo: 100%
Cobertura de Código
© ThoughtWorks 2008
•! Cobertura não garante qualidade dos testes!
•! Heckle: Mutação
Cuidado!
© ThoughtWorks 2008
•! Arrange-Act-Assert
•! Single Assertion per Test
Testes Pequenos
© ThoughtWorks 2008
•! setUp enormes
•! Carrega e limpa o banco de dados
•! E os métodos estáticos?
E mais problemas…
© ThoughtWorks 2008
Test-Driven Development
© ThoughtWorks 2008
Test-Driven Development
© ThoughtWorks 2008
•! Refatoração
•! Maus Cheiros
•! YAGNI
•! Design Evolutivo
Testes Ajudam no Design
© ThoughtWorks 2008
•! Documentação Executável
•! Legibilidade é importante
•! Lemos mais código do que escrevemos
Testes Comunicam Intenção
© ThoughtWorks 2008
© ThoughtWorks 2008
require 'rubygems' "require 'test/unit' "
require 'lib/card' "
class CardTest < Test::Unit::TestCase " def test_equal " assert Card.new('QS') == Card.new('QC')" end "
def test_greater " assert Card.new('AS') > Card.new('KH')" end "
def test_lower " assert Card.new('TD') < Card.new('JH')" end "end
RSpec
© ThoughtWorks 2008
require 'rubygems' "require 'spec' "
require 'lib/card' "
describe Card do " it 'should compare based on rank' do " Card.new('AS').should > Card.new('KH')" Card.new('QS').should == Card.new('QC')" Card.new('TD').should < Card.new('JH')" end "end!
Se está difícil de testar, provavelmente seu design
precisa evoluir
Use o código de teste para comunicar suas
intenções atuais para o “você do futuro” (e
seus colegas também)
Mais algumas lições
© ThoughtWorks 2008
•! Integridade Interna
•! Rápidos
•! Independentes
•! “Desacoplados”
•! Escrito por e para desenvolvedores
•! Não indicam integridade externa
Testes Unitários
© ThoughtWorks 2008
Visão Mais Ampla
© ThoughtWorks 2008
•! Como <papel/usuário>
•! Eu gostaria de <funcionalidade>
•! Pois <valor de negócio>
Histórias
© ThoughtWorks 2008
•! Dado <contexto>
•! Quando <evento>
•! Então <consequência>
Cenários (Exemplos)
© ThoughtWorks 2008
© ThoughtWorks 2008
Story: I can rank poker hands " As a game player " I want to rank a poker hand " So that I can decide a winner for the prize "
Scenario: Straight flush wins Four of a kind " Given a black hand with cards: 2H 3H 4H 5H 6H " And a white hand with cards: AC AH AD AS KC " Then black should win"
Scenario: Four of a kind wins Full house " Given a white hand with cards: 2C 2H 2D 2S AC " And a black hand with cards: AC AH AD KS KC " Then white should win"
Scenario: Full house wins Flush " Given a black hand with cards: 2C 2H 2S 3C 3S " And a white hand with cards: 4C 8C TC KC AC " Then black should win
© ThoughtWorks 2008
require 'rubygems' "require 'spec/story' "
require 'lib/hand' "require 'lib/card' "
steps_for :poker do" Given "a $color hand with cards: $cards" do |color, cards|" instance_eval "@#{color} = Hand.new('#{cards}')" " end " Then ("black should win") { @black.should > @white } " Then ("white should win") { @white.should > @black } " Then ("tie") { @black.should == @white } "end "
with_steps_for :poker do" Dir["**/*.story"].each { |file| run file } "end
•! Novo Story Runner
•! Histórias em Português!
Cucumber
© ThoughtWorks 2008
Funcionalidade: Adição! Como um péssimo matemático " Eu quero saber saomr dois números! Para evitar erros bobos "
Cenário: Adicionar dois números! Dado que eu digitei 50 na calculadora " E que eu digitei 70 na calculadora " Quando eu aperto o botão de soma! Então o resultado na calculadora deve ser 120
© ThoughtWorks 2008
require 'spec’ "class Calculadora " def push(n)" @args ||= []" @args << n" end " def soma; @args.inject(0) {|n,sum| sum+n}; end "end "
Before { @calc = Calculadora.new }"
Given /que eu digitei (\d+) na calculadora/ do |n| ! @calc.push n.to_i!end "When 'eu aperto o botão de soma’ do! @result = @calc.soma!end "Then(/o resultado na calculadora deve ser (\d*)/) { |result| @result.should == result.to_i }
© ThoughtWorks 2008
•! Integridade Externa
•! Mais lentos
•! “Acoplados”
•! Mais difícil detectar causa de erros
•! Escrito para clientes
•! Não indicam integridade interna
Testes de Aceitação
© ThoughtWorks 2008
•! Dirige o browser como se um usuário
estivesse navegando as páginas
•! Várias ferramentas:
–!Selenium IDE
–!Selenium RC
–!Selenium on Rails
–!Selenium Grid*
* Você está curioso? Vá para a palestra do lado !
Selenium
© ThoughtWorks 2008
© ThoughtWorks 2008
© ThoughtWorks 2008
Testes Unitários
© ThoughtWorks 2008
Testes de Aceitação
© ThoughtWorks 2008
© ThoughtWorks 2008
© ThoughtWorks 2008
© ThoughtWorks 2008
?
© ThoughtWorks 2008
Integração
•! Permitem especificar interações
•! Trocam o objeto real por um dublê
•! Verificam se as expectativas foram atendidas
•! Substituem:
–!Objetos “gordos”
–!Serviços externos
–!Bibliotecas (não quero testar se o gem funciona)
–!…
Mock
© ThoughtWorks 2008
© ThoughtWorks 2008
RSpec
© ThoughtWorks 2008
# Expectativas "band.should_receive(:rock)"band.should_not_receive(:pause)"
# Quantidade de chamadas "guitar.should_receive(:tune).once "guitar.should_receive(:play).at_least(3).times "
# Argumentos "guitar.should_receive(:strum_chord).with("E")"guitar.should_receive(:strum_chord).with(:anything)"
# Valores de Retorno "band.should_receive(:rocking?).and_return(true)"band.should_receive(:stop).and_raise(CantStopRocking)
Mocha
© ThoughtWorks 2008
# Expectativas"band.expects(:rock)"band.expects(:pause).never "
# Quantidade de chamadas"guitar.expects(:tune).once"guitar.expects(:play).at_least(3) "
# Argumentos"guitar.expects(:strum_chord).with("E")"guitar.expects(:strum_chord).with(:anything) "
# Valores de Retorno"band.expects(:rocking?).returns(true)"band.expects(:stop).raises(CantStopRocking)
•! Dublês que não verificam expectativas
•! Não se importam com o que aconteceu
Stubs
© ThoughtWorks 2008
# RSpec "band.stub!(:autograph).and_return('signature')"Guitar.stub!(:find).and_return(guitar)"
# Mocha "band.stubs(:autograph).returns('signature')"Guitar.stubs(:find).returns(guitar)
•! Precisar gravar muitas expectativas
•! Muito acoplado com implementação
•! Muitos stubs poucos mocks
•! Mockar o próprio objeto
•! Testes podem passar com o sistema
quebrado
Meus erros com mocks/stubs
© ThoughtWorks 2008
•! Mock Roles not Objects [OOPSLA04]
•! Use mocks para desacoplar testes
•! Cuidado com mock de bibliotecas externas
–!Use um Adapter
•! Mocks não tiram o valor dos testes
acoplados
•! CRC com testes
Lições sobre mocks
© ThoughtWorks 2008
© ThoughtWorks 2008
class DataBranderTest < Test::Unit::TestCase " def test_saves_branded_to_storage " storage = Storage.new 'whatever' " storage.expects(:save).with('METAL - rock')" DataBrander.new(storage).save_branded('rock') " end "end!class DataBrander " BRAND = "METAL" "
def initialize(storage) " @storage = storage" end "
def save_branded(data)" @storage.save "#{BRAND} - #{data}" " end "end!
© ThoughtWorks 2008
class DataBranderTest < Test::Unit::TestCase " def test_saves_branded_to_storage " storage = Storage.new 'whatever' " storage.expects(:save).with('METAL - rock')" DataBrander.new(storage).save_branded('rock') " end "end!class DataBrander " BRAND = "METAL" "
def initialize(storage) " @storage = storage" end "
def save_branded(data)" @storage.save "#{BRAND} - #{data}" " end "end!
© ThoughtWorks 2008
class StorageTest < Test::Unit::TestCase " def test_saves_to_file " Storage.new('test.txt').save('rock')" assert_equal 'rock', File.read('test.txt')" ensure " FileUtils.rm_f('test.txt')" end "end
class Storage " def initialize(filename)" @filename = filename" end "
def save(val)" File.open(@filename, 'w') {|f| f << val} " end "end!
© ThoughtWorks 2008
class StorageTest < Test::Unit::TestCase " def test_saves_to_file " Storage.new('test.txt').save('rock')" assert_equal 'rock', File.read('test.txt')" ensure " FileUtils.rm_f('test.txt')" end "end
class Storage " def initialize(filename)" @filename = filename" end "
def save(val)" File.open(@filename, 'w') {|f| f << val} " end "end!
© ThoughtWorks 2008
class StorageTest < Test::Unit::TestCase " def test_saves_to_file " Storage.new('test.txt').save('rock', 'w')" assert_equal 'rock', File.read('test.txt')" ensure " FileUtils.rm_f('test.txt')" end "end
class Storage " def initialize(filename)" @filename = filename" end "
def save(val, mode)" File.open(@filename, mode) {|f| f << val}" end "end!
© ThoughtWorks 2008
class StorageTest < Test::Unit::TestCase " def test_saves_to_file " Storage.new('test.txt').save('rock', 'w')" assert_equal 'rock', File.read('test.txt')" ensure " FileUtils.rm_f('test.txt')" end "end
class Storage " def initialize(filename)" @filename = filename" end "
def save(val, mode)" File.open(@filename, mode) {|f| f << val}" end "end!
© ThoughtWorks 2008
storage = Storage.new("hello.txt")"data_brander = DataBrander.new(storage) "data_brander.save_branded('Hello')!
© ThoughtWorks 2008
storage = Storage.new("hello.txt")"data_brander = DataBrander.new(storage) "data_brander.save_branded('Hello')!
Synthesis
© ThoughtWorks 2008
© ThoughtWorks 2008
class DataBranderTest < Test::Unit::TestCase " def test_saves_branded_to_storage " storage = Storage.new 'whatever' " storage.expects(:save).with('METAL – rock', 'w')" DataBrander.new(storage).save_branded('rock') " end "end!class DataBrander " BRAND = "METAL" "
def initialize(storage) " @storage = storage" end "
def save_branded(data)" @storage.save "#{BRAND} - #{data}", 'w' " end "end!
Synthesis
© ThoughtWorks 2008
•! “Cobertura de mocks”
•! Testes acoplados somente nas “bordas”
•! Feedback rápido sobre potenciais erros
•! Funciona com Rspec/Mocha/Expectations
•! Tarefa Rake:
Synthesis
© ThoughtWorks 2008
Synthesis::Task.new('synthesis:spec') do |t| " t.adapter = :rspec " t.pattern = 'test_project/rspec/*_spec.rb' "end!
Visualizações
© ThoughtWorks 2008
© ThoughtWorks 2008
Builds rápidos!
© ThoughtWorks 2008
Behaviour-Driven Development
© ThoughtWorks 2008
•! Um processo ponta-a-ponta de
desenvolvimento
•! Independente de ferramenta
•! “Outside-in”
Normalmente…
© ThoughtWorks 2008
BDD
© ThoughtWorks 2008
BDD
© ThoughtWorks 2008
Momento para Refletir
© ThoughtWorks 2008
•! Testes Unitários
•! Cobertura de Código
•! TDD
•! Histórias e STDD
•! Testes de Aceitação
•! Selenium
•! Mocks/Stubs
•! Synthesis
•! BDD
Resumo
© ThoughtWorks 2008
Por que testar?
© ThoughtWorks 2008
Custo de encontrar um erro
© ThoughtWorks 2008
•! Confiança
•! Erros são detectados rapidamente
•! Ajudam a saber quando terminamos
•! Pensar no design antes de implementar
•! Evita generalização desnecessária
•! Regressão automatizada
Feedback
© ThoughtWorks 2008
Build Quality In
© ThoughtWorks 2008
“Work smarter, not harder” -- Tom de Marco, “Slack”
“Inspecionar para previnir defeitos é bom; Inspecionar para encontrar defeitos é desperdício”
-- Shigeo Shingo, “The Toyota Production System”
Auto-Inspeção
© ThoughtWorks 2008
Bugs são testes que você esqueceu de
escrever
•! Resolva a Causa Raiz
Bugs
© ThoughtWorks 2008
Verificação e Validação
© ThoughtWorks 2008