Não Teste! Descreva as Especificações!

#automated-test, #dev, #ruby

O Rspec é uma ferramenta que ajuda o desenvolvedor quando estiver criando sua aplicação web, funciona como um teste, mas não é um teste (segundo o desenvolvedor), infelizmente não sei informar a sutil diferença entre ambos.

Continue a ler vou apresentar o Rspec e o Remarkable do Carlos Brando.

Primeiro instale as gems rspec e rspec-rails:

$ sudo gem install rspec rspec-rails
[sudo] password for dmitry: 
**************************************************

  Thank you for installing rspec-1.2.7

  Please be sure to read History.rdoc and Upgrade.rdoc
  for useful information about this release.

**************************************************
Successfully installed rspec-1.2.7
**************************************************

  Thank you for installing rspec-rails-1.2.7.1

  If you are upgrading, do this in each of your rails apps
  that you want to upgrade:

    $ ruby script/generate rspec

  Please be sure to read History.rdoc and Upgrade.rdoc
  for useful information about this release.

**************************************************
Successfully installed rspec-rails-1.2.7.1
2 gems installed
Installing ri documentation for rspec-1.2.7...
Installing ri documentation for rspec-rails-1.2.7.1...
Installing RDoc documentation for rspec-1.2.7...
Installing RDoc documentation for rspec-rails-1.2.7.1...

Veja a saída do rpec-rails: ruby script/generate rspec, e é exatamente este a linha de comando que deve ser usada para adicionar o suporte a Rspec no rails:

Após criar nossa aplicação vamos adicionar o suporte a Rspec:

$ rails Rspec_example
      create  
      create  app/controllers
      create  app/helpers
      create  app/models
      [ . . . ]
$ cd Rspec_example
$ ./script/generate rspec
      exists  lib/tasks
      create  lib/tasks/rspec.rake
      create  script/autospec
      create  script/spec
      create  script/spec_server
      create  spec
      create  spec/rcov.opts
      create  spec/spec.opts
      create  spec/spec_helper.rb

Adicionado o suporte a Rspec vamos criar nosso primeiro teste, digo, nossa primeira especificação:

Crie o arquivo spec/models/post_spec.rb se necessário crie o diretório model. O conteúdo dele deve ser algo parecido com isso:

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Post do
  before(:each) do
    @valid_attributes = {
      :title => "value for name",
      :body => "value for body"
    }
  end

  it "should create a new instance given valid attributes" do
    Post.create!(@valid_attributes)
  end
end

Vamos criar nosso model e também a migração:

$ ./script/generate model Post title:string body:text
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/post.rb
      create  test/unit/post_test.rb
      create  test/fixtures/posts.yml
      create  db/migrate
      create  db/migrate/20090708181546_create_posts.rb

Fazer a migração:

$ rake db:migrate
(in /home/dmitry/Projects/Rspec_example)
==  CreatePosts: migrating ====================================================
-- create_table(:posts)
   -> 0.0013s
==  CreatePosts: migrated (0.0014s) ===========================================

Vamos executar nosso teste:

$ rake spec:models
(in /home/dmitry/Projects/Rspec_example)
.

Finished in 0.027251 seconds

1 example, 0 failures

Passou! Era realmente para passar, não criamos nenhuma especificação (dá uma vontade de dizer que é teste)!

Agora vamos descrever a especificação para o título, que é “não deve ser válido sem título”.

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Post do
  before(:each) do
    @valid_attributes = {
      :title => "value for name",
      :body => "value for body"
    }
  end

  it "should create a new instance given valid attributes" do
    Post.create!(@valid_attributes)
  end

  it "should not be valid without title" do
    post = Post.new(@valid_attributes.except(:title))
    post.should_not be_valid
    post.should have(1).error_on(:title)
  end
end

E no terminal nossa falha:

$ rake spec:models
(in /home/dmitry/Projects/Rspec_example)
.F

1)
'Post should not be valid without title' FAILED
expected valid? to return false, got true
./spec/models/post_spec.rb:17:

Finished in 0.069284 seconds

2 examples, 1 failure
rake aborted!
Command /usr/bin/ruby1.8 -I"/usr/lib/ruby/gems/1.8/gems/rspec-1.2.7/lib"  "/usr/lib/ruby/gems/1.8/gems/rspec-1.2.7/bin/spec" "spec/models/post_spec.rb" --options "/home/dmitry/Projects/Rspec_example/spec/spec.opts" failed

(See full trace by running task with --trace)

Se corrigirmos no model:

class Post < ActiveRecord::Base
  validates_presence_of :title
end

A nossa especificação passa:

$ rake spec:models
(in /home/dmitry/Projects/Rspec_example)
..

Finished in 0.033128 seconds

2 examples, 0 failures

Explicando: a linha 16 contém esse conteúdo:

post = Post.new(@valid_attributes.except(:title))

Ela informa para passar todos os parâmetros padrão exceto (.except(:title)) o parâmetro `title’.

Deixo para vocês os outros testes, agora quero mostrar algo mais interessante, para isso instale as gems remarkable_rails e carlosbrando-remarkable (esta última no github):

$ sudo gem install remarkable_rails carlosbrando-remarkable
Successfully installed remarkable-3.1.7
Successfully installed remarkable_activerecord-3.1.7
Successfully installed remarkable_rails-3.1.7
PostInstall.txt
Successfully installed carlosbrando-remarkable-2.3.1
4 gems installed
Installing ri documentation for remarkable-3.1.7...
Installing ri documentation for remarkable_activerecord-3.1.7...
Installing ri documentation for remarkable_rails-3.1.7...
Installing ri documentation for carlosbrando-remarkable-2.3.1...
Installing RDoc documentation for remarkable-3.1.7...
Installing RDoc documentation for remarkable_activerecord-3.1.7...
Installing RDoc documentation for remarkable_rails-3.1.7...
Installing RDoc documentation for carlosbrando-remarkable-2.3.1...

Note que na verdade foram instaladas 4 gems por causa de dependência.

Para que o remarkable seja carregado no momento do rake devemos editar dois arquivos, o primeiro é config/environments/test.rb:

# Settings specified here will take precedence over those in config/environment.rb

# The test environment is used exclusively to run your application's
# test suite.  You never need to work with it otherwise.  Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs.  Don't rely on the data there!
config.cache_classes = true

# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true

# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching             = false
config.action_view.cache_template_loading            = true

# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection    = false

# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test

# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql

config.gem "rspec", :lib => false
config.gem "rspec-rails", :lib => false
config.gem "remarkable_rails", :lib => false

Apenas as três últimas linhas foram adicionadas.

Adicione também a linha 7 ao arquivo spec/spec_helper.rb:

# This file is copied to ~/spec when you run 'ruby script/generate rspec'
# from the project root directory.
ENV["RAILS_ENV"] ||= 'test'
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
require 'spec/autorun'
require 'spec/rails'
require 'remarkable_rails'

# Requires supporting files with custom matchers and macros, etc,
# in ./support/ and its subdirectories.
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}

Spec::Runner.configure do |config|
  # If you're not using ActiveRecord you should remove these
  # lines, delete config/database.yml and disable :active_record
  # in your config/boot.rb
  config.use_transactional_fixtures = true
  config.use_instantiated_fixtures  = false
  config.fixture_path = RAILS_ROOT + '/spec/fixtures/'

  # == Fixtures
  #
  # You can declare fixtures for each example_group like this:
  #   describe "...." do
  #     fixtures :table_a, :table_b
  #
  # Alternatively, if you prefer to declare them only once, you can
  # do so right here. Just uncomment the next line and replace the fixture
  # names with your fixtures.
  #
  # config.global_fixtures = :table_a, :table_b
  #
  # If you declare global fixtures, be aware that they will be declared
  # for all of your examples, even those that don't use them.
  #
  # You can also declare which fixtures to use (for example fixtures for test/fixtures):
  #
  # config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
  #
  # == Mock Framework
  #
  # RSpec uses it's own mocking framework by default. If you prefer to
  # use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr
  #
  # == Notes
  #
  # For more information take a look at Spec::Runner::Configuration and Spec::Runner
end

A linha 7 deve ficar abaixo de require 'spec/rails'.

Preparado nosso ambiente: modifique o arquivo spec/models/post_spec.rb para que fique semelhante a isso (apenas a linha 21 foi adiciona) :

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')

describe Post do
  before(:each) do
    @valid_attributes = {
      :title => "value for name",
      :body => "value for body"
    }
  end

  it "should create a new instance given valid attributes" do
    Post.create!(@valid_attributes)
  end

  it "should not be valid without title" do
    post = Post.new(@valid_attributes.except(:title))
    post.should_not be_valid
    post.should have(1).error_on(:title)
  end

  it { should validate_uniqueness_of :registration }
end

Com apenas uma linha o Remarkable faz o que comumente é feito com 5 ou 6 linhas.

Posts nesta série