RSpec no Rails

Aqui está um tutorial introdutório para quem está interessado em começar com RSpec.

Behaviour Driven Development ou BDD é um processo de desenvolvimento ágil que compreende aspectos de aceitação Test Driven Planning, Domain Driven Design and Test Driven Development. Rspec é uma ferramenta destinada a BDD TDD no contexto do TDC.

Você poderia dizer que RSpec é o que é tradicionalmente conhecido como uma estrutura de testes de unidade, mas nós preferimos descrevê-lo como “uma Linguagem de domínio Especifico para descrever o comportamento esperado de um sistema com exemplos executáveis.”

Além disso, o processo que este tutorial orienta é Test Driven Development em sua essência, mas nós usamos palavras como “comportamento” e “exemplo” em vez de “caso de teste” e “método de teste”.

Pré-requisitos

Ruby 1.8.4 ou mais recente
A última gem RSpec (0.9.4 como esta escrito).

Para instalar a gem RSpec, abra o shell de comando e digite …

> gem install rspec

Introdução

 

Para este exemplo, vamos descrever e desenvolver os princípios de uma classe de usuário, o que pode ser atribuído qualquer número de papéis. Comece criando um diretório para os arquivos desse tutorial:

> mkdir rspec_tutorial
> cd rspec_tutorial

 

Primeiro Exemplo

RSpec fornece um domínio específico de linguagem para descrever o comportamento esperado de um sistema com exemplos executáveis. Os primeiros métodos que vamos encontrar são describe e it. Estes métodos utilizados para ter outros nomes (que ainda são suportadas, mas geralmente não é recomendado), mas usamos describe e it porque eles podem levá-lo a pensar mais sobre o comportamento da estrutura.

Usando o seu editor favorito, crie um arquivo neste diretório chamado user_spec.rb e digite o seguinte:

describe User do
end

O método describe cria uma instância do Behaviour. Assim, “descrever Usuário” está realmente dizendo descrever o comportamento da classe User”. Acho que poderia ter chamado o método describe_the_behaviour_of, mas há um ponto em que a clareza solavancos até contra a verbosidade, e nós sentimos que ponto é antes do primeiro sublinhado no describe_the_behaviour_of.

No shell, digite o seguinte comando:

> spec user_spec.rb

O comando spec é instalado quando você instala o gem rspec. Ele suporta um grande número de opções de linha de comando. A maioria das opções estão fora do escopo deste tutorial, mas você pode aprender sobre eles, executando o comando sem argumentos:

> spec

Voltando à tarefa em mãos, executando spec user_spec.rb poderia ter resultado na produção, que inclui o seguinte erro:

./user_spec.rb:1: uninitialized constant User (NameError)

Nós ainda não escrevemos nenhum exemplo e o RSpec já está nos dizendo que precisamos escrever o código. Precisamos criar uma classe de usuário para resolver esse erro, então crie user.rb com o seguinte:

class User

end

E exigem em user_spec.rb:

require 'user'

describe User do
end

Agora, execute o comando spec novamente.

$ spec user_spec.rb

Finished in 6.0e-06 seconds

0 examples, 0 failures

O resultado mostra que não temos exemplos ainda, então vamos adicionar um. Vamos começar por descrever a intenção do exemplo, sem qualquer código.

describe User do
  it "should be in any roles assigned to it" do
  end
end

O método it retorna uma instância de Example. Esta é uma metáfora para um exemplo do comportamento que estamos descrevendo.

Leia em voz alta. É muito gratificante. Podemos agradecer a Dan North para os nomes describe e it.

Execute o spec, mas desta vez adicionar a opção --format:

$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it

Finished in 0.022865 seconds

1 example, 0 failures

O formato specdoc emite o nome de cada Behaviour (o objeto criado pelo método describe) e cada exemplo (o objeto criado pelo método it). Este formato vem TestDox, uma ferramenta que produz um relatório semelhante a partir dos nomes de TestCases JUnit e métodos dentro.

Agora adicione uma declaração Ruby que começa a expressar a intenção descrita.

describe User do
  it "should be in any roles assigned to it" do
    user.should be_in_role("assigned role")
  end
end

… E execute o comando spec.

$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it (ERROR - 1)

1)
NameError in 'User should be in any roles assigned to it'
undefined local variable or method `user' for #<#<Class:0x14ed15c>:0x14ecdd8>
./user_spec.rb:6:

Finished in 0.017956 seconds

1 example, 1 failure

Há um par de coisas a notar sobre essa saída. Em primeiro lugar, o texto “(ERROR – 1)” diz-nos que houve um erro no “deve ser em todas as funções que lhe são atribuídas” exemplo. O “1″ nos diz que à medida que se deslocar até o relatório de falha detalhada que esta falha em particular é descrito em “1)”. Isto irá tornar mais útil como o número de exemplos aumenta.

Outra coisa a notar é a ausência de qualquer referência ao código RSpec no registro de chamadas. Filtros RSpec que por padrão, mas você pode ver toda a backtrace adicionando a opção --backtrace ao comando.

A saída nos diz que não há user, então o próximo passo é fazer um:

describe User do
  it "should be in any roles assigned to it" do
    user = User.new
    user.should be_in_role("assigned role")
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it (ERROR - 1)

1)
NoMethodError in 'User should be in any roles assigned to it'
undefined method `in_role?' for #<User:0x14ec8ec>
./user_spec.rb:7:

Finished in 0.020779 seconds

1 example, 1 failure

Agora ficamos a saber que o usuário não responder a in_role?, por isso acrescentar que para Usuário:

class User
  def in_role?(role)
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it (FAILED - 1)

1)
'User should be in any roles assigned to it' FAILED
expected in_role?("assigned role") to return true, got nil
./user_spec.rb:7:

Finished in 0.0172110000000001 seconds

1 example, 1 failure

Temos agora um exemplo de falha, que é o primeiro objetivo. Nós sempre queremos ver uma falha significativa antes do sucesso, porque essa é a única maneira de ter certeza que o sucesso é o resultado de escrever código no lugar certo no sistema.

Para obter esta a passar, nós fazemos a coisa mais simples que poderia funcionar:

class User
  def in_role?(role)
    true
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it

Finished in 0.018173 seconds

1 example, 0 failures

Que passa, mas nós não terminamos ainda. Dê uma olhada novamente no exemplo:

describe User do
  it "should be in any roles assigned to it" do
    user = User.new
    user.should be_in_role("assigned role")
  end
end

Será que expressar a intenção de descrever? Não totalmente. A descrição diz que o usuário “deve ser em todas as funções que lhe são atribuídas”, mas não atribuiu quaisquer funções a ele. Vamos acrescentar que a atribuição para o exemplo:

describe User do
  it "should be in any roles assigned to it" do
    user = User.new
    user.assign_role("assigned role")
    user.should be_in_role("assigned role")
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it (ERROR - 1)

1)
NoMethodError in 'User should be in any roles assigned to it'
undefined method `assign_role' for #<User:0x14ec784>
./user_spec.rb:6:

Finished in 0.018564 seconds

1 example, 1 failure

Seguindo o conselho na saída, agora adicionar o método assign_role para User.

class User
  def in_role?(role)
    true
  end

  def assign_role(role)
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it

Finished in 0.018998 seconds

1 example, 0 failures

O exemplo está passando novamente, mas estás feito? Desloque-se algumas linhas e dê uma olhada na implementação atual do User. Eu acho que é justo dizer que esta não é a implementação que sabemos que queremos. E este é o ponto no processo que faz TDD “Test-Driven”. Ao invés de implementar o código que pensamos que sabemos que queremos, vamos proceder de acordo com a orientação do princípio de que “o código não existe até que seja testada.”

Agora, a única exigência do sistema que temos é que expressa um “Usuário deve estar em todas as funções que lhe são atribuídas”, e o sistema atende a essa exigência. A fim de empurrar o código para o próximo passo, é necessário para expressar mais exigências com mais exemplos executáveis.

Segundo Exemplo

Como as coisas estão agora, um User vai responder true quando você perguntar a ele se é qualquer papel, independentemente de ter sido atribuído esse papel. Queremos que o User diga que não está em um papel que não foi atribuído, então vamos adicionar esse exemplo:
describe User do
  it "should be in any roles assigned to it" do
    user = User.new
    user.assign_role("assigned role")
    user.should be_in_role("assigned role")
  end

  it "should NOT be in any roles not assigned to it" do
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it

Finished in 0.018231 seconds

2 examples, 0 failures

Agora adicione uma declaração para expressar a intenção:

describe User do
  it "should be in any roles assigned to it" do
    user = User.new
    user.assign_role("assigned role")
    user.should be_in_role("assigned role")
  end

  it "should NOT be in any roles not assigned to it" do
    user.should_not be_in_role("unassigned role")
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it (ERROR - 1)

1)
NameError in 'User should NOT be in any roles not assigned to it'
undefined local variable or method `user' for #<#<Class:0x14eca54>:0x14ebce4>
./user_spec.rb:11:

Finished in 0.018465 seconds

2 examples, 1 failure

Agora crie o User:

describe User do
  it "should be in any roles assigned to it" do
    user = User.new
    user.assign_role("assigned role")
    user.should be_in_role("assigned role")
  end

  it "should NOT be in any roles not assigned to it" do
    user = User.new
    user.should_not be_in_role("unassigned role")
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it (FAILED - 1)

1)
'User should NOT be in any roles not assigned to it' FAILED
expected in_role?("unassigned role") to return false, got true
./user_spec.rb:12:

Finished in 0.019014 seconds

2 examples, 1 failure

Mais uma vez temos um exemplo que está a falhar na maneira que nós queremos que ele falhe – a intenção é corretamente expressa, mas o código não está se comportando como esperado.

Fazer a coisa mais simples que poderia funcionar, podemos obter o exemplo de passar assim:

class User
  def in_role?(role)
    role == "assigned role"
  end

  def assign_role(role)
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it

Finished in 0.017194 seconds

2 examples, 0 failures

Tudo passa, mas agora temos a duplicação entre os exemplos eo código assunto. Tempo para refatorar para remover a duplicação!

class User
  def in_role?(role)
    role == @role
  end

  def assign_role(role)
    @role = role
  end
end
$ spec user_spec.rb --format specdoc

User
- should be in any roles assigned to it
- should NOT be in any roles not assigned to it

Finished in 0.018199 seconds

2 examples, 0 failures

Neste ponto, você provavelmente se sentem como se a implementação não está certo ainda. Esse sentimento é baseado no pressuposto de que devemos ser capazes de atribuir qualquer número de funções a um User. Parte do pressuposto de que vem do nosso exemplo inicial: “deve ser em todas as funções que lhe são atribuídas”. Antes de mergulhar e alterando a implementação, este é um grande momento para perguntar ao cliente se esse pressuposto está correto! Se a resposta é que a User só pode estar em um papel de cada vez, então estamos a fazer com o código, mas que provavelmente deve voltar a frase os exemplos de ler “deve estar no papel que lhe é atribuído” e “não deve estar em um papel não atribuído a ele. ”

Se a resposta é que a User pode estar em mais de uma função de cada vez, então temos mais trabalho a fazer. Eu vou abordar esse cenário na Parte II deste tutorial. Fique atento …

 

Fonte: Blog Davidchelimsky