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

Documentos relacionados