BDD de Aceptación con Ruby
-
Upload
luismi-cavalle -
Category
Entertainment & Humor
-
view
2.524 -
download
5
description
Transcript of BDD de Aceptación con Ruby
Sergio Gil y Luismi Cavallé
Pruebas BDD de Aceptación con Ruby
Agile Spain 2010
Historias de Usuario
[A story] has to be a description of a requirement and its business benefit, and a set of criteria by which we all
agree that it is “done”
– Dan North
http://blog.dannorth.net/whats-in-a-story/
Anatomía de una historia
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Scenario: Buy last coffee Given there are 1 coffees left in the machine And I have deposited 1$ When I press the coffee button Then I should be served a coffee
Narrativa
Criterio de Aceptación
Anatomía de una historia
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Scenario: Buy last coffee Given there are 1 coffees left in the machine And I have deposited 1$ When I press the coffee button Then I should be served a coffee
Narrativa
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Título
Narrativa
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Rol¿Quién?
Narrativa
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Funcionalidad¿Qué?
Narrativa
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Beneficio¿Por qué?
Narrativa
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Regla de los 5 porqués
Regla de los 5 porqués
ENSÉÑAME LA PASTA !!!!
Criterio de Aceptación
Scenario: Buy last coffee
Given there are 1 coffees left in the machine And I have deposited 1$
When I press the coffee button
Then I should be served a coffee
Contexto
Criterio de Aceptación
Scenario: Buy last coffee
Given there are 1 coffees left in the machine And I have deposited 1$
When I press the coffee button
Then I should be served a coffee
Eventos
Criterio de Aceptación
Scenario: Buy last coffee
Given there are 1 coffees left in the machine And I have deposited 1$
When I press the coffee button
Then I should be served a coffee
Resultados
Criterio de Aceptación
Scenario: Buy last coffee
Given there are 1 coffees left in the machine And I have deposited 1$
When I press the coffee button
Then I should be served a coffee
Criterio de Aceptación
Scenario: Buy last coffee
Given there are 1 coffees left in the machine And I have deposited 1$
When I press the coffee button
Then I should be served a coffee
DONE
Beneficio para el negocio
TestersProgramadores
ManagersClientesAnalistas
Beneficio para el negocio
Historia de UsuarioToken de comunicación / discusiónPriorización según beneficio / valor
Criterio común de “terminado”
TestersProgramadores
ManagersClientesAnalistas
Cucumber
Cucumber
Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times
Scenario: Buy last coffee Given there are 1 coffees left in the machine And I have deposited 1$ When I press the coffee button Then I should be served a coffee
Pasos
Given a user “john” with password “secret”
When he tries to login as “james”
Then he should see an error message
Definición de pasos
Given a user “john” with password “secret”
Definición de pasos
Given /^a user "(.+)" with password "(.+)"$/ do |login, password|
Given a user “john” with password “secret”
Definición de pasos
Given /^a user "(.+)" with password "(.+)"$/ do |login, password| User.create! :login => login, :password => passwordend
Given a user “john” with password “secret”
Definición de pasos
When he tries to login as “james”
Definición de pasos
When /^he tries to login as "(.+)"$/ do |login|
When he tries to login as “james”
Definición de pasos
When /^he tries to login as "(.+)"$/ do |login| visit "/login" fill_in "Username", :with => login fill_in "Password", :with => "secret" click_button "Login"end
When he tries to login as “james”
Definición de pasos
Then he should see an error message
Definición de pasos
Then he should see an error message
Then /^I should see an error message$/ do
Definición de pasos
Then he should see an error message
Then /^I should see an error message$/ do assert page.has_content?("Invalid credentials")end
Demo
+40 Idiomas
# language: esCaracterística: Búsqueda de cursos Para asegurar el mejor uso de los cursos Los estudiantes potenciales deberían poder buscar cursos Escenario: Búsqueda por materia Dado que hay 240 cursos, ninguno sobre "biología" Y hay 2 cursos A001, B205 sobre "biología" Cuando busco por "biología" Entonces debería ver los siguientes cursos: | Código de curso | | A001 | | B205 |
Otros lenguajes package cukes;
import cuke4duke.Given;import java.util.List;import java.util.ArrayList;
public class BellySteps { private List<String> belly = new ArrayList<String>();
@Given("I have (\\d+) cukes in my belly") public void bellyCukes(int cukes) { for(int i = 0; i < cukes; i++) { belly.add("cuke " + i); } }}
Otros lenguajes package cukes;
import cuke4duke.Given;import java.util.List;import java.util.ArrayList;
public class BellySteps { private List<String> belly = new ArrayList<String>();
@Given("I have (\\d+) cukes in my belly") public void bellyCukes(int cukes) { for(int i = 0; i < cukes; i++) { belly.add("cuke " + i); } }}
(Given #"^I have entered ([\d.]+) into the calculator$" (fn [number] (push-number (Float. number))))
Más funcionalidades...
Background
Feature: Multiple blog support Background: Given a global administrator named "Greg" And a blog named "Greg's anti-tax rants" And a customer named "Dr. Bill" And a blog named "Expensive Therapy" owned by "Dr. Bill" Scenario: Dr. Bill posts to his own blog Scenario: Dr. Bill tries to post to somebody else's blog Scenario: Greg posts to a client's blog
Tags
$ cucumber --tags @billing,~@important
@billingFeature: Verify billing
@important Scenario: Missing product description
Scenario: Several products
HooksBefore do @browser = Browser.newend
After do @browser.closeend
HooksBefore do @browser = Browser.newend
After do @browser.closeend
Around do |scenario, block| Timeout.timeout(3) do block.call endend
HooksBefore do @browser = Browser.newend
After do @browser.closeend
Around do |scenario, block| Timeout.timeout(3) do block.call endend
Before('@foo')
After('~@bar')
Scenario Outline
Scenario Outline: eating Given there are <start> cucumbers When I eat <eat> cucumbers Then I should have <left> cucumbers
Examples: | start | eat | left | | 12 | 5 | 7 | | 20 | 5 | 15 |
So far, so good?
• Historias de Usuario
• Beneficio para el negocio
• Cucumber
So far, so good?
Coming next...• Testing de aceptación Web
• Beneficio para los devs
• Alternativas a Cucumber
• Historias de Usuario
• Beneficio para el negocio
• Cucumber
Aceptación para Web:Capybara
DSL de interacción web (navegador programático)
Usa el lenguaje del usuario
classmethod
GET / POSTparameters
Usa el lenguaje del usuario
classmethod
GET / POSTparameters
pageURLlink
form
visit "/wadus"
click_link "Add article"
click_link "Add article"click "Add article"
fill_in "Title", :with => "Wadus"
fill_in "Title", :with => "Wadus"choose "Option"
fill_in "Title", :with => "Wadus"choose "Option"check "Option"
fill_in "Title", :with => "Wadus"choose "Option"check "Option"uncheck "Option"
fill_in "Title", :with => "Wadus"choose "Option"check "Option"uncheck "Option"select "1980", :from => "Birth Year"
fill_in "Title", :with => "Wadus"choose "Option"check "Option"uncheck "Option"select "1980", :from => "Birth Year"click_button "Save"
fill_in "Title", :with => "Wadus"choose "Option"check "Option"uncheck "Option"select "1980", :from => "Birth Year"click_button "Save"click "Save"
within :css, ".article:first" do click_link "Edit"end
Matchers
page.should have_content("Wadus")
page.should have_content("Wadus")page.should_not have_content("Wadus")
page.should have_content("Wadus")page.should_not have_content("Wadus")
page.should have_css(".article", :text => "Wadus")
page.should have_content("Wadus")page.should_not have_content("Wadus")
page.should have_css(".article", :text => "Wadus")page.should have_css(".article", :count => 3)
page.should have_content("Wadus")page.should_not have_content("Wadus")
page.should have_css(".article", :text => "Wadus")page.should have_css(".article", :count => 3)
page.should have_xpath("//*[@class='article']")
page.should have_css(".article", :text => "Wadus") do |article| article.should have_css(".author", :text => "@porras") article.should have_css(".links") do |links| links.should have_css("a", :href => "http://wadus.info") links.should have_css("a", :href => "http://bit.ly/wadus") endend
save_and_open_page
Drivers
RackTest
RackTest
Basado en Rack
RackTest
FAST!
Basado en Rack
RackTest
FAST!Sin Javascript
Basado en Rack
RackTest
FAST!Sin Javascript
No es ‘real’
Basado en Rack
Selenium
Selenium
Basado en browsers programables
Selenium
Javascript
Basado en browsers programables
Selenium
JavascriptAceptación pura
Basado en browsers programables
Selenium
JavascriptAceptación pura
¡LENTO!
Basado en browsers programables
Selenium
JavascriptAceptación pura
¡LENTO!Un poco frágil
Basado en browsers programables
Selenium
JavascriptAceptación pura
¡LENTO!Un poco frágil
Java
Basado en browsers programables
Celerity / Culerity
Celerity / Culerity
Basado en HTMLUnit
Celerity / Culerity
Javascript
Basado en HTMLUnit
Celerity / Culerity
JavascriptAceptación casi pura
Basado en HTMLUnit
Celerity / Culerity
JavascriptAceptación casi pura
No muy lento
Basado en HTMLUnit
Celerity / Culerity
JavascriptAceptación casi pura
No muy lentoJava
Basado en HTMLUnit
Envjs
Envjs
Basado en SpiderMonkey
Envjs
Basado en SpiderMonkey
Javascript
Envjs
Basado en SpiderMonkey
JavascriptAceptación casi pura
Envjs
Basado en SpiderMonkey
JavascriptAceptación casi pura
No muy lento
Envjs
Basado en SpiderMonkey
JavascriptAceptación casi pura
No muy lentoRuby
Envjs
Basado en SpiderMonkey
JavascriptAceptación casi pura
No muy lentoRuby
Un poco verde
RackTest
Selenium
Culerity
0 37.5 75.0 112.5 150.0
Valor para el desarrollador
Excelente herramienta de verificación
Pero TDD no es QA
Pero TDD no es QAsólo
Dirige el desarrollo
“Outside-in testing is the best way to avoid overengineering”
http://www.sarahmei.com/blog/2010/05/29/outside-in-bdd/
Up-front
M
Up-front
C
M
Up-front
V
C
M
Up-front
V
C
M
T
Up-front
V
C
M
TT
Up-front
V
C
M
TTT
Up-front
Outside-in
F
Outside-in
F
V
Outside-in
F
V
C
Outside-in
F
V
CM
Outside-in
F
V
CM
F
V
CM
F
V
CM
F
V
CM
F
V
CM
Outside-in
“Tu interfaz es tu producto”
http://gettingreal.37signals.com/ch09_Interface_First.php
Permite otras prácticas ágiles
The Simplest Thing That Could Possibly Work
Dejar que el diseño “emerja”
Pair Programming
Refactorización contínua
Integración Contínua
Propiedad colectiva del código
Release Often
Continuous Deployment
Steak
A veces Cucumber (historias en texto plano)
no es la mejor opción
Todo el “valor para el desarrollador” sin la
burocracia extra
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
feature "Main page" do
background do create_user :login => "wadus" end
scenario "should show existing books" do create_book :title => "The Pragmatic Programmer"
login_as "wadus" visit "/"
page.should have_css(".book", :text => "The Pragmatic Programmer") end
end
Demo
Más amigos
Spork
Spork
Sin spork
Con spork
0 3.25 6.50 9.75 13.00
Factorías
fixture_replacement
fixture_replacementfactory_girl
fixture_replacementfactory_girlmachinist
fixture_replacementfactory_girlmachinistfixjour
fixture_replacementfactory_girlmachinistfixjourcranky
fixture_replacementfactory_girlmachinistfixjourcranky
Ruby
module Factories def create_user(attrs = {}) attrs = attrs.dup attrs[:name] ||= String.random(10) attrs[:email] ||= "#{String.random(10)}@example.com" User.create!(attrs) end def create_article(attrs = {}) attrs = attrs.dup attrs[:title] ||= String.random(10) attrs[:author] ||= create_user Article.create!(attrs) end end
...
create_usercreate_user(:name => "Bartolo")create_articlecreate_article(:author => create_user(:name => "Mr. Wadus"))
Delorean
Delorean
Delorean
it "should show latest created user" do time_travel_to(3.minutes.ago) { create_user :name => "John" } time_travel_to(5.minutes.ago) { create_user :name => "Chris" }
get '/'
page.should have_content("John") page.should_not have_content("Chris")end
Database Cleaner
Database Cleaner
DatabaseCleaner.strategy = :truncationDatabaseCleaner.clean
Database Cleaner
DatabaseCleaner.strategy = :transactionDatabaseCleaner.clean
Email Spec
Email Spec
mailbox_for("[email protected]").should have(1).email
open_email "[email protected]"
current_email.should have_subject("Bienvenido a Wadus 2.0")current_email.should have_text("Hola, porras")
Webmock
Webmock
request(:post, "www.example.com"). with(:body => "abc"). should have_been_made.once
MundoPepino
“MundoPepino es un conjunto de pasos genéricos para testear una
aplicación Rails utilizando Cucumber”
Dado que tenemos un huertoY una huertaY un huerto "En el río"Y una huerta "En el castro"Y un huerto llamado "Regadío"Y una huerta llamada "Secano"Y 2 huertosY 2 huertos "Regadío"Y 2 huertos llamados "Secano"Y 2 huertas llamadas "Secano"Y 3 huertas llamadas "H-01, H-02 y H-03"Entonces tenemos en bbdd 17 huertosY tenemos en bbdd un huerto "En el río"Y tenemos en bbdd una huerta "En el castro"Y tenemos en bbdd 3 huertos "Regadío"Y tenemos en bbdd 5 huertas "Secano"Y tenemos en bbdd un huerto "H-01"Y tenemos en bbdd un huerto "H-02"Y tenemos en bbdd un huerto "H-03"
Cuando visito la página de creación de huertoEntonces debo ver la etiqueta H2 con el valor "Alta de Huerta"Cuando visito la página de alta de huertoEntonces debo ver la etiqueta H2 con el valor "Alta de Huerta"Cuando visito la página de nueva huertaEntonces debo ver la etiqueta H2 con el valor "Alta de Huerta"
Dado que visito la página de creación de huertoEntonces debo ver la etiqueta H2 con el valor "Alta de Huerta"
Dado que tenemos un huerto llamado "H-01"Y que dicho huerto tiene los siguientes aspersores: | nombre | caudal | unidad caudal || A-01 | 15 | m3 || A-02 | 12 | m3 || B-01 | 10 | m3 |Entonces tenemos en bbdd un huertoY tenemos en bbdd tres aspersoresY el huerto "H-01" tiene en bbdd un aspersor "A-01"Y el aspersor "A-01" tiene en bbdd como caudal "15"Y tiene en bbdd como unidad caudal "m3"Y el huerto "H-01" tiene en bbdd un aspersor "A-02"Y el aspersor "A-02" tiene en bbdd como caudal "12"Y tiene en bbdd como unidad caudal "m3"Y el huerto "H-01" tiene en bbdd un aspersor "B-01"Y el aspersor "B-01" tiene en bbdd como caudal "10"Y tiene en bbdd como unidad caudal "m3"
Recapitulando
• Aceptación Web
• Valor para los devs
• Steak (‘cause Cucumber
is for veggies)
• Otros amiguitos...
• Testing de aceptación Web
• Beneficio para los devs
• Alternativas a Cucumber
Gracias!
Gracias!
¿Preguntas?
Agile Spain 2010
@porras y @cavalle