Test::Unit - Danilo Sato
Transcrição
Test::Unit - Danilo Sato
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… printf("Passou 1\n"); printf("valor: %s\n", …! © ThoughtWorks 2008 Era uma vez… printf("Passou 1\n"); printf("valor: %s\n", …! © ThoughtWorks 2008 xUnit •! XP – Kent Beck •! Automatizar passos manuais •! Você não quebra o que já está funcionando © ThoughtWorks 2008 Test::Unit •! 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)! –! …! © 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') > end" def test_lower" assert Card.new('TD') < end" end Card.new('KH')" Card.new('JH')" © ThoughtWorks 2008 StrutsTestCase + AspectJ •! Gravador de testes! •! Gera as classes de teste pra você! © ThoughtWorks 2008 Primeiros Erros •! •! •! •! Testes imensos Várias asserções no mesmo cenário Erros não ajudam muito Debuggar o teste © ThoughtWorks 2008 Primeiras Lições •! 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 © ThoughtWorks 2008 Cobertura de Código •! RCov •! Quais linhas estão sendo executadas pelos testes? •! Objetivo: 100% © ThoughtWorks 2008 Cuidado! •! Cobertura não garante qualidade dos testes! •! Heckle: Mutação © ThoughtWorks 2008 Testes Pequenos •! Arrange-Act-Assert •! Single Assertion per Test © ThoughtWorks 2008 E mais problemas… •! setUp enormes •! Carrega e limpa o banco de dados •! E os métodos estáticos? © ThoughtWorks 2008 Test-Driven Development © ThoughtWorks 2008 Test-Driven Development © ThoughtWorks 2008 Testes Ajudam no Design •! •! •! •! Refatoração Maus Cheiros YAGNI Design Evolutivo © ThoughtWorks 2008 Testes Comunicam Intenção •! Documentação Executável •! Legibilidade é importante •! Lemos mais código do que escrevemos © 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') > end" def test_lower" assert Card.new('TD') < end" end Card.new('KH')" Card.new('JH')" © ThoughtWorks 2008 RSpec 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! © ThoughtWorks 2008 Mais algumas lições 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) © ThoughtWorks 2008 Testes Unitários •! •! •! •! •! •! Integridade Interna Rápidos Independentes “Desacoplados” Escrito por e para desenvolvedores Não indicam integridade externa © ThoughtWorks 2008 Visão Mais Ampla © ThoughtWorks 2008 Histórias •! Como <papel/usuário> •! Eu gostaria de <funcionalidade> •! Pois <valor de negócio> © ThoughtWorks 2008 Cenários (Exemplos) •! Dado <contexto> •! Quando <evento> •! Então <consequência> © 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 © ThoughtWorks 2008 Cucumber •! Novo Story Runner •! Histórias em Português! 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 © ThoughtWorks 2008 Testes de Aceitação •! •! •! •! •! •! Integridade Externa Mais lentos “Acoplados” Mais difícil detectar causa de erros Escrito para clientes Não indicam integridade interna © ThoughtWorks 2008 Selenium •! 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 ! © ThoughtWorks 2008 © ThoughtWorks 2008 © ThoughtWorks 2008 Testes Unitários © ThoughtWorks 2008 Testes de Aceitação © ThoughtWorks 2008 © ThoughtWorks 2008 © ThoughtWorks 2008 ? © ThoughtWorks 2008 Integração © ThoughtWorks 2008 Mock •! •! •! •! 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) –! … © ThoughtWorks 2008 © ThoughtWorks 2008 RSpec # 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) © ThoughtWorks 2008 Mocha # 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) © ThoughtWorks 2008 Stubs •! Dublês que não verificam expectativas •! Não se importam com o que aconteceu # RSpec" band.stub!(:autograph).and_return('signature')" Guitar.stub!(:find).and_return(guitar)" # Mocha" band.stubs(:autograph).returns('signature')" Guitar.stubs(:find).returns(guitar) © ThoughtWorks 2008 Meus erros com mocks/stubs •! •! •! •! •! 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 © ThoughtWorks 2008 Lições sobre mocks •! 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 © 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')! © ThoughtWorks 2008 Synthesis © 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! © ThoughtWorks 2008 Synthesis © ThoughtWorks 2008 Synthesis •! •! •! •! •! “Cobertura de mocks” Testes acoplados somente nas “bordas” Feedback rápido sobre potenciais erros Funciona com Rspec/Mocha/Expectations Tarefa Rake: Synthesis::Task.new('synthesis:spec') do |t|" t.adapter = :rspec" t.pattern = 'test_project/rspec/*_spec.rb'" end! © ThoughtWorks 2008 Visualizações © ThoughtWorks 2008 © ThoughtWorks 2008 Builds rápidos! © ThoughtWorks 2008 Behaviour-Driven Development •! Um processo ponta-a-ponta de desenvolvimento •! Independente de ferramenta •! “Outside-in” © ThoughtWorks 2008 Normalmente… © ThoughtWorks 2008 BDD © ThoughtWorks 2008 BDD © ThoughtWorks 2008 Momento para Refletir © ThoughtWorks 2008 Resumo •! •! •! •! •! •! •! •! •! Testes Unitários Cobertura de Código TDD Histórias e STDD Testes de Aceitação Selenium Mocks/Stubs Synthesis BDD © ThoughtWorks 2008 Por que testar? © ThoughtWorks 2008 Custo de encontrar um erro © ThoughtWorks 2008 Feedback •! •! •! •! •! •! 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 © ThoughtWorks 2008 Build Quality In “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” © ThoughtWorks 2008 Auto-Inspeção © ThoughtWorks 2008 Bugs Bugs são testes que você esqueceu de escrever •! Resolva a Causa Raiz © ThoughtWorks 2008 Verificação e Validação © ThoughtWorks 2008 Obrigado! Perguntas? [email protected] www.dtsato.com © ThoughtWorks 2008