El documento describe la evolución de las herramientas para expresar comportamiento en texto plano, incluyendo Cucumber. Cucumber permite especificar comportamientos de software de una manera internacionalizable y con soporte para lanzar escenarios, reportes y convenciones.
2. Antecedentes
2002: Ward Cunningham's Framework for Integrated Test
Especificaciones ejecutables desde Word, Excel, Wikis, etc.
Febrero 2007: Dan North's RSpec Stories
Stories escritas en Ruby y http://dannorth.net/whats-in-a-story
Octubre 2007: David Chelimsky: plain text support
Escritas en inglés y separadas del código
Agosto 2008: Aslak Hellesøy's Cucumber
Internacionalización y mucho más...
3. 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>]
4. 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!
26. Tarea de Rake
Cucumber::Rake::Task.new(:features) do |t|
t.cucumber_opts = quot;--format prettyquot;
end
attr_accessors:
cucumber_opts
step_pattern / step_list
feature_pattern / feature_list
rcov / rcov_opts
libs / binary
27. Feedback
Fichero 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 quot;readmequot; with quot;Thanks!quot; # features/steps/given_wiki.rb:2
When I visit /readme # features/steps/when_interactions.rb:39
And I follow quot;Editquot; # features/steps/when_interactions.rb:11
And I fill in quot;bodyquot; with quot;This is my changed wiki page!quot; # features/steps/when_interactions.rb:15
And I press quot;sendquot; # 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 quot;readmequot; # features/steps/then_results.rb:2
And I should NOT see quot;Thanks!quot; # features/steps/then_results.rb:6
And I should see quot;This is my changed wiki page!quot; # features/steps/then_results.rb:2
49 steps passed
4 steps failed
11 steps skipped
28. Feedback
Fichero y linea del escenario... cucumber features/edition.feature --line 15
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 quot;readmequot; with quot;Thanks!quot; # features/steps/given_wiki.rb:2
When I visit /readme # features/steps/when_interactions.rb:39
And I follow quot;Editquot; # features/steps/when_interactions.rb:11
And I fill in quot;bodyquot; with quot;This is my changed wiki page!quot; # features/steps/when_interactions.rb:15
And I press quot;sendquot; # 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 quot;readmequot; # features/steps/then_results.rb:2
And I should NOT see quot;Thanks!quot; # features/steps/then_results.rb:6
And I should see quot;This is my changed wiki page!quot; # features/steps/then_results.rb:2
49 steps passed
4 steps failed
11 steps skipped
29. Feedback
Fichero y linea del escenario... cucumber features/edition.feature --line 15
... y del step con parametros subrayados When /^I visit (.+)$/ do |url|
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 quot;readmequot; with quot;Thanks!quot; # features/steps/given_wiki.rb:2
When I visit /readme # features/steps/when_interactions.rb:39
And I follow quot;Editquot; # features/steps/when_interactions.rb:11
And I fill in quot;bodyquot; with quot;This is my changed wiki page!quot; # features/steps/when_interactions.rb:15
And I press quot;sendquot; # 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 quot;readmequot; # features/steps/then_results.rb:2
And I should NOT see quot;Thanks!quot; # features/steps/then_results.rb:6
And I should see quot;This is my changed wiki page!quot; # features/steps/then_results.rb:2
49 steps passed
4 steps failed
11 steps skipped
30. Feedback
Fichero y linea del escenario... cucumber features/edition.feature --line 15
... y del step con parametros subrayados When /^I visit (.+)$/ do |url|
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 quot;readmequot; with quot;Thanks!quot; # features/steps/given_wiki.rb:2
When I visit /readme # features/steps/when_interactions.rb:39
And I follow quot;Editquot; # features/steps/when_interactions.rb:11
And I fill in quot;bodyquot; with quot;This is my changed wiki page!quot; # features/steps/when_interactions.rb:15
And I press quot;sendquot; # features/steps/when_interactions.rb:3
Then I should see quot;readmequot; # features/steps/then_results.rb:2
And I should NOT see quot;Thanks!quot; # features/steps/then_results.rb:6
And I should see quot;This is my changed wiki page!quot; # features/steps/then_results.rb:2
49 steps passed
3 steps failed
11 steps skipped
1 step pending
31. Feedback
Fichero y linea del escenario... cucumber features/edition.feature --line 15
... y del step con parametros subrayados When /^I visit (.+)$/ do |url|
Coloreado completo (casi, lo veremos mas adelante)
y step snippets!
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 quot;readmequot; with quot;Thanks!quot; # features/steps/given_wiki.rb:2
When I visit /readme # features/steps/when_interactions.rb:39
And I follow quot;Editquot; # features/steps/when_interactions.rb:11
And I fill in quot;bodyquot; with quot;This is my changed wiki page!quot; # features/steps/when_interactions.rb:15
And I press quot;sendquot; # features/steps/when_interactions.rb:3
Then I should see quot;readmequot; # features/steps/then_results.rb:2
And I should NOT see quot;Thanks!quot; # features/steps/then_results.rb:6
And I should see quot;This is my changed wiki page!quot; # features/steps/then_results.rb:2
49 steps passed
3 steps failed
11 steps skipped
1 step pending
You can use these snippets to implement pending steps:
Then /^I press “send”$/ do
end
32. Deteccion de
Ambiguedades
Given /Tres (.*) ciegos/ do |animal|
Given /Tres gatos (.*)/ do |handicap|
Given /Tres (.+) (.+)/ do |animal, handicap|
Te obliga a ser mas DRY
Mejora la mantenibilidad
33. 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! |
34. 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
35. 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 |
36. 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
37. Autotest
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:
$ sudo gem install ZenTest
$ AUTOFEATURE=true autospec
38. Perfiles
RAILS_ROOT/cucumber.yml
default: --language es features
html: --language es --format html features
performance: --language es --format profile features
39. Perfiles
RAILS_ROOT/cucumber.yml
default: --language es features
html: --language es --format html features
performance: --language es --format profile features
$ cucumber --profile performance
Profiling enabled.
................................................................
Top 10 average slowest steps with 5 slowest matches:
0.0048184 When /^I (try)?(?: to )?visit [quot;']?([^quot;']+)[quot;']?$/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:28
0.0039920 When /^I press [quot;']?([^quot;']+)[quot;']?$/i # features/steps/when_interactions.rb:3
0.0057240 And I press quot;sendquot; # features/creation.feature:10
0.0043170 And I press quot;sendquot; # features/edition.feature:11
0.0035290 And I press quot;sendquot; # features/creation.feature:27
0.0023980 And I press quot;sendquot; # features/edition.feature:21
41. Ejemplo
features/atencion.feature
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
43. Estilo Imperativo
Cuando voy a la pagina de registrarme
Y 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 email
Y tendra como 'asunto' 'Bienvenido a WadusLand.com'
44. Estilo Imperativo
Cuando voy a la pagina de registrarme
Y 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 email
Y tendra como 'asunto' 'Bienvenido a WadusLand.com'
• En el estilo IMPERATIVO lo importante es, COMO se consigue el
objectivo
45. Estilo Declarativo
Cuando un nuevo usuario se registra
Entonces recibira un email con el 'asunto' 'Bienvenido a WadusLand.com'
46. Estilo Declarativo
Cuando un nuevo usuario se registra
Entonces recibira un email con el 'asunto' 'Bienvenido a WadusLand.com'
• En el estilo DECLARATIVO, lo importante es el QUE es nuestro
objetivo
47. 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?
50. Selenium setup
en The Cocktail
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:
Instancia Xen con Windows XP (sin ruby instalado)
java 1.6.0 & ant 1.7.0
selenium-server-0.9.2
> java -jar selenium-server.jar
Resolvemos test.host y www.example.com hacia la IP CruiseControl
51. The Cocktail Build
task :cruise => :environment do
ENV['SELENIUM_SERVER'] = quot;selenium-serverquot;
ENV['SELENIUM_BROWSER'] = quot;*iexplore C:Archivos de programaMultipleIEsIE6iexplore.exequot;
Rake::Task['features'].invoke
Rake::Task['features_with_ajax'].invoke
end
Cucumber::Rake::Task.new(:features_with_ajax) do |t|
t.cucumber_opts = quot;--profile ajaxquot;
t.feature_pattern = quot;features/caracteristicas_ajax/**/*.featurequot;
t.step_list = quot;features/step_definitions/support/selenium_env.rbquot;
end
Cucumber::Rake::Task.new(:features) do |t|
t.cucumber_opts = quot;--profile webratquot;
t.feature_pattern = quot;features/caracteristicas/**/*.featurequot;
t.step_list = quot;features/step_definitions/support/webrat_env.rbquot;
end
selenium_env.rb
app_server_pid = fork do
[STDOUT, STDERR].each {|f| f.reopen('/dev/null','w')}
exec quot;script/server -e #{ENV['RAILS_ENV']} -p #{apport}quot;
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
52. Selenium setup
en Bebanjo
• Todo en el mismo Servidor (SliceHost)
Ubuntu
Cruise(git)
gema Selenium-1.1.14
Xterm (Firefox)
53. Bebanjo Build
task :cruise do
sh quot;script/cucumber features --profile webratquot;
with_servers do
sh quot;script/cucumber features --profile seleniumquot;
end
end
def with_servers
xserver_pid = run_command(quot;startx -- `which Xvfb` :1 -screen 0 1024x768x24quot;)
selenium_pid = run_command(quot;sh -c 'DISPLAY=:1 selenium'quot;)
webserver_pid = run_command(quot;script/server -e test -p #{ENV['WEBSERVER_PORT'] || 3000}quot;)
yield
ensure
system(quot;killall xtermquot;)
Process.kill(9, webserver_pid)
require 'selenium'
Selenium::SeleniumServer.new.stop
end
def run_command(cmd)
fork do
[STDOUT,STDERR].each {|f| f.reopen '/dev/null', 'w' }
exec cmd
end
end
Selenium::SeleniumDriver.new(
quot;localhostquot;, 4444, quot;*chromequot;, # selenium-server, puerto, browser
quot;http://localhost:3000quot;, 15000) # servidor de aplicacion, timeout
54. Selenium-Grid
> ant launch-hub
> ant -Dport=5555 -Denvironment=quot;IExplorer6quot; launch-remote-control
> ant -Dport=5556 -Denvironment=quot;Firefox3quot; launch-remote-control
> ant -Dport=5557 -Denvironment=quot;GChromequot; launch-remote-control
SELENIUM_BROWSERS = {
:firefox => 'SELENIUM_BROWSER=quot;Firefox3quot;',
:googlechrome => 'RAILS_ENV=test_chrome SELENIUM_BROWSER=quot;GChromequot; SELENIUM_APPORT=3001',
:iexplorer => 'RAILS_ENV=test_iexplorer SELENIUM_BROWSER=quot;IExplorer6quot; 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
55. TestJour
$ mkdir testjour-working-dir
$ testjour slave:start
$ testjour list
Testjour servers:
wadus available wadus-computer.local.:182499
$ testjour run features
56. 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 quot;paginas huerfanasquot; (de forma centralizada)
57. 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 quot;paginas huerfanasquot; (de forma centralizada)
y ademas:
• VERDE (no es COBOL)
58. Inconvenientes
• Una mismo paso se puede expresar de mil formas
• lentos?
• funcionalidades globales en el layout
• cuando spec y cuando feature
• pocas convenciones...
60. Paranoias y Deseos
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 quot;readmequot; with quot;Thanks!quot; # features/steps/given_wiki.rb:2
When I visit /readme # features/steps/when_interactions.rb:39
And I follow quot;Editquot; # features/steps/when_interactions.rb:11
And I fill in quot;bodyquot; with quot;This is my changed wiki page!quot; # features/steps/when_interactions.rb:15
And I press quot;sendquot; # features/steps/when_interactions.rb:3
Then I should see quot;readmequot; # features/steps/then_results.rb:2
And I should NOT see quot;Thanks!quot; # features/steps/then_results.rb:6
And I should see quot;This is my changed wiki page!quot; # features/steps/then_results.rb:2
49 steps passed
3 steps failed
11 steps skipped
1 step TODO (step definition done but pending app implementation)
61. 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...
62. Tip para reutilizar steps
pensando en resources <li class='evento'>
<p><h3>Descripcion</h3>
para testear este codigo html <span>Conferencia Ruby Euroko 2009</span>
</p>
</li>
<% content_tag_for :li, evento do %>
<p class=quot;descripcionquot;>
podemos usar el <%= evento.descripcion %>
content_tag_for en las vistas </p>
<% end %>
y este step re-usable para todos los resources
Then quot;he should see the text '$text' within the $resource '$resource_attribute'quot; do |text, resource, resource_attribute|
response_body.should have_tag_with_child(quot;.#{resource}quot;, quot;*quot;, /#{resource_attribute}/) do
with_tag(quot;*quot;, /#{text}/)
end
end
63. Tips
• /i case insensitive, mayusculas y minusculas, que mas da!
• shouldify, not_shouldify
• try_if_try, 40X response
children = @resource.send(child_model.table_name)
blog.posts
child = @resource.send(child_model.name.downcase)
blog.user
• def unquote(text)
text =~ /^['quot;](.*)['quot;]$/ ? $1 : text
end
end
‘edit article’, “edit article”, edit article
• selenium -browserSessionReuse &
64. more tips
- Parentesis Grouping-only en RegExps: /(?:...)/
Por ejemplo:
...(?:(?:en|con) estado|como)?...
Cazaria:
Dado que la categoria BDD tiene 3 libros
Y que dichos libros estan publicados
o bien:
Y que dichos libros estan como publicados
Y que dichos libros estan en estado publicado
65. 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)
66. 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