You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
require'calculator'describeCalculatordoit'uses sum metod for 2 numbers'do# Arrangecalc=Calculator.new# Actresult=calc.sum(5,7)# Assertexpect(result).toeq(11)endit'uses sum method for 2 negative numbers'do# Arrangecalc=Calculator.new# Actresult=calc.sum(-5,7)# Assertexpect(result).toeq(2)endend
O teste irá falhar, então começamos, passo a passo, a resolver os erros retornados no console pelo RSpec.
Segundo passo: Criar a classe Calculator
lib/calculator.rb
classCalculatorend
Terceiro passo: Criar o método sum na classe Calculator
lib/calculator.rb
classCalculatordefsum(a,b)a + bendend
Agora o teste irá passar sem nenhum erro.
Para rodar um teste específico utilize o comando: rspec spec/caminho_para_o_teste/meuteste_spec.rb
6. Teste em 4 Fases
Testes devem ser confiáveis
Testes devem ser fáceis de escrever
Testes devem ser fáceis de entender hoje e no futuro
Não estamos focados em velocidade!
Um teste do padrão xUnit tem 4 fases, são elas:
Setup ou Arrange: Quando o SUT(System Under Test, o objeto sendo testado) é inserido no estado necessário para que o teste ocorra;
Exercise ou Act: Quando há interação com o SUT;
Verify ou Assert: Quando é verificado o comportamento esperado;
Teardown: Quando o sistema é colocado no estado que estava antes do teste ser executado.
Ex.: Limpar o banco de dados após o teste ser concluido. O Teardown é executado automaticamente pelo RSpec.
7. BDD - Behavior Driven Development ou Desenvolvimento Orientado por Comportamento
O BDD teve sua motivação inicial na dificuldade de Dan North ao explicar TDD para seus alunos quando questionado: Por onde começar a testar? O que testar? Como nomear os testes?
E apesar do BDD ter nascido apenas como um modo de rever a nomenclatura do TDD e o modo como se enxerga essa prática, hoje o BDD é uma abordagem de desenvolvimento de software.
A suíte de testes automatizados gerada através do TDD é "apenas" uma boa consequência do processo.
BDD permite que o cliente participe da especificação dos cenários.
Exemplo utilizando a gem BDD:
context'Searching'doit'Result is found'doGiven'I am on the search page'dovisit'/search'expect(page).tohave_content('Search')endWhen'I search something'dofill_in'Search',with: 'John'click_button'Go'endThen'I should see the word result'doexpect(page).tohave_content('Result')endendend
context: cria um contexto onde os testes, que pertencem a ele, estarão alocados.
Métodos são descritos em context utilizando as sintaxes:
context '.authenticate' do; end, para métodos de classe (def self.authenticate; end)
context '#admin?' do; end, para métodos de instância (def admin?; end)
Iremos refatorar o teste da classe Calculator com a finalidade de torná-lo ainda mais organizado e semântico:
require'calculator'describeCalculatordocontext'#sum'doit'with positive numbers'docalc=Calculator.newresult=calc.sum(5,7)expect(result).toeq(12)endit'with negative and positive numbers'docalc=Calculator.newresult=calc.sum(-5,7)expect(result).toeq(2)endit'with negative numbers'docalc=Calculator.newresult=calc.sum(-5, -7)expect(result).toeq(-12)endendend
.rspec: reune todas as configuração utilizadas nos testes.
Neste exemplo adicionaremos o formato de documentação em .rspec, para que ao utilizar o comando rspec, essa configuração seja chamada. Não precisando assim ser digitada (rspec --format documentation)
.rspec
--require spec_helper
--format documentation
2. Subject
Subject Implícito
Ao identificarmos a classe no método describe, eliminamos a necessidade de a instanciarmos.
Então podemos utilizar o método subject que faz referência à essa classe.
Veja o exemplo refatorado:
require'calculator'describeCalculatordocontext'use sum method for 2 numbers'doit'with positive numbers'doresult=subject.sum(5,7)expect(result).toeq(12)endit'with negative and positive numbers'doresult=subject.sum(-5,7)expect(result).toeq(2)endit'with negative numbers'doresult=subject.sum(-5, -7)expect(result).toeq(-12)endendend
Subject Explícito
Além do subject implícito, que vimos no tópico anterior, temos o subject explícito.
Que nada mais é do que alterar o nome padrão subject para qualquer outro que tenha um significado mais adequado.
Exemplo:
require'calculator'describeCalculatordosubject(:calculator){described_class.new()}context'use sum method for 2 numbers'doit'with positive numbers'doresult=calculator.sum(5,7)expect(result).toeq(12)endit'with negative and positive numbers'doresult=calculator.sum(-5,7)expect(result).toeq(2)endit'with negative numbers'doresult=calculator.sum(-5, -7)expect(result).toeq(-12)endendend
Subject com parâmetros
Caso a classe referenciada no describe necessite de parâmetros, esses devem ser passado via método subject().
Exemplos
Não alterando seu nome: subject(:subject) { described_class.new(param1, param2, ...) }
Alterando seu nome: subject(:calculator) { described_class.new(param1, param2, ...) }
Texto complementar no describe
Exemplo: describe Calculator, 'Sobre a calculadora' do; end
O subject mais interno vence!
Ou seja, quando tivermos describe aninhados, o subject se referirá ao mais interno.
Exemplo:
describe ClassePai do
describe ClasseFilha do
subject.metodo == ClasseFilha.metodo
end
end
One-liner syntax
É uma sintaxe mais enxuta, utiliza a descrição implícita it e, em algums casos, a troca da indicação do subject expect(subject) por is_expected.
Exemplo:
it: descreve o teste, mas se ele não tiver um corpo com o teste em si, ele será sinalizado como pendente.
Exemplo:
it 'with negative numbers'
xit: se quisermos sinalizar outros teste como pendente, mas que possuem corpo, devemos utilizar o método xit no lugar de it.
Exemplo:
xit 'with negative numbers' do
result = subject.sum(-5, -7)
expect(result).to eq(-12)
end
Rodando testes
Sabemos que podemos rodar um único arquivo de teste descrevendo o caminho para esse arquivo.
Exemplo: rspec spec/calculator/calculator_spec.rb
Mas também podemos rodar apenas um teste desse arquivo, utilizando sua descrição ou sua linha.
Exemplo:
calculator_spec.rb
require'calculator'describeCalculatordocontext'use sum method for 2 numbers'doit'with positive numbers'doresult=subject.sum(5,7)expect(result).toeq(12)endit'with negative and positive numbers'doresult=subject.sum(-5,7)expect(result).toeq(2)endit'with negative numbers'doresult=subject.sum(-5, -7)expect(result).toeq(-12)endendend
Exemplo utilizando a descrição do teste:
rspec spec/calculator/calculator_spec.rb -e 'with negative numbers'
Exemplo utilizando a linha em que o teste começa:
rspec spec/calculator/calculator_spec.rb:15
4. Matchers
Matchers são inúmeros comparadores utilizados para a criação de testes mais expressivos.
4.1 Matchers de Igualdade ( equal | be | eql | eq )
equal: testa os objetos, logo objetos diferentes com conteúdos igual serão considerados como diferentes.
Exemplo:
describe'Matchers de Comparação'doit'#equal - Testa se o mesmo objeto'dox='ruby'y='ruby'expect(x).to_notequal(y)endend
O teste passará.
be: o método be é um alias para o método equal.
eql: testa os valores, invés dos objetos como em equal e be.
Exemplo:
describe'Matchers de Comparação'doit'#eql - Testa os valores'dox='ruby'y='ruby'expect(x).toeql(y)endend
eq: o método eq é um alias para o método eql.
4.2 Matchers de Igualdade ( be true | be_truthy | be false | be_falsey | be_nil )
be true: testa se o resultado é verdadeiro.
Exemplo:
it'be true'doexpect(1.odd?).tobetrueend
be_truthy: testa se o resultado é verdadeiro e não nulo.
Exemplo:
it'be_truthy'doexpect(1.odd?).tobe_truthyend
be false: testa se o resultado é falso.
Exemplo:
it'be false'doexpect(1.even?).tobefalseend
be_falsey: testa se o resultado é falso e não nulo.
be_kind_of: compara se o resultado é do mesmo tipo da classe definida no parâmetro deste método, pertencendo exatamente a mesma classe ou tendo herdado dela.
Exemplo:
classStringNaoVazia < Stringdefinitializeself << 'Não sou vazio'endend
Todo método predicado(Clique aqui para saber mais) em Ruby pode ser convertido para um matcher predicado no RSpec, transformando .nil? em be_nil, por exemplo.
Exemplo:
4.7 Matchers de Erros ( raise_exception | raise_error )
raise_exception: um método genérico para testar se a expectativa leventará um erro.
Exemplo:
classCalculatordefdiv(a,b)a / bendend
require'calculator'describeCalculatordocontext'#div'doit'divide by 0'doexpect{subject.div(3,0)}.toraise_exceptionendendend
raise_error: é um método mais específico que o raise_exception pois aceita como parâmetro a classe do erro, a mensagem do erro, as duas ao mesmo tempo ou uma expressão regular.
É importante que a expectativa seja envolvida entre chaves {} em vez de parênteses () para que o RSpec possa tratá-la como exceção.
Exemplo:
classCalculatordefdiv(a,b)a / bendend
require'calculator'describeCalculatordocontext'#div'doit'divide by 0'doexpect{subject.div(3,0)}.toraise_error(ZeroDivisionError)expect{subject.div(3,0)}.toraise_error('divided by 0')expect{subject.div(3,0)}.toraise_error(ZeroDivisionError,'divided by 0')expect{subject.div(3,0)}.toraise_error(/divided/)endendend
4.8 Matchers para Arrays ( include | match_array | contain_exactly )
include: verifica se o(s) elemento(s), passados como argumento no método, estão incluídos no subject.
satisfy: verifica se o subject satisfaz determinada expressão.
Exemplo:
describe'satisfy'doit{expect(9).tosatisfy{ |num| num % 3 == 0}}# Versão mais verbosait{expect(9).tosatisfy('be a multiple of 3')do |num|
num % 3 == 0end}end
4.13 Matcher change
change: detecta quando uma determinada propriedade/método muda de estado.
require'contador'describe'Matcher change'do# verifica se há alteração do estado da classeit{expect{Contador.incrementa}.tochange{Contador.qtd}}# qtd == 1# verifica se há alteração e se ela ocorre da maneira especificada pelo método by()it{expect{Contador.incrementa}.tochange{Contador.qtd}.by(1)}# qtd == 2# verifica o estado inicial e final da alteraçãoit{expect{Contador.incrementa}.tochange{Contador.qtd}.from(2).to(3)}# qtd == 3end
4.14 Matcher output
Métodos que verificam as saídas do console.
stdout: standard out, se refere a saída padrão, comumente a tela.
A seguir veremos um exemplo de um matcher criado para verificar se um número é múltiplo de outro passado como argumento.
Exemplo:
spec/matchers/custom/custom_soec.rb
RSpec::Matchers.define:be_a_multiple_ofdo |expected|
# expected == 3# actual == subject == 18matchdo |actual|
actual % expected == 0end# mensagem de erro customizadafailure_messagedo |actual|
"expected that #{actual} would be a multiple of #{expected}"end# mensagem de êxito customizadadescriptiondo"be a multiple of #{expected}"endenddescribe18,'Custom Matcher'doit{is_expected.tobe_a_multiple_of(3)}end
Para mais informações sobre matchers customizados, clique aqui!
5. Composição de Expectativas
Pode-se compor as expectativas de duas maneiras, adicionando condições onde todas devem ser cumpridas and ou pelo menos uma delas or.
and
Exemplo:
describe'Ruby on Rails'doit{is_expected.tostart_with('Ruby').andend_with('Rails')}end
require_relative'../helpers/helper'RSpec.configuredo |conf|
# Se utilizado por mais testes, pode ser incluído no spec_helper.rb, não esquecendo de importá-lo.conf.includeHelperenddescribe'De módulo'doit{expect(fruta).toeq('banana').oreq('laranja').oreq('uva')}end
6.2 let e let!
let: permite a atribuição de uma variável de instância que é carregada apenas quando for utilizada pela primeira vez, ficando armazenada em cache até o término do teste em questão.
let!: força a invocação do método let antes de cada teste.
Exemplo:
$count =0describe'let!'doordem_de_invocacao=[]let!(:contador)doordem_de_invocacao << :let!
$count += 1endit'chama o método helper antes do teste'doordem_de_invocacao << :exemploexpect(ordem_de_invocacao).toeq([:let!,:exemplo])expect(contador).toeq(1)endend
7. Hooks
7.1 Before e After
Métodos incluídos no spec_helper.rb ou nos próprios arquivos de teste, que possibilitam a realização de ações antes e depois da execução dos testes.
before: realiza ações antes.
Exemplo:
spec/spec_helper.rb
RSpec.configuredo |config|
config.before(:suite)doputs'>>> é executado ANTES da execução de TODA a suite de testes'end# pode-se usar o alias :all para o :contextconfig.before(:context)doputs'>>> é executado ANTES da execução de TODOS os testes dentro do mesmo contexto'end# pode-se usar o alias :each para o :exampleconfig.before(:example)doputs'>>> é executado ANTES da execução de CADA teste'endend
after: realiza ações depois.
Exemplo:
spec/spec_helper.rb
RSpec.configuredo |config|
config.after(:suite)doputs'>>> é executado DEPOIS da execução de TODA a suite de testes'end# pode-se usar o alias :all para o :contextconfig.after(:context)doputs'>>> é executado DEPOIS da execução de TODOS os testes dentro do mesmo contexto'end# pode-se usar o alias :each para o :exampleconfig.after(:example)doputs'>>> é executado DEPOIS da execução de CADA teste'endend
Os métodos before() e after() também podem ser inseridos no próprio arquivo de testes.
Exemplo:
spec/matchers/atributos/atributos_spec.rb
describe'Atributos'dobefore(:example)doputs'>>> é executado ANTES de CADA teste'endafter(:example)doputs'>>> é executado DEPOIS de CADA teste'endit'#have_attributes'do@pessoa=Pessoa.new@pessoa.nome='Italo'@pessoa.idade=33expect(@pessoa).tohave_attributes(nome: 'Italo',idade: 33)endend
7.2 Around
Método que unifica os métodos anteriores before e after em um só, realizando ações antes e depois da execução dos testes.
require'pessoa'describe'Atributos'doaround(:example)do |test|
# BEFOREputs'>>> ANTES de CADA teste'test.run# AFTERputs'>>> DEPOIS de CADA teste'endit'#have_attributes'do@pessoa=Pessoa.new@pessoa.nome='Italo'@pessoa.idade=33expect(@pessoa).tohave_attributes(nome: 'Italo',idade: 33)endend
8. Agregando Falhas
Tem o objetivo de não interromper o teste após o surgimento da primeira falha, elas então são agregadas e mostradas somente depois que o teste finalizado.
aggregate_failures
Exemplo:
describe'Agregando falhas'doit'be_between / falhas agregadas'doaggregate_failuresdoexpect(2).tobe_between(2,7).inclusiveexpect(1).tobe_between(2,7).inclusiveexpect(8).tobe_between(2,7).inclusiveendend# Ou pode-se inserí-las no itit'be_between / falhas agregadas',:aggregate_failuresdoexpect(2).tobe_between(2,7).inclusiveexpect(1).tobe_between(2,7).inclusiveexpect(8).tobe_between(2,7).inclusiveendend
Podemos também definir o método aggregated_failures no spec_helper.rb para que valha para toda suite de testes.
Exemplo:
spec_helper.rb
Lembrando que os métodos it_behaves_like e it_should_behave_like são alias para o método include_examples.
Deve-se utilizar preferivelmente o método it_behaves_like pois o mesmo imprime a ação testada no console deixando o teste mais legível.
Recaptulando o método send
Envia um método para um objeto de forma dinâmica através de texto.
x='italo x.size> 5 y = 'size'
x.send(y)
> 5
10. Tag Filters key: true e key: value
São métodos que filtram os testes, com a finalidade de executar uma determinada parte deles.
key: true: neste exemplo, a chave terá o nome collection
Obs.: key: true tem o mesmo efeito de :key
Para executar os testes filtrados por essa tag collection, utilizamos os comandos: rspec . -t collection ou rspec . --tag collection
key: value: neste exemplo, a chave terá o nome type e o valor collection
E para executar os testes filtrados por essa tag type: collection, utilizamos os comandos: rspec . -t type:collection ou rspec . --tag type:collection
Para que não precisemos ficar adicionando as flags na hora de chamar os testes, podemos configurá-los no .rspec com a seguinte sintaxe: --tag collection para key:true e --tag type:collection para key:value, dessa maneira as flags serão incluídas automaticamente ao digitarmos o comando rspec
Excluindo testes com flags
Além da possibilidade de marcar testes que gostariamos que fossem realizados em conjunto, podemos também marcar testes que não queremos que rodem utilizando a flag slow em suas descrições.
Obs.: slow: true tem o mesmo efeito de :slow
Para que a tag tenha efeito, ela deve ser incuída no arquivo .rspec com a seguinte sintaxe: --tag ~slow
11. Test Doubles
Test Doubles ou Dublês de Teste, um termo genérico para qualquer objeto falso, utilizado no lugar de um objeto real, para propósitos de testes.
Em outras palavras, um dublê age como um objeto Ruby, que pode ou não aceitar "mensagens"(métodos).
Dublês são rigorosos/strict, ou seja, você precisa indicar quais mensagens ele aceitará.
Existem vários tipos de dublês de teste existentes, são:
mock object
stub
spy
fake object
dummy object
O RSpec implementa diretamente três deles, o stub, o mock e o spy.
Exemplo de mock object:
spec/test_doubles/user_spec.rb
describe'Test Double'doit'--'dousuario=double('User')# A classe User não existe# Maneira menos verbosaallow(usuario).toreceive_messages(name: 'Italo',password: 'secret')# Maneira mais verbosaallow(usuario).toreceive(:name).and_return('Italo')allow(usuario).toreceive(:password).and_return('secret')usuario.nameusuario.passwordendend
11.1 Stubs
Um stub é o ato de forçar uma resposta específica para um determinado método de um objeto colaborador.
São utilizando na fase de Arrange/Setup.
Stubs servem para substituir estados.
Exemplo:
lib/student.rb
classStudentattr_accessor:name,:emaildefhas_finished?(course)# retornaria true ou falseendend
lib/course.rb
classCourseattr_accessor:namedefcomplete?# retornaria true or falseendend
require'student'require'course'describe'Stub'doit'Qualquer instância de class'dostudent=Student.newother_student=Student.new# stuballow_any_instance_of(Student).toreceive(:bar).and_return(true)expect(student.bar).tobe_truthyexpect(other_student.bar).tobe_truthyendend
Testando erros
Permite stubar erros utilizando o método .and_raise(RuntimeError)
Exemplo:
lib/student.rb
São utilizando na fase de Assert/Verify.
Mocks servem para testar comportamentos, com a diferença que a ordem das fases do xUnit nesse caso é alterada de:
Arrange / Setup
Act / Exercise
Assert / Verify
para:
Arrange / Setup
Assert / Verify
Act / Exercise
No caso do exemplo a seguir será verificado se em determinado estudante ocorreu a chamada do método bar
Método que reconhece como nulo atributos que não foram permitidos allow(obj).to receive(:attr).and_return(value)
Exemplo:
spec/test_doubles/user_spec.rb
describe'Test Double'doit'as_null_object'dousuario=double('User').as_null_objectallow(usuario).toreceive(:name).and_return('Italo')allow(usuario).toreceive(:password).and_return('secret')usuario.nameusuario.passwordusuario.abcd# atributo não permitido, tem valor nuloendend
15. Configurando RSpec no Rails
No momento da criação da aplicação, utilizamos a flag -T para que não seja configurada a pasta de testes.
Exemplo: rails _version_ new app_name -T
RSpec Rails
Em seguida instalamos a gem do RSpec específica para o Rails, chamada rspec-rails.
Para isso vamos adicioná-la no grupo de desenvolvimento e teste no Gemfile da seguinte maneira:
E para efetivarmos essa instalação rodamos o comando bundle install
Caso a aplicação já esteja configurada e o RSpec for adicionado após isso, deve se verificar a presença de um banco de dados dedicado a testes em config/database.yml
Exemplo:
test:
<<: *defaultdatabase: db/test.sqlite3
Rodar o comando rails db:create:all para a criação dos bancos de dados de desenvolvimento, teste e produção.
E para gerar os arquivos de configuração padrão do RSpec rodamos o comando rails generate rspec:install
Os arquivos de configuração padrão do RSpec são: .rspec, spec, spec/spec_helper.rb, spec/rails_helper.rb
Lembrando que para habilitarmos a opção de formato de documentação precisamos inserir --format documentation em .rspec
O RSpec por padrão criará arquivos para teste de tudo que criarmos na aplicação e para limitarmos essas ações devemos configurar o config/applicarion.rb da seguinte maneira:
classApplication < Rails::Applicationconfig.load_defaults5.2# Don't generate system test files.config.generators.system_tests=nilconfig.generatorsdo |g|
g.test_framework:rspec,fixtures: false,view_specs: false,helper_specs: false,routing_specs: false,request_specs: falseendend
RSpec binstub
Para ganharmos mais velocidade na execução dos testes, utilizaremos a gem spring-commands-rspec que criará um binário que executará os comandos do RSpec diretamente em vez de serem executadas pelo bundle exec.
Para isso adicionamos a gem no grupo de desenvolvimento no Gemfile.
Exemplo:
group:developmentdogem'spring-commands-rspec'end
E para efetivarmos a instalação do spring-commands-rspec rodamos o comando bundle install
Para gerarmos os binários desejados, temos dois comandos:
bundle exec spring binstub rspec: para gerar o binário do RSpec
bundle exec spring binstub --all: para gerar o binário de tudo possivel, caso tenham gem configuradas para tal
E finalmente para que possamos executar o RSpec pelo binário recém criado utilizaremos o comando bin/rspec de agora em diante.
Capybara
Capybara é uma gem essencial para que os testes nas views sejam realizados, simulando como um usuário real interagiria com a aplicação.
E para instalarmos o capybara devemos adicioná-la no grupo de desenvolvimento e teste no Gemfile da seguinte maneira:
group:development,:testdogem'capybara'end
Para efetivarmos a instalação do capybara rodamos o comando bundle install
E por fim, configurá-la no spec_helper.rb dando um require no topo do arquivo require 'capybara/rspec'
Primeiramente precisaremos gerar dois models para que possamos testá-los:
Categoria: rails g model category description
Produto: rails g model product description price:decimal category:references
Caso tenhamos instalado o RSpec depois que os models tenham sido gerados, precisamos executar um comando para que os arquivos de teste sejam gerados: rails g rspec:model <nome-do-model>
16.1 Specs Básicas
Quando instanciado com atributos válidos, o model deve ser válido.
Validações devem ser testadas.
Métodos de classe e intância devem executar corretamente.
16.2.1 Quando instanciado com atributos válidos, o model deve ser válido.
It 'is valid with description, price and category'
spec/models/product_spec.rb
require'rails_helper'RSpec.describeProduct,type: :modeldoit'is valid with description, price and category'doproduto=create(:product)expect(produto).tobe_validendend
16.2.2 Validações devem ser testadas.
Descrição inválida
spec/models/product_spec.rb
require'rails_helper'RSpec.describeProduct,type: :modeldoit'is invalid without description'doproduto=build(:product,description: nil)produto.valid?expect(produto.errors[:description]).toinclude("can't be blank")endend
Preço inválido
spec/models/product_spec.rb
require'rails_helper'RSpec.describeProduct,type: :modeldoit'is invalid without price'doproduto=build(:product,price: nil)produto.valid?expect(produto.errors[:price]).toinclude("can't be blank")endend
Categoria inválida
spec/models/product_spec.rb
require'rails_helper'RSpec.describeProduct,type: :modeldoit'is invalid without category'doproduto=build(:product,category: nil)produto.valid?expect(produto.errors[:category]).toinclude('must exist')endend
16.2.3 Métodos de classe e intância devem executar corretamente.
São dados falsos que deixamos cadastrados para facilitar na hora dos testes, assim não precisamos preencher os atributos dos elementos que usaremos.
O RSpec configura as fixtures para uma pasta dedicada no diretório spec em rails_helper.rb. config.fixture_path = "#{::Rails.root}/spec/fixtures"
Então criaremos essa pasta e consecutivamente uma fixture para trabalharmos com o model Customer, e o arquivo ficará assim:
Exemplo:
spec/fixtures/customers.yml
require'rails_helper'RSpec.describeCustomer,type: :modeldofixtures:customersit'Create a Customer'docustomer=customers(:italo)expect(customer.full_name).toeq('Sr(a). Italo Fasanelli')endend
Quando temos um número considerável de fixtures, podemos alterar a chamada da fixture de fixtures :customers para fixtures :all, assim todas as fixtures serão importadas para o teste.
Primeiramente criaremos o diretório spec/factories para que possamos armazená-las.
Em seguida, vamos criar nossa primeira factory do model Customer, que ficará assim:
spec/factories/customer.rb
build(:factory_name): cria uma instância do objeto da factory mas não a salva no banco de dados do teste.
Exemplo:
spec/models/customer_spec.rb
require'rails_helper'RSpec.describeCustomer,type: :modeldoit'Create a Customer'docustomer=build(:customer)expect(customer.full_name).toeq('Sr(a). Italo Fasanelli')endend
create(:factory_name): cria uma instância do objeto da factory e a salva no banco de dados do teste.
Exemplo:
spec/models/customer_spec.rb
require'rails_helper'RSpec.describeCustomer,type: :modeldoit'Create a Customer'docustomer=create(:customer)expect(customer.full_name).toeq('Sr(a). Italo Fasanelli')endend
attributes_for: método que traz apenas os atributos, em um hash, do elemento passado como argumento.
Muito utilizado para testes de API, para testar JSON.
Exemplo:
spec/models/customer_spec.rb
require'rails_helper'RSpec.describeCustomer,type: :modeldoit'Usando o attributes_for'doattrs=attributes_for(:customer)attrs1=attributes_for(:customer_vip)putsattrsputsattrs1endend# Retorno:{:name=>"Carl Medhurst",:email=>"[email protected]",:vip=>false,:days_to_pay=>15}{:name=>"Saundra Hodkiewicz",:email=>"[email protected]",:vip=>true,:days_to_pay=>30}
Exemplo 2:
spec/models/customer_spec.rb
require'rails_helper'RSpec.describeCustomer,type: :modeldoit'Usando o attributes_for 2'doattrs=attributes_for(:customer)customer=Customer.create(attrs)expect(customer.full_name).tostart_with('Sr(a). ')endend
2.5 Herança
Herdará todos os atributos não definidos nas factories customizadas.
Exemplo:
spec/factories/customer.rb
Tem a finalidade de facilitar a criação do teste quando precisamos criar vários objetos. create_list(:factory, qt_objetos)
Exemplo:
spec/models/order_spec.rb
FactoryBot.definedofactory:customerdotransientdoqt_orders{3}endtrait:with_ordersdoafter(:create)do |customer,evaluator|
create_list(:order,evaluator.qt_orders,customer: customer)endend#Criando uma factory usando uma traitfactory:customer_with_orders,traits: [:with_orders]endend
Exemplo:
spec/models/order_spec.rb
require'rails_helper'RSpec.describeOrder,type: :modeldoit'Has_many'docustomer=create(:customer,:with_orders)expect(customer.orders.count).toeq(3)# Uma factory usando traitcustomer=create(:customer_with_orders)expect(customer.orders.count).toeq(3)#Sobrescrevendo a quantidade de orders, definimos 3 na factorycustomer=create(:customer,:with_orders,qt_orders: 5)expect(customer.orders.count).toeq(5)endend
2.14 Métodos extras
create_list(:factory, qt_elements): Cria uma lista de elementos no banco de dados de teste.
build_list(:factory, qt_elements): Cria uma lista de elementos em memória.
create_pair(:factory): Cria um par de elementos no banco de dados de teste.
build_pair(:factory): Cria um par de elementos em memória.
attributes_for(:factory): Extrai apenas os atributos de determinado objeto.
attributes_for_list(:factory): Extrai apenas os atributos de determinada lista de objetos.
build_sttubed(:factory): Cria um objeto falso(mockado, sttubado) a partir de uma factory.
build_sttubed_list(:factory): Cria uma lista de objetos falsos a partir de uma factory.
2.15 FactoryBot Lint
É uma ação tomada pelo FactoryBot em verificar as validações dos modelse outras configurações e reporta os erros do teste de maneira mais organizada.
Lembrando que essa ação exigirá mais do processamento dos testes.
Para saber mais sobre o FactoryBot Lint, clique aqui
spec/spec_helper.rb
Repositório Webmock
Invés de efetivamente acessarmos a internet como no tópico anterior, simularemos(stub) esse acesso com a finalidade de facilitar a execução do teste.
5.1 Instalação
Gemfile
group:development,:testdogem'webmock'end
E adicionamos require 'webmock/rspec' na primeira linha do arquivo spec/spec_helper.rb
5.2 Exemplo
spec/httparty/httparty_spec.rb
require'rails_helper'describe'HTTParty'doit'Content type - stub'dostub_request(:get,"https://jsonplaceholder.typicode.com/users/2").to_return(status: 200,body: '',headers: {'content-type': 'application/json'})response=HTTParty.get('https://jsonplaceholder.typicode.com/users/2')content_type=response.headers['content-type']expect(content_type).tomatch(/json/)endend
6. VCR
Repositório VCR
Grava as interações HTTP do conjunto de testes e as reproduz durante futuras execuções de teste para aumentar a velocidade e a precisão dos testes.
A configuração acima define o diretório spec/fixtures/vcr_cassettes como diretório onde serão criadas as cassetes.
Ao executarmos o teste pela primeira vez, ele irá armazenar neste diretório toda a resposta da requisição em um arquivo .yml e a partir da segunda vez que executarmos o teste, ele usará esse cassete.
6.2 Exemplo
spec/httparty/httparty_spec.rb
require'rails_helper'describe'HTTParty'doit'Content type - vcr'doVCR.use_cassette('jsonplaceholder/posts')doresponse=HTTParty.get('https://jsonplaceholder.typicode.com/users/2')content_type=response.headers['content-type']expect(content_type).tomatch(/json/)endendend
No caso acima, estamos configurando que para todas as ocorrências da URL do jsonplaceholder, a tag <URL-API> aparecerá em seu lugar.
Resultando no yml:
spec/fixtures/vcr_cassettes/HTTParty/content-type-vcr-metadata.yml
:once: Grava o cassete uma única vez, não recomendado para URIs não determinísticas. É configurado por padrão e chamado se nenhum outro modo for configurado.
Exemplo:
spec/httparty/httparty_spec.rb
it'Content type - vcr'doVCR.use_cassette('jsonplaceholder/posts')doresponse=HTTParty.get('https://jsonplaceholder.typicode.com/users/2')content_type=response.headers['content-type']expect(content_type).tomatch(/json/)endend
:new_episodes: A cada nova URL, uma nova cassete será gravada.
Exemplo:
spec/httparty/httparty_spec.rb
spec/rails_helper.rb
```ruby
RSpec.configure do |config|
config.include ActiveSupport::Testing::TimeHelpers
end
7.2 Métodos travel, travel_back e travel_to
:travel: Altera a hora atual para a hora no futuro ou no passado por uma determinada diferença de tempo, fazendo stub em Time.now, Date.today e DateTime.now.
Exemplos:
Time.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00travel1.dayTime.current# => Sun, 10 Nov 2013 15:34:49 EST -05:00Date.current# => Sun, 10 Nov 2013DateTime.current# => Sun, 10 Nov 2013 15:34:49 -0500
Esse método também aceita bloco, que retornarão o current_time ao seu estado original quanto o bloco terminar.
Time.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00travel1.daydoUser.create.created_at# => Sun, 10 Nov 2013 15:34:49 EST -05:00endTime.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00
:travel_back: Retorna a hora atual de volta ao seu estado original, removendo os stubs adicionados por travel e travel_to.
Exemplo:
Time.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00travel_toTime.zone.local(2004,11,24,01,04,44)Time.current# => Wed, 24 Nov 2004 01:04:44 EST -05:00travel_backTime.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00
:travel_to: Altera a hora atual para a hora fornecida, fazendo stub em Time.now, Date.today e DateTime.now para retornar a hora ou data passada para este método.
Exemplos:
Time.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00travel_toTime.zone.local(2004,11,24,01,04,44)Time.current# => Wed, 24 Nov 2004 01:04:44 EST -05:00Date.current# => Wed, 24 Nov 2004DateTime.current# => Wed, 24 Nov 2004 01:04:44 -0500
Este método também aceita um bloco, que retornará a hora atual de volta ao seu estado original no final do bloco:
Time.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00travel_toTime.zone.local(2004,11,24,01,04,44)doTime.current# => Wed, 24 Nov 2004 01:04:44 EST -05:00endTime.current# => Sat, 09 Nov 2013 15:34:49 EST -05:00