final, mas ela pode ficar melhor. O que queremos testar é que existem 2 palavras
na bag, e não que o array words tem 2 elementos. Perceba que os dois modos
de pensar dão no mesmo resultado, mas pensar em palavras dentro do saco está
mais perto do domínio do nossso problema do que número de elementos de um
array. Podemos melhorar a expressividade do nosso teste usando um matcher mais
apropriado para esse contexto, que nesse caso seria o Have Matcher.
Seguindo essa idéia, reescreva a expectation do nosso teste para ficar assim:
require "spec_helper"
require "bag_of_words"
describe BagOfWords do
it "is possible to put words on it" do
bag = BagOfWords.new
bag.put("hello", "world")
expect(bag).to have(2).words
end
end
E rode o teste novamente:
$ bundle exec rspec --format documentation
30
Casa do Código
Capítulo 3. Introdução ao básico do RSpec
(...)
Failures:
1) BagOfWords is possible to put words on it
Failure/Error: expect(bag).to have(2).words
expected 2 words, got 0
Perceba como a mensagem de erro mudou. A mensagem agora fala: expected
2 words, got 0. A mensagem agora faz bem mais sentido em relação ao domínio do
nosso problema! Perceba também como fica diferente ler a nova expectation com-
parada a antiga:
# antiga
expect(bag.words.size).to eq(2)
# nova
expect(bag).to have(2).words
Não só a mensagem de erro ficou mais expressiva como também o próprio có-
digo do nosso teste também ficou! Essa é a vantagem de se usar o matcher correto. É
por isso que o RSpec vem com mais de 30 matchers, para que nossos testes sejam ex-
pressivos e para que possamos aplicar a idéia do BDD de escrever testes que sirvam
como especificação e documentação do nosso código.
Bom, agora que já estamos usando o matcher correto, vale a pena fazermos o
teste passar. Basta fazermos com que o método BagOfWords#put concatene os
seus argumentos no array de @words interno. Modifique esse método no arquivo
lib/bag_of_words.rb para ficar assim:
class BagOfWords
attr_reader :words
def initialize
@words = []
end
def put(*words)
@words += words
end
end
31
3.2. Porquê existem tantos matchers no RSpec
Casa do Código
Agora ao rodar os testes, vemos que eles passam:
$ bundle exec rspec --format documentation
BagOfWords
is possible to put words on it
Finished in 0.00139 seconds
1 example, 0 failures
Agora estamos prontos para dar uma olhada nos built-in matchers do RSpec (que
são os matchers padrão do RSpec).
Curiosidade: a manipulação do $LOAD_PATH feita pelo RSpec
Para entender essa curiosidade, você deve saber que em Ruby ao fazer require
"file_path", o Ruby vai procurar o arquivo file_path dentro da variável global $LOAD_PATH e vai carregá-lo se achar.
No teste que fizemos acima, você deve se lembrar que demos require do
spec_helper e do arquivo bag_of_words:
# spec/bag_of_words_spec.rb
require "spec_helper"
require "bag_of_words"
describe BagOfWords do
# (...)
end
Ao ler o código de testes de outros projetos com
RSpec, é possível que você veja
que a pessoa está inserindo o diretório lib/ explicitamente no $LOAD_PATH para
conseguir dar require em um arquivo desse diretório. Um exemplo desse tipo de
código poderia ser assim:
require "spec_helper"
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib") require "bag_of_words"
Há tempos atrás eu me perguntei porque que algumas pessoas ficavam colo-
cando o diretório lib/ explicitamente dentro do $LOAD_PATH e outras não. Acon-
tece que no RSpec, é desnecessário fazer isso, porque ele já faz isso por nós. Isso é 32
Casa do Código
Capítulo 3. Introdução ao básico do RSpec
feito pelo rspec-core (https://github.com/rspec/rspec-core/tree/v2.13.1/) , no arquivo lib/core/load_path.rb:
require 'rspec/core/ruby_project'
RSpec::Core::RubyProject.add_to_load_path('lib', 'spec')
Portanto, não esqueça, não é necessário colocar o diretório lib/ e spec/ ex-
plicitamente no $LOAD_PATH, o RSpec já faz isso por nós.
3.3
Conhecendo os RSpec built-in matchers
Um matcher no RSpec é um objeto que serve para verificar o comportamento es-
perado no nosso teste. Ele é usado para montar uma expectation do RSpec de dois
modos diferentes:
expect(actual).to matcher(expected)
expect(actual).to_not matcher(expected)
Um exemplo para cada um dos modos de expectation acima pode ser:
expect(1).to eq(1)
expect(1).to_not eq(2)
Como mencionado na seção anterior, o RSpec vem com muitos built-in mat-
chers para nos ajudar a escrever testes expressivos. Vamos dar uma olhada neles,
começando pelos mais básicos, os "be matchers.
3.4
Matchers relacionados a truthy e falsy
Os be matchers servem para você testar se um objeto é avaliado como true ou
false. Você pode usá-los do seguinte modo:
expect(obj).to be_true # passa se obj é truthy (não nil ou não false)
expect(obj).to be_false # passa se obj é falsy (nil ou false)
expect(obj).to be_nil
# passa se obj é nil
expect(obj).to be
# passa se obj é truthy (não nil ou não false)
Note que os be matchers já seriam o suficiente para fazer qualquer tipo de teste