Pruebas BDD de
      Aceptación con Ruby
                   Sergio Gil y Luismi Cavallé




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
Anatomía de una historia
    Feature: Serve coffee
      In order to earn money             Narrativa
      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


Criterio de Aceptación
Narrativa

Feature: Serve coffee
 In order to earn money
 Customers should be able to
 buy coffee at all times
Narrativa
                          Título

Feature: Serve coffee
 In order to earn money
 Customers should be able to
 buy coffee at all times
Narrativa

     Feature: Serve coffee
          In order to earn money
          Customers should be able to

  Rol     buy coffee at all times
¿Quién?
Narrativa

Feature: Serve coffee
 In order to earn money
 Customers should be able to
 buy coffee at all times

                           Funcionalidad
                              ¿Qué?
Narrativa

                           Beneficio
Feature: Serve coffee
                           ¿Por qué?
 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
Criterio de Aceptación
                                    Contexto
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$
                                       Eventos
 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
                                       Resultados
 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




                              NE
 Then I should be served a coffee



                             O
                            D
Beneficio para el negocio
Programadores              Testers




Analistas       Clientes   Managers
Beneficio para el negocio
Programadores                                      Testers



              Historia de Usuario
            Token de comunicación / discusión
            Priorización según beneficio / valor
              Criterio común de “terminado”




Analistas                Clientes                  Managers
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 “john” with password “secret”



Given /^a user "(.+)" with password "(.+)"$/ do |login, password|
Definición de pasos
      Given a user “john” with password “secret”



Given /^a user "(.+)" with password "(.+)"$/ do |login, password|
  User.create! :login => login, :password => password
end
Definición de pasos
When he tries to login as “james”
Definición de pasos
    When he tries to login as “james”



When /^he tries to login as "(.+)"$/ do |login|
Definición de pasos
    When he tries to login as “james”



When /^he tries to login as "(.+)"$/ do |login|
  visit "/login"
  fill_in "Username", :with => login
  fill_in "Password", :with => "secret"
  click_button "Login"
end
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
http://vimeo.com/12441999
+40 Idiomas
# language: es
Caracterí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
  @billing
  Feature: Verify billing

    @important
    Scenario: Missing product description

    Scenario: Several products




$ cucumber --tags @billing,~@important
Hooks
Before do
  @browser = Browser.new
end

After do
  @browser.close
end
Hooks
Before do
  @browser = Browser.new   Around do |scenario, block|
end                          Timeout.timeout(3) do
                               block.call
After do                     end
  @browser.close           end
end
Hooks
Before do
  @browser = Browser.new    Around do |scenario, block|
end                           Timeout.timeout(3) do
                                block.call
After do                      end
  @browser.close            end
end




                     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?
• Historias de Usuario
• Beneficio para el negocio
• Cucumber

  Coming next...
• Testing de aceptación Web
• Beneficio para los devs
• Alternativas a Cucumber
Aceptación para Web:
     Capybara
DSL de interacción web
(navegador programático)
Usa el lenguaje del usuario

   class
  method
GET / POST
parameters
Usa el lenguaje del usuario

   class           page
  method           URL
GET / POST          link
parameters         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")
  end
end
save_and_open_page
Drivers
RackTest
RackTest
Basado en Rack
RackTest
Basado en Rack


    FAST!
RackTest
Basado en Rack


    FAST!
Sin Javascript
RackTest
Basado en Rack


    FAST!
Sin Javascript
 No es ‘real’
Selenium
Selenium
Basado en browsers programables
Selenium
Basado en browsers programables


          Javascript
Selenium
Basado en browsers programables


          Javascript
        Aceptación pura
Selenium
Basado en browsers programables


          Javascript
        Aceptación pura
           ¡LENTO!
Selenium
Basado en browsers programables


          Javascript
        Aceptación pura
           ¡LENTO!
         Un poco frágil
Selenium
Basado en browsers programables


          Javascript
        Aceptación pura
           ¡LENTO!
         Un poco frágil
             Java
Celerity / Culerity
Celerity / Culerity
   Basado en HTMLUnit
Celerity / Culerity
   Basado en HTMLUnit


       Javascript
Celerity / Culerity
   Basado en HTMLUnit


       Javascript
   Aceptación casi pura
Celerity / Culerity
   Basado en HTMLUnit


       Javascript
   Aceptación casi pura
      No muy lento
Celerity / Culerity
   Basado en HTMLUnit


       Javascript
   Aceptación casi pura
      No muy lento
          Java
Envjs
Envjs
Basado en SpiderMonkey
Envjs
Basado en SpiderMonkey


      Javascript
Envjs
Basado en SpiderMonkey


      Javascript
  Aceptación casi pura
Envjs
Basado en SpiderMonkey


      Javascript
  Aceptación casi pura
     No muy lento
Envjs
Basado en SpiderMonkey


      Javascript
  Aceptación casi pura
     No muy lento
         Ruby
Envjs
Basado en SpiderMonkey


      Javascript
  Aceptación casi pura
     No muy lento
         Ruby
    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 QA
             só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
Up-front




   M
Up-front




   C

   M
Up-front



   V

   C

   M
Up-front

   T

   V

   C

   M
Up-front

   T
           T

   V

   C

   M
Up-front

       T
T              T

       V

       C

       M
Outside-in
Outside-in

    F
Outside-in

    F


    V
Outside-in

    F


    V
    C
Outside-in

    F


    V
    C
    M
Outside-in

                     F
    F                            F

                 V           V
         V
F




                                     F
             C C C
    V




                                 V
        C



                             C
                     MM
                 M       M
             M
“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
http://vimeo.com/12468092
Más amigos
Spork
Spork

Sin spork


Con spork


            0    3.25 6.50   9.75 13.00
Factorías
fixture_replacement
fixture_replacement
   factory_girl
fixture_replacement
   factory_girl
     machinist
fixture_replacement
   factory_girl
     machinist
      fixjour
fixture_replacement
   factory_girl
     machinist
      fixjour
      cranky
fixture_replacement
   factory_girl
     machinist
      fixjour
      cranky
        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_user
create_user(:name => "Bartolo")
create_article
create_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 = :truncation
DatabaseCleaner.clean
Database Cleaner


DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean
Email Spec
Email Spec

mailbox_for("porras@example.com").should have(1).email

open_email "porras@example.com"

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 huerto
Y una huerta
Y 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 huertos
Y 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 huertos
Y 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 huerto
Entonces debo   ver la etiqueta H2 con el valor "Alta de Huerta"
Cuando visito   la página de alta de huerto
Entonces debo   ver la etiqueta H2 con el valor "Alta de Huerta"
Cuando visito   la página de nueva huerta
Entonces debo   ver la etiqueta H2 con el valor "Alta de Huerta"

Dado que visito la página de creación de huerto
Entonces 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 huerto
Y tenemos en bbdd tres aspersores
Y 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...
Gracias!
Gracias!
                   @porras y @cavalle



                   ¿Preg untas?


Agile Spain 2010

BDD de Aceptación con Ruby