Integração Sinatra, Cucumber e Webrat

Sempre que consigo algum tempo tento escrever alguma coisa no blog e desta vez quero mostrar como é fácil o desenvolvimento em BDD no Sinatra usando Cucumber e Webrat. Para quem numca ouviu falar nesses caras vamos as apresentações. 🙂

Quem é esse Sinatra?
Para quem não saber Sinatra é uma linguagem de domínio específico (DSL – Domain Specific Language) para a criação rápida de aplicações web escritas em ruby. Ele mantém uma característica mínima definida, deixando livre o desenvolvedor para utilizar as ferramentas que melhor lhe servir em sua aplicação.

BDD? Cucumber?
BDD ou Behavior Driven Development(Desenvolvimento Guiado por Comportamento) é uma técnica de desenvolvimento Ágil que encoraja colaboração entre desenvolvedores, setores de qualidade e pessoas não-técnicas ou de negócios num projeto de software. O foco em BDD é a linguagem e interações usadas no processo de desenvolvimento de software.

O Cucumber foi criado para permitir que você execute a documentação de funcionalidades de uma aplicação, escritas em texto puro (também conhecidas como “estórias”). Com o Cucumber, isto é uma especificação executável que você pode discutir com seu cliente e então usá-la para verificar o comportamento correto dos testes. Por trás dos bastidores, você faz isto funcionar criando “steps”, que são expressões regulares que executam código em Ruby.

Webrat
Webrat é uma ferramenta fantástica que permite escrever rapidamente testes de aceitação expressivos e robustos para uma aplicação web Ruby. Ele nos fornece entre outras coisas:

  • Simulador de browser de alto nível;
  • Suporta vários frameworks web Ruby;
  • Suporta os mais populares frameworks de teste;
  • Fornece uma API para verificar o HTML gerado usando CSS, XPath, etc.

Depois de feita as devidas apresentações vamos colocar a mão na massa. O primeiro passo é criar o diretório de nosso projeto.

1
2
$ mkdir sinatra-cucumber
$ cd sinatra-cucumber

Vamos acessar a pasta do projeto que acabamos de criar e executar os comandos abaixo para criar a pasta onde iremos definir nossas features.

1
2
$ mkdir features
$ touch features/ola.feature

Obs.: Para quem não conhece o comando touch apenas criou um arquivo vazio.

No arquivo ola.feature escreva o seguinte código:

1
2
3
4
5
6
7
8
9
# language: pt
Funcionalidade: Ver páginas
  Como um usuário qualquer
  Eu quero acessar as páginas do sistema
  Para ter acesso a seu conteúdo
 
  Cenário: Página principal
    Dado que acabei de acessar o sistema
    Então Eu devo ver o texto "Olá, pessoal!"

Vamos executar o cucumber e ver o que acontece. 🙂

1
$ cucumber features/ola.feature

Como era de esperar o teste não passou. Vamos em seguida criar os testes para nossa funcionalidade mais antes iremos criar uma tarefa rake para otimizar a chamada do Cucumber.

1
$ touch Rakefile

O código para nossa tarefa rake que será executada com o comando “rake features” é o seguinte:

1
2
3
4
5
6
require 'rubygems'
require 'cucumber/rake/task'
 
Cucumber::Rake::Task.new(:features) do |t|
  t.cucumber_opts = '--format pretty'
end

Agora sim podemos continuar.

1
2
$ mkdir features/step_definitions
$ touch features/step_definitions/ola_steps.rb

No arquivo ola_steps.rb teremos o seguinte código:

1
2
3
4
5
6
7
Dado /^que acabei de acessar o sistema$/ do
  visit("/")
end
 
Entao /^Eu devo ver o texto "(.+)"$/ do |texto|
  response_body.should =~ Regexp.new(Regexp.escape(texto))
end

Estes dois passos simples fazem uma solicitação a url do nosso aplicativo pelo Webrat e verifica se a resposta contém o texto que estamos procurando.

Abaixo segue as configurações que fazem a integração realmente acontecer. Vamos configurar o ambiente do Cucumber para usar o Webrat.

1
2
$ mkdir features/support
$ touch features/support/env.rb

O conteúdo do arquivo env.rb deve ser o seguinte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require 'spec/expectations'
require 'rack/test'
require 'webrat'
 
Webrat.configure do |config|
  config.mode = :rack
end
 
class MyWorld
  include Rack::Test::Methods
  include Webrat::Methods
  include Webrat::Matchers
 
  Webrat::Methods.delegate_to_session :response_code, :response_body
 
  def app
    Sinatra::Application
  end
end
 
World do
  MyWorld.new
end
 
require File.dirname(__FILE__) + '/../../ola'

Agora que temos nosso cenário montado podemos escrever nossa aplicação web com essas simples linhas abaixo:

1
$ touch ola.rb
1
2
3
4
5
6
require 'rubygems'
require 'sinatra'
 
get '/' do
  "Olá, pessoal!"
end

Agora vamos executar mais uma vez o Cucumber e ver os testes passando para ficarmos felizes. 🙂

1
$ rake features

Bom pessoal, o objetivo foi cumprido e espero que tenha ficado claro como é fácil desenvolver em Sinatra usando BDD com Cucumber e Webrat. Sei que o exemplo foi bem simples e abaixo segue o código fonte do projeto e alguma referências para você conhecer mais do assunto.

Código fonte
http://github.com/igocoelho/sinatra-cucumber

Conheça mais
Livro de Sinatra em Português
http://sinatra.tailorfontela.com.br/

Aplicação simples com Sinatra
http://pomoti.com/aplicacao-simples-com-sinatra

BDD com Cucumber, Selenium e Rails
http://www.slideshare.net/cmilfont/bdd-com-cucumber-selenium-e-rails

Introducão ao BDD com Cucumber, RSpec, Webrat e Selenium – Parte I
http://jefferson.eti.br/?p=96

Introducão ao BDD com Cucumber, RSpec, Webrat e Selenium – Parte II
http://jefferson.eti.br/?p=105

Introducão ao BDD com Cucumber, RSpec, Webrat e Selenium – Parte III
http://jefferson.eti.br/?p=139

Screencast Ruby on Rails: Introdução a RSpec e Cucumber
http://vimeo.com/7108280

Gerenciando as dependências com Ruby Tracker

Ruby Tracker é um projeto de apoio à comunidade liderado por Jacob Swanner que tem como objetivo monitorar e informar as dependências entre gems em projetos Ruby.

O Ruby Tracker funciona examinando o projeto e verificando quais gems são utilizadas e principalmente quais versões. Quando for preciso autalizar qualquer gem você será informado quais outras também devem ser atualizadas e para qual versão.

Segue abaixo um vídeo demonstrando melhor o uso da ferramenta:

Para não esquecer: Ruby on Rails Unit Testing Assertions

Basic Assertions

assert(boolean, message)
assert(person.name == “John”, “Name was expected to be John.”)
assert(item.errors.invalid?(:price))

assert_equal(expected, actual, message)
assert_equal(person.name, “John”, “Name was expected to be John.”)
assert_equal(“can’t be empty”, product.errors.on(:price))

assert_not_equal(expected, actual, message)

assert_not_equal(person.name, “Mary”, “Name was Mary and it should not be.”)
assert_not_equal(“is not a number”, product.errors.on(:price))

assert_raise(Exception, message) { block… }

assert_raise(ZeroDivisionError, “Cannot divide by zero!”) { 100 / 0 }
assert_raise(ActiveRecord::RecordNotFound) { Product.find(bad_id) }

assert_nothing_raised(Exception, message) { block… }

assert_nothing_raised(ZeroDivisionError) { 100 / [0,1].max }
assert_nothing_raised(ActiveRecord::RecordNotFound) { Product.find(good_id) }

assert_nil(object, message)

assert_nil( product, “Expected product to be nil.” )
assert_nil( Wine.find(:first, :conditions => ‘id = 1000’) )

assert_not_nil(object, message)

assert_not_nil( product, “Product should not be nil.” )
assert_not_nil( Wine.find(:first, :conditions => ‘id = 1’) )

assert_valid(activerecord_object)

same as: assert(object.valid?)
assert_valid(@person)
assert_valid( Wine.find(1) )

flunk(message)

always fails immediately; same as: assert(false, message)
flunk(“Quantity should not be greater than 100”) if quantity > 100
flunk(“Either user or account should be valid”) unless user.valid? || account.valid?

Advanced Assertions

assert_match(pattern, string, message)
assert_match(/^\d,\d{3},\d{3}$/, “1,000,000”, “Should match this format.”)

assert_no_match(pattern, string, message)

assert_no_match(/\d{3},\d{2}$/, “1,000,000”, “Should not match this format.”)

assert_in_delta(expected_float, actual_float, delta, message)

assert_in_delta(100.0, price, 20.0, “Price should be between 80.00 and 120.00”)
assert_in_delta(2, length, 1, “Length should be 1-3 feet.”)

assert_instance_of( klass, object, message )

assert_instance_of( User, person, “person should be an instance of User” )

assert_kind_of( klass, object, message )

assert_kind_of( User, person, “person should be a kind of User” )
assert_kind_of( Class, User, “User should be a kind of Class” )

assert_respond_to( object, symbol, message )

Instances only respond to instance methods, classes only respond to class methods
assert_respond_to( person, :full_name, “No response to full_name” )
assert_respond_to( User, :custom_find, “No response to custom_find” )

assert_throws(expected_symbol, message) { block… }

assert_throws(:done, “Array should be empty”) { throw :done if [].empty? }

assert_nothing_thrown(message) { block… }

assert_nothing_thrown(“Array should not be empty”) { throw :done if [1].empty? }

Rare Assertions & DEFAULT EROR MESSA GES

assert_same( expected, actual, message)
same as: assert_equal(expected, actual)
assert_same( person.name, “John”)

assert_not_same( expected, actual, message)

same as: assert_not_equal(expected, actual)
assert_not_same( person.name, “Mary”)

assert_operator( object1, operator, object2, message )

same as: assert( object1.operator(object2) )
assert_operator( 1000, :<, 2000, “Expected 1000 to be less than 2000” )
assert_operator( user, :old_enough?, Time.now(), “User should be old enough”)

assert_send([receiver, symbol, arg1, arg2], message)

same as: assert( receiver.message(arg1, arg2) )
assert_send([product, :decrement_inventory, qty], “Decrement should succeed”)

From: /activerecord/lib/active_record/validations.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@@default_error_messages = {
:inclusion => “is not included in the list”,
:exclusion => “is reserved”,
:invalid => “is invalid”,
:confirmation => “doesn’t match confirmation”,
:accepted => “must be accepted”,
:empty => “can’t be empty”,
:blank => “can’t be blank”,
:too_long => “is too long (maximum is %d characters)”,
:too_short => “is too short (minimum is %d characters)”,
:wrong_length => “is the wrong length (should be %d characters)”,
:taken => “has already been taken”,
:not_a_number => “is not a number”,
:greater_than => “must be greater than %d”,
:greater_than_or_equal_to => “must be greater than or equal to %d”,
:equal_to => “must be equal to %d”,
:less_than => “must be less than %d”,
:less_than_or_equal_to => “must be less than or equal to %d”,
:odd => “must be odd”,
:even => “must be even”
}

Referência:
http://www.nullislove.com/2008/02/20/testing-in-rails-part-10-assertions/