Sinatra con Cucumber

Uso de cucumber-sinatra para comenzar el proyecto.

[gomix@movix CaptaHuellas]$ su -c "gem install cucumber-sinatra" 
Contraseña: 
Fetching: cucumber-sinatra-0.4.0.gem (100%)
Successfully installed cucumber-sinatra-0.4.0
1 gem installed

Arranque desde cero

$ mkdir Captahuella
$ cd Captahuella
$ cucumber-sinatra init --app Captahuella lib/captahuella.rb 
Generating with init generator:
     [ADDED]  features/support/env.rb
     [ADDED]  features/support/paths.rb
     [ADDED]  features/step_definitions/web_steps.rb
     [ADDED]  lib/captahuella.rb
     [ADDED]  config.ru

features/support/env.rb

# Generated by cucumber-sinatra. (Sat Dec 24 12:22:36 -0430 2011)

ENV['RACK_ENV'] = 'test'

require File.join(File.dirname(__FILE__), '..', '..', 'lib/captahuella.rb')

require 'capybara'
require 'capybara/cucumber'
require 'rspec'

Capybara.app = Captahuella

class CaptahuellaWorld
  include Capybara::DSL
  include RSpec::Expectations
  include RSpec::Matchers
end

World do
  CaptahuellaWorld.new
end

features/support/paths.rb

# Taken from the cucumber-rails project.

module NavigationHelpers
  # Maps a name to a path. Used by the
  #
  #   When /^I go to (.+)$/ do |page_name|
  #
  # step definition in web_steps.rb
  #
  def path_to(page_name)
    case page_name

    when /the home\s?page/
      '/'

    # Add more mappings here.
    # Here is an example that pulls values out of the Regexp:
    #
    #   when /^(.*)'s profile page$/i
    #     user_profile_path(User.find_by_login($1))

    else
      raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
        "Now, go and add a mapping in #{__FILE__}" 
    end
  end
end

World(NavigationHelpers)

config.ru

# config.ru
require 'rubygems'
require File.join(File.dirname(__FILE__), 'lib/captahuella.rb')

lib/captahuella.rb

1require 'sinatra/base'
2
3class Captahuella < Sinatra::Base
4  get '/' do
5    'Hello Captahuella!'
6  end
7end

$ rackup -p 4567
127.0.0.1 - - [24/Dec/2011 08:20:46] "GET / HTTP/1.0" 200 18 0.6021
...
$ curl http://localhost:4567/
Hello Captahuella!

Primera funcionalidad

Recuerde que el primer paso en BDD es describir su funcionalidad.

features/f1.feature

 1# language: es
 2Característica: Ejecutar comando ftrScanAPI_Ex
 3        Con la finalidad de crear objetos Minucia, se debe primero
 4        capturar la imagen de la huella digital de la persona.
 5        Para capturar la huella hay que ejecutar ftrScanAPI_Ex.
 6
 7        Escenario: Captura de huella
 8                Dado que acceso '/scan'
 9                Entonces yo debería ver el resultado 'Imagen obtenida'

Ahora las definiciones de paso

features/step_definitions/scan_pasos_basicos.rb

$ cucumber features/f1.feature 
# language: es
Característica: Ejecutar comando ftrScanAPI_Ex
  Con la finalidad de crear objetos Minucia, se debe primero
  capturar la imagen de la huella digital de la persona.
  Para capturar la huella hay que ejecutar ftrScanAPI_Ex.

  Escenario: Captura de huella                             # features/f1.feature:7
    Dado que acceso '/scan'                                # features/f1.feature:8
    Entonces yo debería ver el resultado 'Imagen obtenida' # features/f1.feature:9

1 scenario (1 undefined)
2 steps (2 undefined)
0m0.005s

You can implement step definitions for undefined steps with these snippets:

Dado /^que acceso '\/scan'$/ do
  pending # express the regexp above with the code you wish you had
end

Entonces /^yo debería ver el resultado 'Imagen obtenida'$/ do
  pending # express the regexp above with the code you wish you had
end

Implementando con dichas sugerencias en features/step_definitions/scan_pasos_basicos.rb y volviendo a correr:

$ cucumber features/f1.feature 
# language: es
Característica: Ejecutar comando ftrScanAPI_Ex
  Con la finalidad de crear objetos Minucia, se debe primero
  capturar la imagen de la huella digital de la persona.
  Para capturar la huella hay que ejecutar ftrScanAPI_Ex.

  Escenario: Captura de huella                             # features/f1.feature:7
    Dado que acceso "/scan"                                # features/step_definitions/scan_pasos_basicos.rb:1
      TODO (Cucumber::Pending)
      ./features/step_definitions/scan_pasos_basicos.rb:2:in `/^que acceso "([^"]*)"$/'
      features/f1.feature:8:in `Dado que acceso "/scan"'
    Entonces yo debería ver el resultado "Imagen obtenida" # features/step_definitions/scan_pasos_basicos.rb:5

1 scenario (1 pending)
2 steps (1 skipped, 1 pending)
0m0.006s

Ahora toca implementar, aquí me detengo un momento para pensar y evaluar. Ya que mi ejemplo está apuntando a probar una RESTful Web API hecho en Sinatra, creo que lo más adecuado es usar Rack::Test y un entorno de pruebas simple, es decir Test::Unit.

Después de unas cuantas iteraciones más, aquí va mi el código de mis archivos nuevamente:

features/support/env.rb

 1require 'rack/test'
 2
 3ENV['RACK_ENV'] = 'test'
 4
 5require File.join(File.dirname(__FILE__), '..', '..', 'lib/captahuella.rb')
 6
 7Captahuella.set :environment, :test
 8
 9World do
10    def app
11      @app = Rack::Builder.new do
12        run Captahuella
13      end
14    end
15
16    include Rack::Test::Methods
17    include Test::Unit::Assertions
18end

features/scan.feature (renombre algunos archivos)

 1# language: es
 2Característica: Ejecutar comando ftrScanAPI_Ex    
 3    Con la finalidad de crear objetos Minucia, se debe primero
 4    capturar la imagen de la huella digital de la persona.
 5    Para capturar la huella hay que ejecutar ftrScanAPI_Ex.
 6    En particular debe usarse el método HTTP GET.
 7
 8    Escenario: Captura de huella
 9    Dado que el cliente acepta respuesta MIME image/bmp
10    Y que se accede a '/scan' 
11    Y que se ha usado el método 'HTTP GET'
12    Entonces debería obtener respuesta HTTP '200'
13    Y recibir una descarga de una imagen tipo bmp

Corrida de cucumber

$ cucumber features/scan.feature
# language: es
Característica: Ejecutar comando ftrScanAPI_Ex
  Con la finalidad de crear objetos Minucia, se debe primero
  capturar la imagen de la huella digital de la persona.
  Para capturar la huella hay que ejecutar ftrScanAPI_Ex.
  En particular debe usarse el método HTTP GET.

  Escenario: Captura de huella                          # features/scan.feature:8
    Dado que el cliente acepta respuesta MIME image/bmp # features/step_definitions/scan_pasos_basicos.rb:1
    Y que se accede a '/scan'                           # features/step_definitions/scan_pasos_basicos.rb:5
    Y que se ha usado el método 'HTTP GET'              # features/step_definitions/scan_pasos_basicos.rb:9
    Entonces debería obtener respuesta HTTP '200'       # features/step_definitions/scan_pasos_basicos.rb:13
    Y recibir una descarga de una imagen tipo bmp       # features/step_definitions/scan_pasos_basicos.rb:17

1 scenario (1 passed)
5 steps (5 passed)
0m5.221s

features/step_definitions/scan_pasos_basicos.rb

 1Dado /^que el cliente acepta respuesta MIME image\/bmp$/ do
 2  # header es método de rack-test
 3  header 'Accept', 'image/bmp'
 4end
 5
 6Dado /^que se accede a '\/scan'$/ do
 7  # get es un método de rack-test
 8  get('/scan')
 9end
10
11Dado /^que se ha usado el método 'HTTP GET'$/ do
12  # assert_equal es un método de Test::Unit
13  assert_equal("GET",last_request.env["REQUEST_METHOD"])
14end
15
16Entonces /^debería obtener respuesta HTTP '(\d+)'$/ do |status|
17  # assert_equal es un método de Test::Unit
18  assert_equal(status, last_response.status.to_s)
19end
20
21Entonces /^recibir una descarga de una imagen tipo bmp$/ do
22  # assert_equal es un método de Test::Unit
23  assert_equal("image/bmp",last_response.content_type)
24end

Ahora veamos el código que implementa la funcionalidad:

 1require 'sinatra/base'
 2
 3class Captahuella < Sinatra::Base
 4
 5  get '/scan' do
 6    # Servir una imagen capturada desde el scanner
 7    begin
 8    case 
 9      when request.accept.include?("image/bmp")
10        `ftrScanAPI_Ex`
11        send_file ("frame_Ex.bmp") 
12    else
13      # Solicitud de tipo de contenido no aceptable
14      status 406
15    end
16
17    rescue
18      # No encuentro dicho dispositivo
19      status 404
20      case 
21      when request.accept.include?("image/bmp")
22        result = "Scan encontró problemas"   # ¿Devolver algo además del código de estado?
23      else
24        # Solicitud de tipo de contenido no aceptable
25        status 406
26        result = "Objeto no encontrado" 
27      end
28    end
29  end
30
31end

Referencias y enlaces

cucumber-feature-salida-1.png (24.3 kB) Guillermo Gómez, 12/24/2011 10:35 am