Cucumber: Expresando comportamiento en texto plano

Post on 01-Nov-2014

1.149 views 0 download

Tags:

description

Introduccion a la herramienta Cucumber, utilizada para escribir tests en un lenguaje natural facilitando la interaccion de todos los miembros del proyecto

Transcript of Cucumber: Expresando comportamiento en texto plano

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