Post on 01-Nov-2014
description
Cucumber:Expresando Comportamiento en Texto Plano
Raimond Garcia GimenezFernando Garcia Samblas
Antecedentes2002: Ward Cunningham's Framework for Integrated Test
Especificaciones ejecutables desde Word, Excel, Wikis, etc.
Febrero 2007: Dan North's RSpec StoriesStories escritas en Ruby y http://dannorth.net/whats-in-a-story
Octubre 2007: David Chelimsky: plain text supportEscritas en inglés y separadas del código
Agosto 2008: Aslak Hellesøy's CucumberInternacionalización y mucho más...
given/when/then
Feature: <funcionalidad> In order to <beneficio/valor/why?> As a <rol> I want to <feature>
Scenario: <escenario> Given <contexto> [And <contexto>] When <evento> [And <evento>] Then <resultado> [And <contexto>] [But <contexto>]
given/when/then
Feature: <funcionalidad> In order to <beneficio/valor/why?> As a <rol> I want to <feature>
Scenario: <escenario> Given <contexto> [And <contexto>] When <evento> [And <evento>] Then <resultado> [And <contexto>] [But <contexto>]
Ejemplo en breve... With GivenScenario!
Limitaciones de RSpec Stories
Limitaciones de RSpec Stories
Niñoooooooossss..... a comer!!!!!!
Resumiendo... Entorno Crudo
•Sin herramienta para lanzar stories/scenarios•Sin soporte rake•Sin convenciones•Forzosamente escritas en ingles•Feedback limitado •Hooks preparacion/finalizacion artesanales
Cucumber
Script• script/cucumber
Script• script/cucumber• cucumber (default profile en cucumber.yml)
Script• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features
Script• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*
Script• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature
Script• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10
Script• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Script
--require
• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Script
--format --require
• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Script
--format --require --out=FILE
• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Script
--no-source
--format --require --out=FILE
• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Script
--no-source
--dry-run --format --require --out=FILE
• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Script
--no-source
--dry-run --format
--language
--require --out=FILE
• script/cucumber• cucumber (default profile en cucumber.yml)• cucumber features• cucumber features/admin*• cucumber features/hola.feature• cucumber features/hola.feature --line 10• cucumber --profile html_report
Tarea de Rake
Cucumber::Rake::Task.new(:features) do |t|
t.cucumber_opts = "--format pretty"
end
Tarea de Rake
Cucumber::Rake::Task.new(:features) do |t|
t.cucumber_opts = "--format pretty"
end
attr_accessors:cucumber_opts
step_pattern / step_list feature_pattern / feature_list
rcov / rcov_optslibs / binary
Feedback
Scenario: with change something changes # features/edition.feature:15 Given authentication is disabled # features/steps/given_wiki.rb:6 And I have a page called "readme" with "Thanks!" # features/steps/given_wiki.rb:2 When I visit /readme # features/steps/when_interactions.rb:39 And I follow "Edit" # features/steps/when_interactions.rb:11 And I fill in "body" with "This is my changed wiki page!" # features/steps/when_interactions.rb:15 And I press "send" # features/steps/when_interactions.rb:3 can't convert Symbol into String (TypeError) /Library/Ruby/Gems/1.8/gems/activesupport-2.2.0/lib/active_support/inflector.rb:261:in `escape' ./features/steps/../../sinatra_wiki.rb:39 Then I should see "readme" # features/steps/then_results.rb:2 And I should NOT see "Thanks!" # features/steps/then_results.rb:6 And I should see "This is my changed wiki page!" # features/steps/then_results.rb:2
49 steps passed4 steps failed11 steps skipped
Fichero y linea del escenario...
FeedbackFichero y linea del escenario...
Scenario: with change something changes # features/edition.feature:15 Given authentication is disabled # features/steps/given_wiki.rb:6 And I have a page called "readme" with "Thanks!" # features/steps/given_wiki.rb:2 When I visit /readme # features/steps/when_interactions.rb:39 And I follow "Edit" # features/steps/when_interactions.rb:11 And I fill in "body" with "This is my changed wiki page!" # features/steps/when_interactions.rb:15 And I press "send" # features/steps/when_interactions.rb:3 can't convert Symbol into String (TypeError) /Library/Ruby/Gems/1.8/gems/activesupport-2.2.0/lib/active_support/inflector.rb:261:in `escape' ./features/steps/../../sinatra_wiki.rb:39 Then I should see "readme" # features/steps/then_results.rb:2 And I should NOT see "Thanks!" # features/steps/then_results.rb:6 And I should see "This is my changed wiki page!" # features/steps/then_results.rb:2
49 steps passed4 steps failed11 steps skipped
cucumber features/edition.feature --line 15
Feedback
Scenario: with change something changes # features/edition.feature:15 Given authentication is disabled # features/steps/given_wiki.rb:6 And I have a page called "readme" with "Thanks!" # features/steps/given_wiki.rb:2 When I visit /readme # features/steps/when_interactions.rb:39 And I follow "Edit" # features/steps/when_interactions.rb:11 And I fill in "body" with "This is my changed wiki page!" # features/steps/when_interactions.rb:15 And I press "send" # features/steps/when_interactions.rb:3 can't convert Symbol into String (TypeError) /Library/Ruby/Gems/1.8/gems/activesupport-2.2.0/lib/active_support/inflector.rb:261:in `escape' ./features/steps/../../sinatra_wiki.rb:39 Then I should see "readme" # features/steps/then_results.rb:2 And I should NOT see "Thanks!" # features/steps/then_results.rb:6 And I should see "This is my changed wiki page!" # features/steps/then_results.rb:2
49 steps passed4 steps failed11 steps skipped
cucumber features/edition.feature --line 15
When /^I visit (.+)$/ do |url|
Fichero y linea del escenario...
... y del step con parametros subrayados
Feedbackcucumber features/edition.feature --line 15
When /^I visit (.+)$/ do |url|
Fichero y linea del escenario...
... y del step con parametros subrayados
Coloreado completo (casi, lo veremos mas adelante)
Scenario: with change something changes # features/edition.feature:15 Given authentication is disabled # features/steps/given_wiki.rb:6 And I have a page called "readme" with "Thanks!" # features/steps/given_wiki.rb:2 When I visit /readme # features/steps/when_interactions.rb:39 And I follow "Edit" # features/steps/when_interactions.rb:11 And I fill in "body" with "This is my changed wiki page!" # features/steps/when_interactions.rb:15 And I press "send" # features/steps/when_interactions.rb:3 Then I should see "readme" # features/steps/then_results.rb:2 And I should NOT see "Thanks!" # features/steps/then_results.rb:6 And I should see "This is my changed wiki page!" # features/steps/then_results.rb:2
49 steps passed3 steps failed11 steps skipped1 step pending
Feedback
Scenario: with change something changes # features/edition.feature:15 Given authentication is disabled # features/steps/given_wiki.rb:6 And I have a page called "readme" with "Thanks!" # features/steps/given_wiki.rb:2 When I visit /readme # features/steps/when_interactions.rb:39 And I follow "Edit" # features/steps/when_interactions.rb:11 And I fill in "body" with "This is my changed wiki page!" # features/steps/when_interactions.rb:15 And I press "send" # features/steps/when_interactions.rb:3 Then I should see "readme" # features/steps/then_results.rb:2 And I should NOT see "Thanks!" # features/steps/then_results.rb:6 And I should see "This is my changed wiki page!" # features/steps/then_results.rb:2
49 steps passed3 steps failed11 steps skipped1 step pending
You can use these snippets to implement pending steps:
Then /^I press “send”$/ doend
cucumber features/edition.feature --line 15
When /^I visit (.+)$/ do |url|
Fichero y linea del escenario...
... y del step con parametros subrayados
Coloreado completo (casi, lo veremos mas adelante)
y step snippets!
Deteccion de Ambiguedades
Given /Tres (.*) ciegos/ do |animal|Given /Tres gatos (.*)/ do |handicap|
Te obliga a ser mas DRYMejora la mantenibilidad
Given /Tres (.+) (.+)/ do |animal, handicap|
FIT Tables
Característica: saludo localizado Para entender el mensaje de bienvenida Como un usuario Quiero que me saluden en mi idioma Escenario: Locale del browser Dado que el locale de mi browser es 'es_ES' Cuando visito la home Entonces vere el texto 'Buenos Dias Salao!'
More i18n Examples: | locale | page | Saludo | | es_CA ! home | Bones Salat! | | en_US ! home | Good Day Salty! |
FIT Steps Dado el usuario Raimond de BeBanjo con email voodoo@example.com nacido en Mallorca en 1982 Y el usuario Nando de TheCocktail con email nando@example.com nacido en Madrid en 1973 Y el usuario Wadus de BeTheWadus con email wadus@example.com nacido en WadusLand en 1847
FIT Steps Dado el usuario Raimond de BeBanjo con email voodoo@example.com nacido en Mallorca en 1982 Y el usuario Nando de TheCocktail con email nando@example.com nacido en Madrid en 1973 Y el usuario Wadus de BeTheWadus con email wadus@example.com nacido en WadusLand en 1847
Dado que existen los siguientes usuarios | nombre | empresa | correo-e | ciudad | ano | | Raimond | BeBanjo | voodoo@example.com | Mallorca | 1982 | | Nando | TheCocktail | nando@example.com | Madrid | 1973 | | Wadus | BeTheWadus | wadus@example.com | WadusLand | 1847 |
FIT Steps Dado el usuario Raimond de BeBanjo con email voodoo@example.com nacido en Mallorca en 1982 Y el usuario Nando de TheCocktail con email nando@example.com nacido en Madrid en 1973 Y el usuario Wadus de BeTheWadus con email wadus@example.com nacido en WadusLand en 1847
Dado que existen los siguientes usuarios | nombre | empresa | correo-e | ciudad | ano | | Raimond | BeBanjo | voodoo@example.com | Mallorca | 1982 | | Nando | TheCocktail | nando@example.com | Madrid | 1973 | | Wadus | BeTheWadus | wadus@example.com | WadusLand | 1847 |
Given /que existen los siguientes usuarios/ do |usuarios| #Array de hashes, del tipo {:nombre => ‘Wadus’, ....}end
Autotest
$ sudo gem install ZenTest$ AUTOFEATURE=true autospec
Flow:•Ejecuta tus specs hasta que todos pasen•Ejecuta tus escenarios fallidos hasta que pasen•Despues ejecuta todos sus specs otra vez•Y todos los features
Puesta en marcha:
Perfiles
default: --language es featureshtml: --language es --format html featuresperformance: --language es --format profile features
RAILS_ROOT/cucumber.yml
Perfiles
default: --language es featureshtml: --language es --format html featuresperformance: --language es --format profile features
RAILS_ROOT/cucumber.yml
$ cucumber --profile performanceProfiling enabled.................................................................
Top 10 average slowest steps with 5 slowest matches:0.0048184 When /^I (try)?(?: to )?visit ["']?([^"']+)["']?$/i # features/steps/when_interactions.rb:39 0.0290940 When I visit /brand-new # features/creation.feature:8 0.0038860 When I visit /brand-new # features/creation.feature:25 0.0035640 When I visit the home # features/destruction.feature:9 0.0018260 When I visit the home # features/home.feature:8 0.0017800 When I visit the home # features/destruction.feature:280.0039920 When /^I press ["']?([^"']+)["']?$/i # features/steps/when_interactions.rb:3 0.0057240 And I press "send" # features/creation.feature:10 0.0043170 And I press "send" # features/edition.feature:11 0.0035290 And I press "send" # features/creation.feature:27 0.0023980 And I press "send" # features/edition.feature:21
i18n
cucumber --language es
feature: Característica scenario: Escenario given_scenario: DadoElEscenario given: Dado when: Cuando then: Entonces and: Y
lib/cucumber/languages.yml
Ejemplo
Característica: Mantener Atencion
Para que todo el mundo use Cucumber Como desarollador agil Quiero mantener la atencion de la audiencia durante la ponencia
Escenario: Presentando la ponencia de Cucumber Dado Que presentamos una propuesta para la Conferencia Rails 2008 Y Que nos las aceptan Y Que mandamos nuestras fotos xuflas Y Que confirmamos nuestra asistencia Y Que logramos llegar a tiempo despues de pillarnos un pedo en la cena del jueves Escenario: Crisis Temporal DadoElEscenario: Presentando la ponencia de Cucumber Y Que la audiencia comienza a quedarse sopa Cuando pulsamos “CTRL + SHIFT + R” Entonces sale una foto de unas tetas curiosas Y la gente se pone muy contenta Y algunos se rien Escenario: Exito Total DadoElEscenario: Presentando la ponencia de Cucumber Y Que la audiencia comienze a excitarse Cuando pulsamos “CTRL + R” Entonces sale una foto de media teta intrigante Y la gente se pone pensativa
features/atencion.feature
Declarative vs Imperative
Estilo Imperativo
Cuando voy a la pagina de registrarmeY relleno el campo 'email' con 'wadus@wadusland.com'Y relleno el campo 'nombre' con 'wadus'Y relleno el campo 'ciudad' con 'WadusLand'Y relleno el campo 'telefono' con '111-999-333'Y pincho en el boton 'Registrarme!'Entonces 'wadus@wadusland.com' recibira un emailY tendra como 'asunto' 'Bienvenido a WadusLand.com'
Estilo Imperativo
Cuando voy a la pagina de registrarmeY relleno el campo 'email' con 'wadus@wadusland.com'Y relleno el campo 'nombre' con 'wadus'Y relleno el campo 'ciudad' con 'WadusLand'Y relleno el campo 'telefono' con '111-999-333'Y pincho en el boton 'Registrarme!'Entonces 'wadus@wadusland.com' recibira un emailY tendra como 'asunto' 'Bienvenido a WadusLand.com'
• En el estilo IMPERATIVO lo importante es, COMO se consigue el objectivo
Estilo Declarativo
Cuando un nuevo usuario se registraEntonces recibira un email con el 'asunto' 'Bienvenido a WadusLand.com'
Estilo Declarativo
Cuando un nuevo usuario se registraEntonces recibira un email con el 'asunto' 'Bienvenido a WadusLand.com'
• En el estilo DECLARATIVO, lo importante es el QUE es nuestro objetivo
Declarative vs Imperative
Cual usar? pues depende del caso.
Quien va a leer la historia? hombre de negocio o desarrollador?
Que quieres testear y resaltar en esa historia?
Arte?
Big Picture
Nuestro Picture
Selenium setupen The Cocktail
Instancia Xen con Windows XP (sin ruby instalado)java 1.6.0 & ant 1.7.0selenium-server-0.9.2
> java -jar selenium-server.jar
Resolvemos test.host y www.example.com hacia la IP CruiseControl
Servidor CruiseControl:
Linux Debian con Selenium-1.1.14.gem (selenium-driver, cliente)Build Secuencial (lanza APP siempre en el puerto 3000)
Servidor de Selenium Remote Control:
The Cocktail Buildtask :cruise => :environment do ENV['SELENIUM_SERVER'] = "selenium-server" ENV['SELENIUM_BROWSER'] = "*iexplore C:\\Archivos de programa\\MultipleIEs\\IE6\\iexplore.exe" Rake::Task['features'].invoke Rake::Task['features_with_ajax'].invokeend
Cucumber::Rake::Task.new(:features_with_ajax) do |t| t.cucumber_opts = "--profile ajax" t.feature_pattern = "features/caracteristicas_ajax/**/*.feature" t.step_list = "features/step_definitions/support/selenium_env.rb"end
Cucumber::Rake::Task.new(:features) do |t| t.cucumber_opts = "--profile webrat" t.feature_pattern = "features/caracteristicas/**/*.feature" t.step_list = "features/step_definitions/support/webrat_env.rb"end
selenium_env.rbapp_server_pid = fork do [STDOUT, STDERR].each {|f| f.reopen('/dev/null','w')} exec "script/server -e #{ENV['RAILS_ENV']} -p #{apport}"end$selenium_driver = Selenium::SeleniumDriver.new(selenium_server, 4444, browser, appurl, 15000)$selenium_driver.start at_exit do $selenium_driver.stop Process.kill(9, app_server_pid)end
Selenium setup en Bebanjo
• Todo en el mismo Servidor (SliceHost)
UbuntuCruise(git) gema Selenium-1.1.14Xterm (Firefox)
task :cruise do sh "script/cucumber features --profile webrat" with_servers do sh "script/cucumber features --profile selenium" endend
def with_servers xserver_pid = run_command("startx -- `which Xvfb` :1 -screen 0 1024x768x24") selenium_pid = run_command("sh -c 'DISPLAY=:1 selenium'") webserver_pid = run_command("script/server -e test -p #{ENV['WEBSERVER_PORT'] || 3000}") yieldensure system("killall xterm") Process.kill(9, webserver_pid) require 'selenium' Selenium::SeleniumServer.new.stopend
def run_command(cmd) fork do [STDOUT,STDERR].each {|f| f.reopen '/dev/null', 'w' } exec cmd endend
Bebanjo Build
Selenium::SeleniumDriver.new( "localhost", 4444, "*chrome", # selenium-server, puerto, browser "http://localhost:3000", 15000) # servidor de aplicacion, timeout
Selenium-Grid
SELENIUM_BROWSERS = { :firefox => 'SELENIUM_BROWSER="Firefox3"', :googlechrome => 'RAILS_ENV=test_chrome SELENIUM_BROWSER="GChrome" SELENIUM_APPORT=3001', :iexplorer => 'RAILS_ENV=test_iexplorer SELENIUM_BROWSER="IExplorer6" SELENIUM_APPORT=3002' } task :features_in_selenium_grid do pids, failures_in = {}, [] SELENIUM_BROWSERS.each do |key, hash| pid = fork { exec(hash[:env] + ' SELENIUM_SERVER=selenium-server cucumber --profile selenium') } pids[pid] = key end
SELENIUM_BROWSERS.size.times do Process.wait failures_in << pids[$?.pid] if $?.exitstatus == 0 end raise 'Features failure in ' + failures_in.join(' and ') unless failures_in.empty?end
> ant launch-hub> ant -Dport=5555 -Denvironment="IExplorer6" launch-remote-control> ant -Dport=5556 -Denvironment="Firefox3" launch-remote-control> ant -Dport=5557 -Denvironment="GChrome" launch-remote-control
TestJour$ mkdir testjour-working-dir$ testjour slave:start
$ testjour list Testjour servers:
wadus available wadus-computer.local.:182499
$ testjour run features
Ventajas
• Acerca el lenguaje empleado por el cliente (o business) • Abre la spec a todos los miembros del equipo (cliente incluido)• Emerge el vocabulario de la app (comunicacion mas precisa)• Integración de toda la aplicación (JavaScript incluido)• Ausencia de "paginas huerfanas" (de forma centralizada)
Ventajas
• Acerca el lenguaje empleado por el cliente (o business) • Abre la spec a todos los miembros del equipo (cliente incluido)• Emerge el vocabulario de la app (comunicacion mas precisa)• Integración de toda la aplicación (JavaScript incluido)• Ausencia de "paginas huerfanas" (de forma centralizada)
y ademas:• VERDE (no es COBOL)
Inconvenientes
• Una mismo paso se puede expresar de mil formas
• lentos?
• funcionalidades globales en el layout
• cuando spec y cuando feature
• pocas convenciones...
Inconvenientes
• lentos?
• funcionalidades globales en el layout
• cuando spec y cuando feature
• pocas convenciones...
• VERDE (no es COBOL)
Imaginemos que ya tenemos todas las step_definitions escritas. Cuando escribo una nueva feature... que hago?
la comiteo petando el build? me creo un rama ad-hoc?
creo un archivo/directorio especial? presssssssssss?
!BASTA DE NIAPAS!Scenario: with change something changes # features/edition.feature:15 Given authentication is disabled # features/steps/given_wiki.rb:6 And I have a page called "readme" with "Thanks!" # features/steps/given_wiki.rb:2 When I visit /readme # features/steps/when_interactions.rb:39 And I follow "Edit" # features/steps/when_interactions.rb:11 And I fill in "body" with "This is my changed wiki page!" # features/steps/when_interactions.rb:15 And I press "send" # features/steps/when_interactions.rb:3 Then I should see "readme" # features/steps/then_results.rb:2 And I should NOT see "Thanks!" # features/steps/then_results.rb:6 And I should see "This is my changed wiki page!" # features/steps/then_results.rb:2
49 steps passed3 steps failed11 steps skipped1 step TODO (step definition done but pending app implementation)
Paranoias y Deseos
• Problema: tenemos features con ajax y sin ajax separadas en diferentes archivos relacionadas con la mism feature, lo queremos todo junto.
• Solucion: profile por escenario (HTML / JavaScript)
• Problema: Testear unobtrusive-javascript sin duplicar escenarios.
• Solucion: ProfileGroup (unobtrusive = html profile + javascript profile) Ademas que en el step definition sepamos el contexto (profile!) en el que se esta/debe ejectuarse (http, javascript)
• Problema: usar fit tables en un escenario con muchas variables, Solucion: no se conoce hasta ahora...
Paranoias y Deseos
Tip para reutilizar steps pensando en resources
<% content_tag_for :li, evento do %> <p class="descripcion"> <%= evento.descripcion %> </p><% end %>
Then "he should see the text '$text' within the $resource '$resource_attribute'" do |text, resource, resource_attribute| response_body.should have_tag_with_child(".#{resource}", "*", /#{resource_attribute}/) do with_tag("*", /#{text}/) endend
<li class='evento'> <p><h3>Descripcion</h3> <span>Conferencia Ruby Euroko 2009</span> </p></li>
para testear este codigo html
podemos usar el content_tag_for en las vistas
y este step re-usable para todos los resources
Tips• /i case insensitive, mayusculas y minusculas, que mas da!
• shouldify, not_shouldify
• try_if_try, 40X responsechildren = @resource.send(child_model.table_name)blog.postschild = @resource.send(child_model.name.downcase)blog.user
• def unquote(text) text =~ /^['"](.*)['"]$/ ? $1 : text endend‘edit article’, “edit article”, edit article
• selenium -browserSessionReuse &
more tips- Parentesis Grouping-only en RegExps: /(?:...)/
Por ejemplo:
...(?:(?:en|con) estado|como)?...
Cazaria:
Dado que la categoria BDD tiene 3 librosY que dichos libros estan publicadoso bien:Y que dichos libros estan como publicadosY que dichos libros estan en estado publicado
anti-dolores tip
• si en un step utilizamos RegExp no podemos usar variables $
• selenium setup for ie6
> java -jar selenium-server.jar -interactive...cmd=getNewBrowserSession&1=*iexplore&2=http://test.host:3000(...y podemos cerrar el navegador que nos abre)
GRACIAS!
• Gracias a Mamuso por el casco para volver a casa en moto a las tantas
• Gracias a Macla por la foto de la ensalada
• Gracias a nuestros jefes por darnos el miercoles para terminar la ponencia
• Gracias a Vosotros por haber venido!
• Preguntas?
http://github.com/voodoorai2000/conferenciarails2008