1. CURSO DE TESTING OSL
12 – 16 DE ABRIL 2010
Cucumber y BDD
Alberto Perdomo
Web: http://albertoperdomo.net
Email: alberto.perdomo@aentos.es
Twitter: @albertoperdomo http://www.aentos.com
2. EL PROBLEMA DEL
ENTENDIMIENTO
→ Los clientes hablan el lenguaje de su negocio
→ Los desarrolladores hablan lenguaje técnico
→ ¿Cómo escribir especifcaciones que podamos entender
todos?
3. EL MOVIMIENTO BDD
→ BDD = Behaviour Driven Development
→ una nueva forma de enfocar el TDD
→ Origen: Dan North → http://blog.dannorth.net/introducing-bdd/
→ Criterios de aceptación ejecutables
4. EL MOVIMIENTO DSL
→ DSL = Domain Specifc Languange
→ Lenguaje de programación enfocado a un determinado
problema o dominio en particular
→ Negocios
→ Construcción
→ ...
6. CUCUMBER
→ Librería de tests de aceptación
→ ¡Historias de usuario! (en texto plano)
como criterios de aceptación y test
→ Web en http://cukes.info/
8. FEATURE: CARACTERÍSTICA
BUSINESS VALUE
Feature: Some terse yet descriptive text of what is desired
In order to realize a named business value
As an explicit system actor
I want to gain some beneficial outcome which furthers the goal
Scenario: Some determinable business situation
¿Para qué sirve esta
Given some precondition Funcionalidad?
And some other precondition
When some action by the actor
And some other action
¿Qué valor aporta?
And yet another action
Then some testable outcome is achieved ¿A quién va dirigida?
And something else we can check happens too
Scenario: A different situation
...
¡HISTORIA DE USUARIO Y TEST EJECUTABLE!
No tengas más de 4-6 escenarios por característica
e intenta separar las características de forma que tengan sentido
9. SCENARIOS
Una característica puede tener mútiples escenarios ESCENARIO
Feature: Some terse yet descriptive text of what is desired
In order to realize a named business value
As an explicit system actor
I want to gain some beneficial outcome which furthers the goal
Scenario: Some determinable business situation
Caso de uso,
Given some precondition Ejemplo
And some other precondition
When some action by the actor
And some other action
And yet another action
Then some testable outcome is achieved
And something else we can check happens too
Scenario: A different situation
...
GIVEN-WHEN-THEN steps
¡uno por línea!
10. GIVEN-WHEN-THEN
Given → Precondición
When → Acción
Then → Resultado
And / But → conectar steps
¡El orden no importa!
11. CUCUMBER: ELEMENTOS
FEATURE
features/login.feature
CRITERIOS DE
Given a user with email "admin@example.com"
ACEPTACIÓN
(TEXTO PLANO)
Cucumber busca la
defnición de los steps
usando expresiones regulares
features/step_defnitions/user_steps.rb
STEP DEFINITIONS
Given /^a user with email "(.*)"$/ do |email|
user = Factory(:user, :email => email)
(RUBY) end
12. CUCUMBER: STEPS
→ ¡Los steps son reusables! (si se escriben bien)
→ Utiliza variables
→ Utiliza parámetros o variables opcionales
When /^I login as "(.*)"$/ do |login|
visit login_path
fill_in("Login", :with => login)
fill_in("Password", :with => "secret")
click_button("Login")
end
13. CUCUMBER: PROBLEMA
Pero...
el cliente y yo hablamos español, no inglés...
15. CUCUMBER: EJEMPLO EN
ESPAÑOL
Hay que defnir el idioma en el que está escrito el feature
#language: es
Característica: Empresas gestión
Para tener una relación de las empresas con las que trabajo
Como usuario con rol admin
Administro las empresas en el sistema
Escenario: Registro de una nueva empresa
Dado un usuario con rol "admin" "Fred"
Cuando inicio sesión como "Fred"
Y visito el listado de empresas
Cuando pulso el enlace "Nueva empresa"
Y registro una empresa
Entonces debería ver "Empresa registrada correctamente"
Y debería ver "Listado de empresas"
16. LIBRERÍAS Y STEPS GENÉRICOS
→ Librerías para probar aplicaciones Rails, Javascript y en
general cualquier aplicación (web o no)
→ Rails
→ PHP, Java, .NET
→ una web cualquier, p.ej. Google
→ Steps predefnidos para lo más común:
→ Pulsar botones, seguir enlaces, rellenar campos,
comprobar la presencia de textos, etc.
17. CUCUMBER
FEATURES
STEPS
Libs. de tests para web Factorías, Otras libs.
(emulan o usan un navegador) Mocks, ... en Ruby
APLICACIÓN
18. LIBRERÍAS PARA WEB
Capybara, Webrat, etc.
→ Emulando un navegador: más rápido, sin JS
→ Utilizando un navegador: más lento, con JS
→ Culerity (sin interfaz gráfco, ideal)
→ Selenium (Firefox)
→ Watir (Firefox, Safari, Chrome, IE)
→ ...
19. LA VELOCIDAD IMPORTA
Para probar funcionalidades estándar
→ modo emulación → velocidad
Para probar funcionalidades con AJAX o con efectos JS
→ modo navegador → más lento
→ culerity (relativamente rápido, no hay interfaz gráfca)
→ otros (más lento pero sirve para probar en navegadores
específcos)
20. WEBRAT vs. CAPYBARA
Webrat
→ compatible con muchos navegadores
→ se lanzan las pruebas en un modo espec., p.ej. Selenium
→ hay que lanzar las pruebas con/sin JS de forma separada
Capybara
→ más reciente, confguración más sencilla
→ igual o más compatible
→ se puede cambiar la sesión en una misma ejecución de tests
→ API compatible con Webrat
21. CUCUMBER: VENTAJAS
→ Criterios de aceptación
→ Especifcación y test
→ en un mismo documento
→ en el lenguaje del cliente
→ en su idioma
→ vocabulario compartido
→ Evitamos confusiones al transformar especifcación en
funcionalidades o tests unitarios
→ Ayudar a enfocarnos en las funcionalidades más valiosas
→ Documentación
22. MUCHO MÁS
→ Hooks
→ Tags
→ Tagged hooks
→ Multiline arguments
→ FIT tables
→ Background
23. CUCUMBER EN RAILS:
ESTRUCTURA
features/ → directorio de cucumber
features/*.feature → características
features/login.feature
features/realizar_pedido.feature
...
features/step_defnitions/*.rb → implementación de los steps
features/step_defnitions/web_steps.rb → steps para visitar, clickar, etc. (de serie)
features/step_defnitions/web_steps_es.rb → traducción al español (de serie)
features/step_defnitions/pedido_steps.rb → steps relacionados con pedidos
...
features/support/env.rb → conf. de cucumber
features/support/paths.rb → traducción de rutas
24. STEPS WEB EN ESPAÑOL I
Dado /^que estoy en (.+)$/ do |page_name|
Given %{I am on #{page_name}}
end
Cuando /^voy a (.+)$/ do |page_name|
When %{I go to #{page_name}}
end
Cuando /^pulso "([^"]*)"$/ do |button|
When %{I press "#{button}"}
end
Cuando /^hago click en "([^"]*)"$/ do |link|
When %{I follow "#{link}"}
end
Cuando /^completo "([^"]*)" con "([^"]*)"$/ do |field, value|
When %{I fill in "#{field}" with "#{value}"}
end
¡Vienen de serie!
25. STEPS WEB EN ESPAÑOL II
Cuando /^selecciono "([^"]*)" de "([^"]*)"$/ do |value, field|
When %{I select "#{value}" from "#{field}"}
end
Entonces /^debería ver "([^"]*)"$/ do |text|
Then %{I should see "#{text}"}
end
Entonces /^no debería ver "([^"]*)"$/ do |text|
Then %{I should not see "#{text}"}
end
Entonces /^debería estar en (.+)$/ do |page_name|
Then %{I should be on #{page_name}}
end
Entonces /^múestrame la página$/ do
Then %{show me the page}
end
y muchos más...
26. IMPLEMENTACIÓN DE STEPS
Given /^(?:|I )am on (.+)$/ do |page_name|
visit path_to(page_name)
end
When /^(?:|I )go to (.+)$/ do |page_name|
visit path_to(page_name)
end
When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
with_scope(selector) do
click_button(button)
end
end
When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
with_scope(selector) do
click_link(link)
end
end
When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector|
with_scope(selector) do
fill_in(field, :with => value)
end
end
27. EJEMPLO BDD: PASO 1
Defnimos el valor de negocio y un escenario
features/sesion.feature
#language: es
Característica: Iniciar y cerrar sesión
Para poder identificarme correctamente y realizar mis pedidos de forma segura
Como usuario
Quiero poder iniciar y cerrar sesión
Escenario: Login correcto
Dado un usuario con email "roger@test.com"
Cuando voy a la portada
Y inicio sesión como "roger@test.com"
Entonces debería ver "Bienvenido"
28. EJEMPLO BDD: PASO 2
Ejecutamos el escenario
$ cucumber features/sesion.feature
VERDE: OK
AMARILLO: NO DEFINIDO
AZUL: SALTADO
ROJO: ERROR
Resumen
Ayuda para
Implementar
los steps
29. EJEMPLO BDD: PASO 3
Implementar el primer step pendiente
(si lo hay)
features/step_defnitions/user_steps.rb
Dado /^un usuario con email "([^"]*)"$/ do |email|
Factory(:user, :email => email)
end
30. EJEMPLO BDD: PASO 4
Volvemos a ejecutar el escenario
Ejecutar de nuevo
Si hay errores → paso 5
Si hay steps pendientes → paso 3
Si está todo OK → siguiente escenario o característica
31. EJEMPLO BDD: PASO 3
Implementar o arreglar el step
features/step_defnitions/web_steps_es.rb features/support/paths.rb
Cuando /^voy a (.+)$/ do |page_name| module NavigationHelpers
When %{I go to #{page_name}}
end def path_to(page_name)
case page_name
when /la portada/
'/'
features/step_defnitions/web_steps.rb ....
When /^(?:|I )go to (.+)$/ do |page_name|
visit path_to(page_name)
end
Añadimos la ruta
32. EJEMPLO BDD: PASO 4
Volvemos a ejecutar el escenario
Ejecutar de nuevo
Si hay errores → paso 5
Si hay steps pendientes → paso 3
Si está todo OK → siguiente escenario o característica
33. EJEMPLO BDD: PASO 5
Implementar la funcionalidad para que el step pase
confg/routes.rb
map.root :controller => "user_sessions", :action => "new" # optional, this just sets the root route
34. EJEMPLO BDD: PASO 6
Volvemos a ejecutar el escenario
Ejecutar de nuevo
Si hay errores → paso 5
Si hay steps pendientes → paso 3
Si está todo OK → siguiente escenario o característica
35. EJEMPLO BDD: PASO 3
Implementar o arreglar el step
features/step_defnitions/user_steps.rb
Cuando /^inicio sesión con email "([^"]*)"$/ do |email|
fill_in("Email", :with => email)
fill_in("Contraseña", :with => "secret")
click_button("Acceder")
end
36. EJEMPLO BDD: PASO 4
Volvemos a ejecutar el escenario
Ejecutar de nuevo
Si hay errores → paso 5
Si hay steps pendientes → paso 3
Si está todo OK → siguiente escenario o característica
37. EJEMPLO BDD: PASO 5
Implementar la funcionalidad para que el step pase
app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
@user_session = UserSession.new
end
app/views/user_sessions/new.html.erb
<h1>Iniciar sesión</h1>
<% form_for @user_session, :url => user_session_path do |f| %>
<%= f.error_messages %>
<%= f.label :email %><br />
<%= f.text_field :email %><br />
<br />
<%= f.label :password, "Contraseña" %><br />
<%= f.password_field :password %><br />
<br />
<%= f.check_box :remember_me, "No cerrar sesión" %><%= f.label :remember_me %><br />
<br />
<%= f.submit "Iniciar sesión" %>
<% end %>
38. EJEMPLO BDD: PASO 6
Ejecutar de nuevo
Si hay errores → paso 5
Si hay steps pendientes → paso 3
Si está todo OK → siguiente escenario o característica
39. EJEMPLO BDD: PASO 5
Implementar la funcionalidad para que el step pase
app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
before_filter :require_no_user, :only => [:new, :create]
before_filter :require_user, :only => :destroy
def new
@user_session = UserSession.new
end
def create
@user_session = UserSession.new(params[:user_session])
if @user_session.save
flash[:notice] = "Bienvenido"
redirect_back_or_default account_url
else
render :action => :new
end
end
40. EJEMPLO BDD: PASO 6
Ejecutar de nuevo
Si hay errores → paso 5
Si hay steps pendientes → paso 3
Si está todo OK → siguiente escenario o característica
TODO VERDE → HEMOS ACABADO