RSpec - Guia Rápido

RSpec é uma estrutura de teste de unidade para a linguagem de programação Ruby. RSpec é diferente de estruturas xUnit tradicionais como JUnit porque RSpec é uma ferramenta de desenvolvimento orientada por comportamento. O que isso significa é que os testes escritos em RSpec enfocam o “comportamento” de um aplicativo que está sendo testado. RSpec não coloca ênfase em como o aplicativo funciona, mas em como ele se comporta, em outras palavras, o que o aplicativo realmente faz.

Ambiente RSpec

Em primeiro lugar, você precisará instalar o Ruby em seu computador. No entanto, se você ainda não fez isso antes, pode baixar e instalar o Ruby do site principal do Ruby - Ruby .

Se você está instalando Ruby no Windows, você deve ter o instalador Ruby para Windows aqui em - http://www.rubyinstaller.org

Para este tutorial, você só precisará de um editor de texto, como o Bloco de notas e um console de linha de comando. Os exemplos aqui usarão cmd.exe no Windows.

Para executar o cmd.exe, basta clicar no menu Iniciar e digitar “cmd.exe” e, em seguida, pressionar a tecla Return.

No prompt de comando da janela cmd.exe, digite o seguinte comando para ver qual versão do Ruby você está usando -

ruby -v

Você deve ver a saída abaixo que é semelhante a esta -

ruby 2.2.3p173 (2015-08-18 revision 51636) [x64-mingw32]

Os exemplos neste tutorial usarão Ruby 2.2.3, mas qualquer versão de Ruby superior a 2.0.0 será suficiente. Em seguida, precisamos instalar a gem RSpec para a instalação do Ruby. Uma gema é uma biblioteca Ruby que você pode usar em seu próprio código. Para instalar uma gema, você precisa usar ogem comando.

Vamos instalar a gem Rspec agora. Volte para a janela cmd.exe e digite o seguinte -

gem install rspec

Você deve ter uma lista de gems dependentes que foram instaladas, são gems de que a gem rspec precisa para funcionar corretamente. No final da saída, você deve ver algo parecido com isto -

Done installing documentation for diff-lcs, rspec-support, rspec-mocks,
   rspec-expectations, rspec-core, rspec after 22 seconds 
6 gems installed

Não se preocupe se sua saída não for exatamente a mesma. Além disso, se você estiver usando um computador Mac ou Linux, pode ser necessário executargem install rspec comando usando sudo ou use uma ferramenta como o HomeBrew ou RVM para instalar a gem rspec.

Hello World

Para começar, vamos criar um diretório (pasta) para armazenar nossos arquivos RSpec. Na janela cmd.exe, digite o seguinte -

cd \

Então digite -

mkdir rspec_tutorial

E, finalmente, digite -

cd rspec_tutorial

A partir daqui, vamos criar outro diretório chamado spec, faça isso digitando -

mkdir spec

Vamos armazenar nossos arquivos RSpec nesta pasta. Os arquivos RSpec são conhecidos como “especificações”. Se isso parecer confuso para você, pense em um arquivo de especificação como um arquivo de teste. RSpec usa o termo “especificação”, que é uma forma abreviada de “especificação”.

Como o RSpec é uma ferramenta de teste BDD, o objetivo é focar no que o aplicativo faz e se segue ou não uma especificação. No desenvolvimento orientado por comportamento, a especificação é freqüentemente descrita em termos de uma “História de Usuário”. RSpec é projetado para deixar claro se o código de destino está se comportando corretamente, em outras palavras, seguindo a especificação.

Voltemos ao nosso código Hello World. Abra um editor de texto e adicione o seguinte código -

class HelloWorld

   def say_hello 
      "Hello World!"
   end
   
end

describe HelloWorld do 
   context “When testing the HelloWorld class” do 
      
      it "should say 'Hello World' when we call the say_hello method" do 
         hw = HelloWorld.new 
         message = hw.say_hello 
         expect(message).to eq "Hello World!"
      end
      
   end
end

Em seguida, salve-o em um arquivo denominado hello_world_spec.rb na pasta spec que você criou acima. Agora, de volta à janela cmd.exe, execute este comando -

rspec spec spec\hello_world_spec.rb

Quando o comando for concluído, você deverá ver uma saída semelhante a esta -

Finished in 0.002 seconds (files took 0.11101 seconds to load) 
1 example, 0 failures

Parabéns, você acabou de criar e executar seu primeiro teste de unidade RSpec!

Na próxima seção, continuaremos a discutir a sintaxe dos arquivos RSpec.

Vamos dar uma olhada no código de nosso HelloWorldexemplo. Em primeiro lugar, caso não esteja claro, estamos testando a funcionalidade doHelloWorldclasse. Obviamente, esta é uma classe muito simples que contém apenas um métodosay_hello().

Aqui está o código RSpec novamente -

describe HelloWorld do 
   context “When testing the HelloWorld class” do 
      
      it "The say_hello method should return 'Hello World'" do 
         hw = HelloWorld.new 
         message = hw.say_hello 
         expect(message).to eq "Hello World!" 
      end
      
   end 
end

Descrever a palavra-chave

A palavra describeé uma palavra-chave RSpec. É usado para definir um “Grupo de exemplo”. Você pode pensar em um “Grupo de exemplo” como uma coleção de testes. odescribepalavra-chave pode receber um nome de classe e / ou argumento de string. Você também precisa passar um argumento de bloco paradescribe, este conterá os testes individuais, ou como são conhecidos no RSpec, os “Exemplos”. O bloco é apenas um bloco Ruby designado pelo Rubydo/end palavras-chave.

A palavra-chave de contexto

o context palavra-chave é semelhante a describe. Ele também pode aceitar um nome de classe e / ou argumento de string. Você deve usar um bloco comcontexttambém. A ideia de contexto é que ele inclui testes de um certo tipo.

Por exemplo, você pode especificar grupos de exemplos com contextos diferentes como este -

context “When passing bad parameters to the foobar() method” 
context “When passing valid parameters to the foobar() method” 
context “When testing corner cases with the foobar() method”

o context palavra-chave não é obrigatória, mas ajuda a adicionar mais detalhes sobre os exemplos que ela contém.

A palavra-chave it

A palavra ité outra palavra-chave RSpec que é usada para definir um “Exemplo”. Um exemplo é basicamente um teste ou caso de teste. De novo comodescribe e context, it aceita o nome da classe e os argumentos da string e deve ser usado com um argumento de bloco, designado com do/end. No caso deit, é comum passar apenas uma string e um argumento de bloco. O argumento string geralmente usa a palavra "deveria" e se destina a descrever qual comportamento específico deve acontecer dentro doit block. Em outras palavras, descreve que o resultado esperado é para o exemplo.

Note o it block do nosso exemplo HelloWorld -

it "The say_hello method should return 'Hello World'" do

A string deixa claro o que deve acontecer quando chamamos say hello em uma instância da classe HelloWorld. Esta parte da filosofia RSpec, um exemplo não é apenas um teste, é também uma especificação (uma especificação). Em outras palavras, um exemplo documenta e testa o comportamento esperado de seu código Ruby.

A palavra-chave esperada

o expectpalavra-chave é usada para definir uma “expectativa” em RSpec. Esta é uma etapa de verificação em que verificamos se uma condição esperada específica foi atendida.

Do nosso exemplo HelloWorld, temos -

expect(message).to eql "Hello World!"

A ideia com expectafirmações é que eles são lidos como inglês normal. Você pode dizer isso em voz alta como “Espere que a mensagem variável seja igual à string 'Hello World'”. A ideia é que seja descritiva e também fácil de ler, mesmo para partes interessadas não técnicas, como gerentes de projeto.

The to keyword

o to palavra-chave é usada como parte de expectafirmações. Observe que você também pode usar onot_topalavra-chave para expressar o oposto, quando você deseja que a Expectativa seja falsa. Você pode ver que para é usado com um ponto,expect(message).to,porque na verdade é apenas um método Ruby normal. Na verdade, todas as palavras-chave RSpec são apenas métodos Ruby.

The eql keyword

o eqlpalavra-chave é uma palavra-chave RSpec especial chamada Matcher. Você usa Matchers para especificar que tipo de condição você está testando como verdadeira (ou falsa).

Em nosso HelloWorld expect declaração, é claro que eqlsignifica igualdade de string. Observe que existem diferentes tipos de operadores de igualdade em Ruby e, consequentemente, diferentes Matchers correspondentes em RSpec. Exploraremos os diversos tipos de Matchers em uma seção posterior.

Neste capítulo, iremos criar uma nova classe Ruby, salvá-la em seu próprio arquivo e criar um arquivo spec separado para testar esta classe.

Primeiro, em nossa nova classe, é chamado StringAnalyzer. É uma classe simples que, você adivinhou, analisa strings. Nossa classe tem apenas um métodohas_vowels?que, como o próprio nome sugere, retorna verdadeiro se uma string contém vogais e falso se não contém. Aqui está a implementação paraStringAnalyzer -

class StringAnalyzer 
   def has_vowels?(str) 
      !!(str =~ /[aeio]+/i) 
   end 
end

Se você seguiu a seção HelloWorld, criou uma pasta chamada C: \ rspec_tutorial \ spec.

Exclua o arquivo hello_world.rb se o tiver e salve o código StringAnalyzer acima em um arquivo denominado string_analyzer.rb na pasta C: \ rspec_tutorial \ spec.

Aqui está a fonte de nosso arquivo de especificações para testar StringAnalyzer -

require 'string_analyzer' 

describe StringAnalyzer do 
   context "With valid input" do 
      
      it "should detect when a string contains vowels" do 
         sa = StringAnalyzer.new 
         test_string = 'uuu' 
         expect(sa.has_vowels? test_string).to be true 
      end 
		
      it "should detect when a string doesn't contain vowels" do 
         sa = StringAnalyzer.new 
         test_string = 'bcdfg' 
         expect(sa.has_vowels? test_string).to be false
      end 
      
   end 
end

Salve-o no mesmo diretório de especificações, dando a ele o nome string_analyzer_test.rb.

Na janela cmd.exe, faça cd para a pasta C: \ rspec_tutorial e execute este comando: dir spec

Você deve ver o seguinte -

Diretório de C: \ rspec_tutorial \ spec

09/13/2015 08:22 AM  <DIR>    .
09/13/2015 08:22 AM  <DIR>    ..
09/12/2015 11:44 PM                 81 string_analyzer.rb
09/12/2015 11:46 PM              451 string_analyzer_test.rb

Agora vamos executar nossos testes, execute este comando: rspec spec

Quando você passa o nome de uma pasta para rspec, ele executa todos os arquivos de especificação dentro da pasta. Você deve ver este resultado -

No examples found.

Finished in 0 seconds (files took 0.068 seconds to load)
0 examples, 0 failures

O motivo pelo qual isso aconteceu é que, por padrão, rspecexecuta apenas arquivos cujos nomes terminam em “_spec.rb”. Renomeie string_analyzer_test.rb para string_analyzer_spec.rb. Você pode fazer isso facilmente executando este comando -

ren spec\string_analyzer_test.rb string_analyzer_spec.rb

Agora corra rspec especificação novamente, você deve ver uma saída parecida com esta -

F.
Failures:

   1) StringAnalyzer With valid input should detect when a string contains vowels
      Failure/Error: expect(sa.has_vowels? test_string).to be true 
         expected true
            got false
      # ./spec/string_analyzer_spec.rb:9:in `block (3 levels) in <top (required)>'

Finished in 0.015 seconds (files took 0.12201 seconds to load)
2 examples, 1 failure

Failed examples:
rspec ./spec/string_analyzer_spec.rb:6 # StringAnalyzer With valid 
   input should detect when a string contains vowels
Do you see what just happened? Our spec failed because we have a bug in 
   StringAnalyzer. The bug is simple to fix, open up string_analyzer.rb
   in a text editor and change this line:
!!(str =~ /[aeio]+/i)
to this:
!!(str =~ /[aeiou]+/i)

Agora, salve as alterações que você acabou de fazer em string_analyizer.rb e execute o comando rspec spec novamente, você deve ver uma saída semelhante a -

..
Finished in 0.002 seconds (files took 0.11401 seconds to load)
2 examples, 0 failures

Parabéns, os exemplos (testes) em seu arquivo de especificações agora estão passando. Corrigimos um bug na expressão regular que tem o método de vogais, mas nossos testes estão longe de estar completos.

Faria sentido adicionar mais exemplos que testassem vários tipos de strings de entrada com o método has vowels.

A tabela a seguir mostra algumas das permutações que podem ser adicionadas em novos exemplos (ele bloqueia)

String de entrada Descrição Resultado esperado com has_vowels?
'aaa', 'eee', 'iii', 'o' Apenas uma vogal e nenhuma outra letra. verdadeiro
'abcefg' 'Pelo menos uma vogal e algumas consoantes' verdadeiro
'mnklp' Apenas consoantes. falso
'' String vazia (sem letras) falso
'abcde55345 & ??' Vogais, consoantes, números e caracteres de pontuação. verdadeiro
'423432 %%% ^ &' Apenas números e caracteres de pontuação. falso
'AEIOU' Apenas vogais maiúsculas. verdadeiro
'AeiOuuuA' Apenas vogais maiúsculas e vogais inferiores. verdadeiro
'AbCdEfghI' Vogais maiúsculas e minúsculas e consoantes. verdadeiro
'BCDFG' Apenas consoantes maiúsculas. falso
'' Apenas caracteres de espaço em branco. falso

Cabe a você decidir quais exemplos adicionar ao seu arquivo de especificações. Existem muitas condições a serem testadas, você precisa determinar qual subconjunto de condições é mais importante e testar seu código da melhor forma.

o rspec comando oferece muitas opções diferentes, para ver todos eles, digite rspec-Socorro. A tabela a seguir lista as opções mais populares e descreve o que elas fazem.

Sr. Não. Opção / sinalizador e descrição
1

-I PATH

Adiciona PATH ao caminho de carregamento (requer) que rspec usa ao procurar por arquivos fonte Ruby.

2

-r, --require PATH

Adiciona um arquivo de origem específico a ser exigido em sua especificação. arquivos).

3

--fail-fast

Com esta opção, rspec irá parar de executar as especificações após a falha do primeiro exemplo. Por padrão, rspec executa todos os arquivos de especificação especificados, não importa quantas falhas existam.

4

-f, --format FORMATTER

Esta opção permite especificar diferentes formatos de saída. Consulte a seção sobre Formatadores para obter mais detalhes sobre os formatos de saída.

5

-o, --out FILE

Esta opção direciona o rspec para gravar os resultados do teste no arquivo de saída FILE em vez de na saída padrão.

6

-c, --color

Ativa a cor na saída do rspec. Os resultados do exemplo de sucesso serão exibidos em texto verde, as falhas serão impressas em texto vermelho.

7

-b, --backtrace

Exibe backtraces completos de erros na saída do rspec.

8

-w, --warnings

Exibe avisos Ruby na saída do rspec.

9

-P, --pattern PATTERN

Carregue e execute arquivos de especificação que correspondam ao padrão PATTERN. Por exemplo, se você passar -p “* .rb”, rspec executará todos os arquivos Ruby, não apenas aqueles que terminam em “_spec.rb”.

10

-e, --example STRING

Esta opção direciona o rspec para executar todos os exemplos que contêm o texto STRING em suas descrições.

11

-t, --tag TAG

Com esta opção, o rspec executará apenas exemplos que contenham a tag TAG. Observe que TAG é especificado como um símbolo Ruby. Consulte a seção sobre Tags RSpec para obter mais detalhes.

Se você se lembra do nosso exemplo original do Hello World, ele continha uma linha parecida com esta -

expect(message).to eq "Hello World!"

A palavra-chave eql é um RSpec“Matcher”. Aqui, apresentaremos os outros tipos de matchers no RSpec.

Equality / Identity Matchers

Matchers para testar a igualdade de objeto ou valor.

Matcher Descrição Exemplo
eq Passa quando real == esperado esperar (real). para eq esperado
eql Passa quando actual.eql? (Esperado) esperar (real). para eql esperado
estar Passa quando actual.equal? ​​(Esperado) esperar (real) .para ser esperado
igual Também é aprovado quando actual.equal? ​​(Esperado) esperar (real). igual ao esperado

Exemplo

describe "An example of the equality Matchers" do 

   it "should show how the equality Matchers work" do 
      a = "test string" 
      b = a 
      
      # The following Expectations will all pass 
      expect(a).to eq "test string" 
      expect(a).to eql "test string" 
      expect(a).to be b 
      expect(a).to equal b 
   end
   
end

Quando o código acima for executado, ele produzirá a seguinte saída. O número de segundos pode ser ligeiramente diferente no seu computador -

.
Finished in 0.036 seconds (files took 0.11901 seconds to load)
1 example, 0 failures

Comparison Matchers

Matchers para comparação com valores.

Matcher Descrição Exemplo
> Passa quando real> esperado esperar (real). ser> esperado
> = Passa quando real> = esperado esperar (real). ser> = esperado
< Passa quando real <esperado esperar (real). ser <esperado
<= Passa quando real <= esperado esperar (real). ser <= esperado
estar_entre inclusivo Passa quando o real é <= min e> = max esperar (real). estar entre (min, max) .inclusive
ser_entre exclusivo Passa quando o real é <min e> max esperar (real). estar entre (min, max) .exclusivo
partida Passa quando o real corresponde a uma expressão regular esperar (real) .para corresponder (/ regex /)

Exemplo

describe "An example of the comparison Matchers" do

   it "should show how the comparison Matchers work" do
      a = 1
      b = 2
      c = 3		
      d = 'test string'
      
      # The following Expectations will all pass
      expect(b).to be > a
      expect(a).to be >= a 
      expect(a).to be < b 
      expect(b).to be <= b 
      expect(c).to be_between(1,3).inclusive 
      expect(b).to be_between(1,3).exclusive 
      expect(d).to match /TEST/i 
   end
   
end

Quando o código acima for executado, ele produzirá a seguinte saída. O número de segundos pode ser ligeiramente diferente no seu computador -

. 
Finished in 0.013 seconds (files took 0.11801 seconds to load) 
1 example, 0 failures

Correspondentes de classe / tipo

Matchers para testar o tipo ou classe de objetos.

Matcher Descrição Exemplo
be_instance_of Passa quando real é uma instância da classe esperada. expect (real) .to be_instance_of (esperado)
be_kind_of Passa quando real é uma instância da classe esperada ou qualquer uma de suas classes pai. expect (real) .to be_kind_of (esperado)
respond_to Passa quando o real responde ao método especificado. esperar (real). para responder_to (esperado)

Exemplo

describe "An example of the type/class Matchers" do
 
   it "should show how the type/class Matchers work" do
      x = 1 
      y = 3.14 
      z = 'test string' 
      
      # The following Expectations will all pass
      expect(x).to be_instance_of Fixnum 
      expect(y).to be_kind_of Numeric 
      expect(z).to respond_to(:length) 
   end
   
end

Quando o código acima for executado, ele produzirá a seguinte saída. O número de segundos pode ser ligeiramente diferente no seu computador -

. 
Finished in 0.002 seconds (files took 0.12201 seconds to load) 
1 example, 0 failures

Matchers verdadeiro / falso / nulo

Matchers para testar se um valor é verdadeiro, falso ou nulo.

Matcher Descrição Exemplo
seja verdadeiro Passa quando real == verdadeiro esperar (real). ser verdade
ser falso Passa quando real == falso esperar (real). ser falso
be_truthy Passa quando o real não é falso ou nulo esperar (real). ser_truthy
be_falsey Passa quando o real é falso ou nulo esperar (real). ser_falsey
be_nil Passa quando o real é nulo esperar (real). ser_nil

Exemplo

describe "An example of the true/false/nil Matchers" do
   it "should show how the true/false/nil Matchers work" do
      x = true 
      y = false 
      z = nil 
      a = "test string" 
      
      # The following Expectations will all pass
      expect(x).to be true 
      expect(y).to be false 
      expect(a).to be_truthy 
      expect(z).to be_falsey 
      expect(z).to be_nil 
   end 
end

Quando o código acima for executado, ele produzirá a seguinte saída. O número de segundos pode ser ligeiramente diferente no seu computador -

. 
Finished in 0.003 seconds (files took 0.12301 seconds to load) 
1 example, 0 failures

Matchers de erros

Matchers para teste, quando um bloco de código gera um erro.

Matcher Descrição Exemplo
raise_error (ErrorClass) Passa quando o bloco gera um erro do tipo ErrorClass. esperar {block} .to raise_error (ErrorClass)
raise_error ("mensagem de erro") Passa quando o bloco levanta um erro com a mensagem “mensagem de erro”. esperar {bloquear} .para elevar_error (“mensagem de erro”)
raise_error (ErrorClass, "mensagem de erro") É aprovado quando o bloco gera um erro do tipo ErrorClass com a mensagem “mensagem de erro” esperar {bloquear} .para elevar_error (ErrorClass, “mensagem de erro”)

Exemplo

Salve o seguinte código em um arquivo com o nome error_matcher_spec.rb e execute-o com este comando - rspec error_matcher_spec.rb.

describe "An example of the error Matchers" do 
   it "should show how the error Matchers work" do 
      
      # The following Expectations will all pass 
      expect { 1/0 }.to raise_error(ZeroDivisionError)
      expect { 1/0 }.to raise_error("divided by 0") 
      expect { 1/0 }.to raise_error("divided by 0", ZeroDivisionError) 
   end 
end

Quando o código acima for executado, ele produzirá a seguinte saída. O número de segundos pode ser ligeiramente diferente no seu computador -

. 
Finished in 0.002 seconds (files took 0.12101 seconds to load) 
1 example, 0 failures

Neste capítulo, discutiremos RSpec Doubles, também conhecidos como RSpec Mocks. Um Double é um objeto que pode “substituir” outro objeto. Você provavelmente está se perguntando o que isso significa exatamente e por que precisa de um.

Digamos que você esteja construindo um aplicativo para uma escola e tenha uma classe representando uma classe de alunos e outra classe para alunos, ou seja, você tem uma classe Sala de Aula e uma classe Aluno. Você precisa escrever o código para uma das classes primeiro, então vamos dizer que, comece com a classe Sala de aula -

class ClassRoom 
   def initialize(students) 
      @students = students 
   end 
   
   def list_student_names 
      @students.map(&:name).join(',') 
   end 
end

Esta é uma classe simples, ela tem um método list_student_names, que retorna uma string delimitada por vírgulas de nomes de alunos. Agora, queremos criar testes para esta classe, mas como faremos isso se ainda não criamos a classe Aluno? Precisamos de um teste duplo.

Além disso, se tivermos uma classe “fictícia” que se comporta como um objeto Student, nossos testes ClassRoom não dependerão da classe Student. Chamamos isso de isolamento de teste.

Se nossos testes de ClassRoom não dependem de nenhuma outra classe, então quando um teste falha, podemos saber imediatamente que há um bug em nossa classe ClassRoom e não em alguma outra classe. Lembre-se de que, no mundo real, você pode estar construindo uma aula que precisa interagir com outra aula escrita por outra pessoa.

É aqui que RSpec Doubles (mocks) se tornam úteis. Nosso método list_student_names chama o método name em cada objeto Student em sua variável de membro @students. Portanto, precisamos de um Double que implemente um método de nome.

Aqui está o código para ClassRoom junto com um exemplo RSpec (teste), mas observe que não há nenhuma classe de aluno definida -

class ClassRoom 
   def initialize(students) 
      @students = students 
   end
   
   def list_student_names 
      @students.map(&:name).join(',') 
   end 
end

describe ClassRoom do 
   it 'the list_student_names method should work correctly' do 
      student1 = double('student') 
      student2 = double('student') 
      
      allow(student1).to receive(:name) { 'John Smith'} 
      allow(student2).to receive(:name) { 'Jill Smith'} 
      
      cr = ClassRoom.new [student1,student2]
      expect(cr.list_student_names).to eq('John Smith,Jill Smith') 
   end 
end

Quando o código acima for executado, ele produzirá a seguinte saída. O tempo decorrido pode ser ligeiramente diferente no seu computador -

. 
Finished in 0.01 seconds (files took 0.11201 seconds to load) 
1 example, 0 failures

Como você pode ver, usando um test doublepermite que você teste seu código mesmo quando ele depende de uma classe indefinida ou indisponível. Além disso, isso significa que, quando há uma falha no teste, você pode dizer imediatamente que é por causa de um problema em sua aula e não de uma aula escrita por outra pessoa.

Se você já leu a seção RSpec Doubles (também conhecido como Mocks), então você já viu Stubs RSpec. No RSpec, um stub é frequentemente chamado de Stub de Método, é um tipo especial de método que "substitui" um método existente ou um método que ainda não existe.

Aqui está o código da seção RSpec Doubles -

class ClassRoom 
   def initialize(students) 
      @students = students 
   End
   
   def list_student_names 
      @students.map(&:name).join(',') 
   end 
end 

describe ClassRoom do 
   it 'the list_student_names method should work correctly' do 
      student1 = double('student') 
      student2 = double('student') 
      
      allow(student1).to receive(:name) { 'John Smith'}
      allow(student2).to receive(:name) { 'Jill Smith'} 
      
      cr = ClassRoom.new [student1,student2]
      expect(cr.list_student_names).to eq('John Smith,Jill Smith') 
   end 
end

Em nosso exemplo, o método allow () fornece os stubs de método que precisamos para testar a classe ClassRoom. Nesse caso, precisamos de um objeto que funcione como uma instância da classe Aluno, mas essa classe (ainda) não existe. Sabemos que a classe Student precisa fornecer um método name () e usamos allow () para criar um stub de método para name ().

Uma coisa a observar é que a sintaxe do RSpec mudou um pouco ao longo dos anos. Em versões mais antigas do RSpec, os stubs de método acima seriam definidos assim -

student1.stub(:name).and_return('John Smith') 
student2.stub(:name).and_return('Jill Smith')

Vamos pegar o código acima e substituir os dois allow() linhas com a sintaxe RSpec antiga -

class ClassRoom 
   def initialize(students) 
      @students = students 
   end 
   
   def list_student_names 
      @students.map(&:name).join(',') 
   end 
	
end 

describe ClassRoom do 
   it 'the list_student_names method should work correctly' do 
      student1 = double('student') 
      student2 = double('student')
      
      student1.stub(:name).and_return('John Smith')
      student2.stub(:name).and_return('Jill Smith') 
      
      cr = ClassRoom.new [student1,student2] 
      expect(cr.list_student_names).to eq('John Smith,Jill Smith') 
   end 
end

Você verá esta saída quando executar o código acima -

.
Deprecation Warnings:

Using `stub` from rspec-mocks' old `:should` syntax without explicitly 
   enabling the syntax is deprec 

ated. Use the new `:expect` syntax or explicitly enable `:should` instead. 
   Called from C:/rspec_tuto 

rial/spec/double_spec.rb:15:in `block (2 levels) in <top (required)>'.
If you need more of the backtrace for any of these deprecations 
   to identify where to make the necessary changes, you can configure 

`config.raise_errors_for_deprecations!`, and it will turn the 
   deprecation warnings into errors, giving you the full backtrace.

1 deprecation warning total

Finished in 0.002 seconds (files took 0.11401 seconds to load)
1 example, 0 failures

É recomendável que você use a nova sintaxe allow () quando precisar criar stubs de método em seus exemplos RSpec, mas fornecemos o estilo mais antigo aqui para que você o reconheça se o vir.

Quando você está escrevendo testes de unidade, geralmente é conveniente executar o código de configuração e desmontagem antes e depois dos testes. Código de instalação é o código que configura ou “define” as condições para um teste. O código Teardown faz a limpeza, garante que o ambiente esteja em um estado consistente para os testes subsequentes.

De modo geral, seus testes devem ser independentes uns dos outros. Quando você executa um conjunto inteiro de testes e um deles falha, você quer ter certeza de que ele falhou porque o código que está testando tem um bug, não porque o teste anterior deixou o ambiente em um estado inconsistente.

Os ganchos mais comuns usados ​​no RSpec são antes e depois dos ganchos. Eles fornecem uma maneira de definir e executar o código de configuração e desmontagem que discutimos acima. Vamos considerar este código de exemplo -

class SimpleClass 
   attr_accessor :message 
   
   def initialize() 
      puts "\nCreating a new instance of the SimpleClass class" 
      @message = 'howdy' 
   end 
   
   def update_message(new_message) 
      @message = new_message 
   end 
end 

describe SimpleClass do 
   before(:each) do 
      @simple_class = SimpleClass.new 
   end 
   
   it 'should have an initial message' do 
      expect(@simple_class).to_not be_nil
      @simple_class.message = 'Something else. . .' 
   end 
   
   it 'should be able to change its message' do
      @simple_class.update_message('a new message')
      expect(@simple_class.message).to_not be 'howdy' 
   end
end

Ao executar este código, você obterá a seguinte saída -

Creating a new instance of the SimpleClass class 
. 
Creating a new instance of the SimpleClass class 
. 
Finished in 0.003 seconds (files took 0.11401 seconds to load) 
2 examples, 0 failures

Vamos dar uma olhada mais de perto no que está acontecendo. O método before (: each) é onde definimos o código de configuração. Quando você passa o: each argumento, você está instruindo o método before a ser executado antes de cada exemplo em seu Grupo de Exemplo, ou seja, os dois blocos it dentro do bloco describe no código acima.

Na linha: @simple_class = SimpleClass.new, estamos criando uma nova instância da classe SimpleClass e atribuindo-a a uma variável de instância de um objeto. Que objeto você deve estar se perguntando? RSpec cria uma classe especial nos bastidores no escopo do bloco de descrição. Isso permite que você atribua valores a variáveis ​​de instância desta classe, que você pode acessar dentro dos blocos it em seus exemplos. Isso também torna mais fácil escrever um código mais limpo em nossos testes. Se cada teste (exemplo) precisa de uma instância de SimpleClass, podemos colocar esse código no gancho before e não ter que adicioná-lo a cada exemplo.

Observe que a linha "Criando uma nova instância da classe SimpleClass" é escrita no console duas vezes, isso mostra que, antes de o gancho ser chamado em cada um dos it blocks.

Como mencionamos, RSpec também tem um gancho posterior e ambos os ganchos antes e depois podem receber: tudo como um argumento. O gancho posterior será executado após o destino especificado. O destino: all significa que o gancho será executado antes / depois de todos os exemplos. Aqui está um exemplo simples que ilustra quando cada gancho é chamado.

describe "Before and after hooks" do 
   before(:each) do 
      puts "Runs before each Example" 
   end 
   
   after(:each) do 
      puts "Runs after each Example" 
   end 
   
   before(:all) do 
      puts "Runs before all Examples" 
   end 
   
   after(:all) do 
      puts "Runs after all Examples"
   end 
   
   it 'is the first Example in this spec file' do 
      puts 'Running the first Example' 
   end 
   
   it 'is the second Example in this spec file' do 
      puts 'Running the second Example' 
   end 
end

Ao executar o código acima, você verá esta saída -

Runs before all Examples 
Runs before each Example 
Running the first Example 
Runs after each Example 
.Runs before each Example 
Running the second Example 
Runs after each Example 
.Runs after all Examples

RSpec Tags fornecem uma maneira fácil de executar testes específicos em seus arquivos de especificação. Por padrão, o RSpec executará todos os testes nos arquivos de especificação que executa, mas talvez você só precise executar um subconjunto deles. Digamos que você tenha alguns testes que são executados muito rapidamente e que acabou de fazer uma alteração no código do seu aplicativo e deseja apenas executar os testes rápidos, este código irá demonstrar como fazer isso com tags RSpec.

describe "How to run specific Examples with Tags" do 
   it 'is a slow test', :slow = > true do 
      sleep 10 
      puts 'This test is slow!' 
   end 
   
   it 'is a fast test', :fast = > true do 
      puts 'This test is fast!' 
   end 
end

Agora, salve o código acima em um novo arquivo chamado tag_spec.rb. Na linha de comando, execute este comando: rspec --tag slow tag_spec.rb

Você verá esta saída -

Opções de execução: incluir {: lento => verdadeiro}

This test is slow! 
. 
Finished in 10 seconds (files took 0.11601 seconds to load) 
1 example, 0 failures

Em seguida, execute este comando: rspec --tag fast tag_spec.rb

Você verá esta saída -

Run options: include {:fast = >true} 
This test is fast! 
. 
Finished in 0.001 seconds (files took 0.11201 seconds to load) 
1 example, 0 failures

Como você pode ver, as tags RSpec facilitam muito a execução de um subconjunto de testes!

Um dos pontos fortes do RSpec é que ele fornece muitas maneiras de escrever testes, limpar testes. Quando seus testes são curtos e organizados, torna-se mais fácil focar no comportamento esperado e não nos detalhes de como os testes são escritos. Os assuntos RSpec são mais um atalho que permite a você escrever testes simples e diretos.

Considere este código -

class Person 
   attr_reader :first_name, :last_name 
   
   def initialize(first_name, last_name) 
      @first_name = first_name 
      @last_name = last_name 
   end 
end 

describe Person do 
   it 'create a new person with a first and last name' do
      person = Person.new 'John', 'Smith'
      
      expect(person).to have_attributes(first_name: 'John') 
      expect(person).to have_attributes(last_name: 'Smith') 
   end 
end

Na verdade, está bem claro como está, mas poderíamos usar o recurso de assunto do RSpec para reduzir a quantidade de código no exemplo. Fazemos isso movendo a instanciação do objeto pessoa para a linha de descrição.

class Person 
   attr_reader :first_name, :last_name 
   
   def initialize(first_name, last_name) 
      @first_name = first_name 
      @last_name = last_name 
   end 
	
end 

describe Person.new 'John', 'Smith' do 
   it { is_expected.to have_attributes(first_name: 'John') } 
   it { is_expected.to have_attributes(last_name: 'Smith') }
end

Ao executar este código, você verá esta saída -

.. 
Finished in 0.003 seconds (files took 0.11201 seconds to load) 
2 examples, 0 failures

Observe como o segundo exemplo de código é muito mais simples. Nós pegamos aqueleit block no primeiro exemplo e substituí-lo por dois it blocks que acabam exigindo menos código e são igualmente claros.

Às vezes, seus exemplos RSpec precisam de uma maneira fácil de compartilhar código reutilizável. A melhor maneira de fazer isso é com ajudantes. Helpers são basicamente métodos regulares de Ruby que você compartilha entre exemplos. Para ilustrar o benefício de usar ajudantes, vamos considerar este código -

class Dog 
   attr_reader :good_dog, :has_been_walked 
   
   def initialize(good_or_not) 
      @good_dog = good_or_not 
      @has_been_walked = false 
   end 
   
   def walk_dog 
      @has_been_walked = true 
   end 
end 

describe Dog do 
   it 'should be able to create and walk a good dog' do 
      dog = Dog.new(true) 
      dog.walk_dog 
      
      expect(dog.good_dog).to be true
      expect(dog.has_been_walked).to be true 
   end 
   
   it 'should be able to create and walk a bad dog' do 
      dog = Dog.new(false) 
      dog.walk_dog 

      expect(dog.good_dog).to be false
      expect(dog.has_been_walked).to be true 
 
   end 
end

Este código é claro, mas é sempre uma boa ideia reduzir códigos repetidos sempre que possível. Podemos pegar o código acima e reduzir parte dessa repetição com um método auxiliar chamado create_and_walk_dog ().

class Dog
   attr_reader :good_dog, :has_been_walked 
   
   def initialize(good_or_not)
      @good_dog = good_or_not 
      @has_been_walked = false 
   end 
   
   def walk_dog 
      @has_been_walked = true 
   end 
end 

describe Dog do 
   def create_and_walk_dog(good_or_bad)
      dog = Dog.new(good_or_bad)
      dog.walk_dog
      return dog 
   end 
   
   it 'should be able to create and walk a good dog' do
      dog = create_and_walk_dog(true)
      
      expect(dog.good_dog).to be true
      expect(dog.has_been_walked).to be true 
   end 
   
   it 'should be able to create and walk a bad dog' do 
      dog = create_and_walk_dog(false)
      
      expect(dog.good_dog).to be false
      expect(dog.has_been_walked).to be true 
   end 
end

Ao executar o código acima, você verá esta saída -

.. 
Finished in 0.002 seconds (files took 0.11401 seconds to load) 
2 examples, 0 failures

Como você pode ver, fomos capazes de empurrar a lógica para criar e levar um objeto cachorro para um Helper, o que permite que nossos exemplos sejam mais curtos e claros.

RSpec é uma ferramenta flexível e poderosa. A funcionalidade de metadados no RSpec não é exceção. Metadados geralmente se referem a “dados sobre dados”. No RSpec, isso significa dados sobre o seudescribe, context e it blocks.

Vamos dar uma olhada em um exemplo -

RSpec.describe "An Example Group with a metadata variable", :foo => 17 do 
   context 'and a context with another variable', :bar => 12 do 
      
      it 'can access the metadata variable of the outer Example Group' do |example| 
         expect(example.metadata[:foo]).to eq(17) 
      end
      
      it 'can access the metadata variable in the context block' do |example|  
         expect(example.metadata[:bar]).to eq(12) 
      end 
      
   end 
end

Ao executar o código acima, você verá esta saída -

.. 
Finished in 0.002 seconds (files took 0.11301 seconds to load) 
2 examples, 0 failures

Os metadados fornecem uma maneira de atribuir variáveis ​​em vários escopos em seus arquivos RSpec. A variável example.metadata é um hash Ruby que contém outras informações sobre seus grupos de exemplos e exemplos.

Por exemplo, vamos reescrever o código acima para ficar assim -

RSpec.describe "An Example Group with a metadata variable", :foo => 17 do
   context 'and a context with another variable', :bar => 12 do 
      
      it 'can access the metadata variable in the context block' do |example|
         expect(example.metadata[:foo]).to eq(17) 
         expect(example.metadata[:bar]).to eq(12) 
         example.metadata.each do |k,v|
         puts "#{k}: #{v}"
      end
		
   end 
end

Quando executamos este código, vemos todos os valores no hash example.metadata -

.execution_result: #<RSpec::Core::Example::ExecutionResult:0x00000002befd50>
block: #<Proc:0x00000002bf81a8@C:/rspec_tutorial/spec/metadata_spec.rb:7>
description_args: ["can access the metadata variable in the context block"]
description: can access the metadata variable in the context block
full_description: An Example Group with a metadata variable and a context 
   with another variable can access the metadata variable in the context block
described_class:
file_path: ./metadata_spec.rb
line_number: 7
location: ./metadata_spec.rb:7
absolute_file_path: C:/rspec_tutorial/spec/metadata_spec.rb
rerun_file_path: ./metadata_spec.rb
scoped_id: 1:1:2
foo: 17
bar: 12
example_group:
{:execution_result=>#<RSpec::Core::Example::ExecutionResult:
   0x00000002bfa0e8>, :block=>#<
   Proc:0x00000002bfac00@C:/rspec_tutorial/spec/metadata_spec.rb:2>, 
   :description_args=>["and a context with another variable"], 
	
   :description=>"and a context with another variable", 
   :full_description=>"An Example Group with a metadata variable
   and a context with another variable", :described_class=>nil, 
      :file_path=>"./metadata_spec.rb", 
		
   :line_number=>2, :location=>"./metadata_spec.rb:2", 
      :absolute_file_path=>"C:/rspec_tutorial/spec/metadata_spec.rb",
      :rerun_file_path=>"./metadata_spec.rb", 
		
   :scoped_id=>"1:1", :foo=>17, :parent_example_group=>
      {:execution_result=>#<
      RSpec::Core::Example::ExecutionResult:0x00000002c1f690>, 
      :block=>#<Proc:0x00000002baff70@C:/rspec_tutorial/spec/metadata_spec.rb:1>
      , :description_args=>["An Example Group with a metadata variable"], 
		
   :description=>"An Example Group with a metadata variable", 
   :full_description=>"An Example Group with a metadata variable", 
	:described_class=>nil, :file_path=>"./metadata_spec.rb", 
   :line_number=>1, :location=>"./metadata_spec.rb:1",
   :absolute_file_path=>
	
   "C:/rspec_tutorial/spec/metadata_spec.rb", 
   :rerun_file_path=>"./metadata_spec.rb", 
   :scoped_id=>"1", :foo=>17}, 
   :bar=>12}shared_group_inclusion_backtrace: [] 
	
last_run_status: unknown .
.
Finished in 0.004 seconds (files took 0.11101 seconds to load) 
2 examples, 0 failures

Provavelmente, você não precisará usar todos esses metadados, mas observe o valor da descrição completa -

Um grupo de exemplo com uma variável de metadados e um contexto com outra variável pode acessar a variável de metadados no bloco de contexto.

Esta é uma frase criada a partir da descrição do bloco de descrição + a descrição do bloco de contexto contido + a descrição do it block.

O que é interessante notar aqui é que essas três strings juntas parecem uma frase normal em inglês. . . que é uma das ideias por trás do RSpec, com testes que parecem descrições de comportamento em inglês.

Você pode querer ler a seção sobre Metadados RSpec antes de ler esta seção porque, ao que parece, a filtragem RSpec é baseada em Metadados RSpec.

Imagine que você tem um arquivo de especificações e ele contém dois tipos de testes (exemplos): testes funcionais positivos e testes negativos (erros). Vamos defini-los assim -

RSpec.describe "An Example Group with positive and negative Examples" do 
   context 'when testing Ruby\'s build-in math library' do
      
      it 'can do normal numeric operations' do 
         expect(1 + 1).to eq(2) 
      end 
      
      it 'generates an error when expected' do
         expect{1/0}.to raise_error(ZeroDivisionError) 
      end
      
   end 
end

Agora, salve o texto acima como um arquivo chamado 'filter_spec.rb' e execute-o com este comando -

rspec filter_spec.rb

Você verá uma saída parecida com isto -

.. 
Finished in 0.003 seconds (files took 0.11201 seconds to load) 
2 examples, 0 failures

Agora, e se quisermos executar novamente apenas os testes positivos neste arquivo? Ou apenas os testes negativos? Podemos fazer isso facilmente com filtros RSpec. Altere o código acima para este -

RSpec.describe "An Example Group with positive and negative Examples" do 
   context 'when testing Ruby\'s build-in math library' do
      
      it 'can do normal numeric operations', positive: true do 
         expect(1 + 1).to eq(2) 
      end 
      
      it 'generates an error when expected', negative: true do 
         expect{1/0}.to raise_error(ZeroDivisionError) 
      end
      
   end 
end

Salve suas alterações em filter_spec.rb e execute este comando ligeiramente diferente -

rspec --tag positive filter_spec.rb

Agora, você verá uma saída semelhante a esta -

Run options: include {:positive=>true} 
. 
Finished in 0.001 seconds (files took 0.11401 seconds to load) 
1 example, 0 failures

Ao especificar --tag positivo, estamos dizendo ao RSpec para executar exemplos apenas com a variável de metadados: positive definida. Poderíamos fazer a mesma coisa com testes negativos executando o comando como este -

rspec --tag negative filter_spec.rb

Lembre-se de que esses são apenas exemplos, você pode especificar um filtro com qualquer nome que desejar.

Formatadores RSpec

Os formatadores permitem que o RSpec exiba a saída dos testes de maneiras diferentes. Vamos criar um novo arquivo RSpec contendo este código -

RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do 
   context 'when running some tests' do 
      
      it 'the test usually calls the expect() method at least once' do 
         expect(1 + 1).to eq(2) 
      end
      
   end 
end

Agora, salve em um arquivo chamado formatter_spec.rb e execute este comando RSpec -

rspec formatter_spec.rb

Você deve ver uma saída parecida com esta -

. 
Finished in 0.002 seconds (files took 0.11401 seconds to load) 
1 example, 0 failures

Agora execute o mesmo comando, mas desta vez especifique um formatador, como este -

rspec --format progress formatter_spec.rb

Você deve ver a mesma saída desta vez -

. 
Finished in 0.002 seconds (files took 0.11401 seconds to load) 
1 example, 0 failures

O motivo é que o formatador de “progresso” é o formatador padrão. Vamos tentar um formatador diferente a seguir, tente executar este comando -

rspec --format doc formatter_spec.rb

Agora você deve ver esta saída -

A spec file to demonstrate how RSpec Formatters work 
   when running some tests 
      the test usually calls the expect() method at least once
Finished in 0.002 seconds (files took 0.11401 seconds to load) 
1 example, 0 failures

Como você pode ver, a saída é bem diferente com o formatador “doc”. Este formatador apresenta a saída em um estilo semelhante a documentação. Você pode estar se perguntando como são essas opções quando ocorre uma falha em um teste (exemplo). Vamos mudar o código emformatter_spec.rb parecer assim -

RSpec.describe "A spec file to demonstrate how RSpec Formatters work" do 
   context 'when running some tests' do 
      
      it 'the test usually calls the expect() method at least once' do 
         expect(1 + 1).to eq(1) 
      end
      
   end 
end

A expectativa expect(1 + 1).to eq(1)deve falhar. Salve suas alterações e execute novamente os comandos acima -

rspec --format progress formatter_spec.rb e lembre-se, como o formatador de “progresso” é o padrão, você pode simplesmente executar: rspec formatter_spec.rb. Você deve ver esta saída -

F 
Failures:
1) A spec file to demonstrate how RSpec Formatters work when running some tests 
the test usually calls the expect() method at least once
   Failure/Error: expect(1 + 1).to eq(1)
	
      expected: 1
         got: 2
			  
      (compared using ==)			  
   # ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>'

Finished in 0.016 seconds (files took 0.11201 seconds to load)
1 example, 1 failure
Failed examples:

rspec ./formatter_spec.rb:3 # A spec file to demonstrate how RSpec 
   Formatters work when running some tests the test usually calls 
   the expect() method at least once

Agora, vamos tentar o formatador doc, execute este comando -

rspec --format doc formatter_spec.rb

Agora, com o teste que falhou, você deve ver esta saída -

A spec file to demonstrate how RSpec Formatters work
   when running some tests
      the test usually calls the expect() method at least once (FAILED - 1)
		
Failures:

1) A spec file to demonstrate how RSpec Formatters work when running some
   tests the test usually calls the expect() method at least once
   Failure/Error: expect(1 + 1).to eq(1)
	
   expected: 1
        got: 2
		  
   (compared using ==)
   # ./formatter_spec.rb:4:in `block (3 levels) in <top (required)>'
	
Finished in 0.015 seconds (files took 0.11401 seconds to load) 
1 example, 1 failure

Exemplos com falha

rspec ./formatter_spec.rb:3 # Um arquivo de especificações para demonstrar como os formatadores RSpec funcionam ao executar alguns testes, o teste geralmente chama o método expect () pelo menos uma vez.

Os formatadores RSpec oferecem a capacidade de alterar a forma como os resultados do teste são exibidos, é até possível criar seu próprio formatador personalizado, mas esse é um tópico mais avançado.

Quando você aprende RSpec, pode ler muito sobre expectativas e pode ser um pouco confuso no início. Existem dois detalhes principais que você deve ter em mente ao ver o termo Expectativa -

  • Uma expectativa é simplesmente uma declaração em um it block que usa o expect()método. É isso aí. Não é mais complicado do que isso. Quando você tem um código como este:expect(1 + 1).to eq(2), você tem uma Expectativa em seu exemplo. Você está esperando que a expressão1 + 1 avalia para 2. A formulação é importante, pois RSpec é uma estrutura de teste BDD. Chamando essa declaração de Expectation, fica claro que seu código RSpec está descrevendo o “comportamento” do código que está testando. A ideia é que você esteja expressando como o código deve se comportar, de uma forma que pareça documentação.

  • A sintaxe da expectativa é relativamente nova. Antes de oexpect() método foi introduzido (em 2012), RSpec usou uma sintaxe diferente que foi baseada no should()método. A Expectation acima é escrita assim na sintaxe antiga:(1 + 1).should eq(2).

Você pode encontrar a sintaxe RSpec antiga para Expectativas ao trabalhar com um código mais antigo baseado ou uma versão anterior do RSpec. Se você usar a sintaxe antiga com uma nova versão do RSpec, verá um aviso.

Por exemplo, com este código -

RSpec.describe "An RSpec file that uses the old syntax" do
   it 'you should see a warning when you run this Example' do 
      (1 + 1).should eq(2) 
   end 
end

Ao executá-lo, você obterá uma saída semelhante a esta -

. Deprecation Warnings:

Using `should` from rspec-expectations' old `:should` 
   syntax without explicitly enabling the syntax is deprecated. 
   Use the new `:expect` syntax or explicitly enable 
	
`:should` with `config.expect_with( :rspec) { |c| c.syntax = :should }`
   instead. Called from C:/rspec_tutorial/spec/old_expectation.rb:3 :in 
   `block (2 levels) in <top (required)>'.

If you need more of the backtrace for any of these deprecations to
   identify where to make the necessary changes, you can configure 
`config.raise_errors_for_deprecations!`, and it will turn the deprecation 
   warnings into errors, giving you the full backtrace.

1 deprecation warning total 
Finished in 0.001 seconds (files took 0.11201 seconds to load) 
1 example, 0 failures

A menos que seja necessário usar a sintaxe antiga, é altamente recomendável que você use expect () em vez de should ().