Intro a RSpec, BDD, webapps User Acceptance Testing
-
Upload
jean-michel-garnier -
Category
Technology
-
view
2.195 -
download
3
description
Transcript of Intro a RSpec, BDD, webapps User Acceptance Testing
Jean-Michel Garnier | 21croissants.com
XP Day France | 6 mai 2008
Introductionà RSpecFramework de BDD & Tests d'Acceptation
2
Bonjour! Hi! Hola!
Mon blog: 21croissants.com linqia Mind map: http://www.21croissants.com/bdd
3
Mind map de la présentation
Origine du BDD
Origine du BDD
1975: The Mythical Man-Month
● ¼ Spécificier● 1/6 Coder● Relire le code!
● ½ Tests !!!
1994: 1er framework Tests Unitaires
Kent Beck, article “Simple Smalltalk Testing”
Class: SetTestCase superclass: TestCase instance variables: empty full
SetTestCase>>setUp empty := Set new.
SetTestCase>>testAdd empty add: 5. self should: [empty includes: 5]
1998: Junit
class TestMath extends TestCase { public void testAdd() { int num1 = 2; int num2 = 2; int expected = 4; int result = Math.add(num1, num2); assertEquals(expected, result); }}
Ecrire les tests
avant
le code
2002: TDD
2003: TestDox (Junit) public class FooTest extends TestCase { public void testIsASingleton() {} public void testAReallyLongNameIsAGoodThing() {}}
Foo- is a singleton- a really long name is a good thing
génère la doc de la classe
2005: BDD=TDD+should+behaviour
class OldSchoolAgileDeveloperTest < TestCase def setup @ an_agile_developer = OldSchoolAgileDeveloper.new end
def test_should_write_tests_before_code assert_true @an_agile_developer.is_writing_code_before_tests? end
describe AgileDeveloperBDD, "behaves like a coooool dude" do before :each do @an_agile_developer= AgileDeveloperBDD.new end it {
@an_agile_developer.should be_writing_code_before_specs }
BDD is cool
TDD is old school
BDD is cool!
2008: La Controverse de Valladolid
Le BDD a-t-il une âme?
“L'apparition du terme BDD m'a énervé dès l'origine. Il n'y a rien de nouveau par rapport au TDD, on a juste l'impression que des gens on voulu se rendre intéressant en critiquant le TDD (mal fait) et en utilisant un nouveau terme pour ce qui n'est autre que le TDD (bien fait).”
D.W.(19/02/2008 8:09)
Le BDD en une phrase
"I would say that TDD is a tool to help you solve the problem of designing and implementing behavior. xUnit works fine in that regard, but RSpec reduces the semantic distance between the developer and the problem domain."
Pat Maddox (26/07/2007 00:47)
2006 - Présent:
javascript
● easyj● Instinct● jtestr
● RSpec● expectation● bacon● shoulda
● JSSpec● Screw.Unit
● phpSpec● .net● erlang ● ...
Livres pour 2008
RSpecFramework de
BDD
RSpec
Anatomie d'une “spec”
describe Spec do before :each do @spec = Spec.new end
describe "après avoir été créée" do before :each do end it "devrait spécifier une seule classe" it "devrait avoir 0 comportements décrits" # ... end
describe "Lors de l'execution:" do # ... C
ompo
rtem
ents
Exemples
exécutable
Pending:. Une spec, après avoir été créée devrait spécifier une seule classe (Not Yet Implemented). Une spec, après avoir été créée devrait avoir 0 comportements décrits (Not Yet Implemented)
Finished in 0.159 seconds
2 examples, 0 failures, 2 pending
Anatomie d'un “exemple”
it "devrait spécifier une classe" do @spec.specified_class.should == Spec end
# Old School syntaxe TDD: it "devrait spécifier une classe" do assert_equals(Spec, @spec.specified_class) end
it "devrait avoir 0 comportements décrits" do @spec.should have(0).behaviours end
# Old School syntaxe TDD: it "devrait avoir 0 comportements décrits" do assert_equals(0, @spec.behaviours.size) end
Plus de diff entre spec & code
On écrit en langage “naturel” (VO)
“fluent interface” de Martin Fowler
'foo'.should == 'foo' ''.should be_empty 'foo with bar'.should include('with') 'http://21croissants.com'.should match(/http:\/\/.+/i) nil.should be_nil 100.should < 200 (200 - 100).should == 100
[1,2,3].should have(3).items [].should be_empty [1,2,3].should include(2)
Tester en isolation avec les “mocks”
describe Person, "age" do
before :each do @bob = Person.new(:birth_date => "23/08/1975") end
it "should calculate age depending on birthday" do un_jour_sans_fin = Date.strptime('06/05/2008', '%d/%m/%Y') Time.should_receive(:now).and_return(un_jour_sans_fin) @bob.age.should == 32 end
end
Exemple: EcoComparateur
PARIS → MARSEILLE
EcoComparateur
En tant qu'eco-citoyen
Je veux connaître les emissions de CO2 d'un 4x4 pour un trajet A/R donné
Afin de réduire mon empreinte carbone
scénario 1
bla bla bla
scénario 1
bla bla bla
exemples
bla bla bla4x4Paris → Marseille
bla bla bla
1er exemple
EcoComparateurwhen it calculates CO2emissions for a 'Paris-Marseille return', travelling with a SUV: should find 313kg of CO2
Identifier les “classes”
EcoComparateur when it calculates CO2emissions for a 'Paris-Marseille return', travelling with a SUV: should find 313kg of CO2
describe EcoComparateur do
describe "when it calculates CO2 emissions for a Paris-Marseille return," do
describe "travelling with a SUV:" do it "should find 313kg of CO2"
...
Conversion a RSpec
“pending”
EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV:- should find 313kg of CO2 (PENDING: Not Yet Implemented)
Pending:EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV: should find 313kg of CO2 (Not Yet Implemented)
Finished in 0.053053 seconds
1 example, 0 failures, 1 pending
describe EcoComparateur do before(:each) do @eco_comparateur = EcoComparateur.new end
describe "when it calculates CO2 emissions for a Paris-Marseille return," do describe "travelling with a SUV:" do before :each do @suv = mock("Car") @suv.should_receive(:kg_of_co2_per_km).and_return(20) end it "should find 313kg of CO2" do
@eco_comparateur.calculate_co2_emissions_for_an(@suv).travelling("Paris", "Marseille").
should == 313 end
...
class EcoComparateur
def calculate_co2_emissions_for_an(vehicle) chain do travelling do |from, to| # TODO Implementation goes HERE! end end end
Implémentation du squelette
EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV:- should find 313kg of CO2 (FAILED - 1)
1)'EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV: should find 313kg of CO2' FAILEDexpected "find 313 kg of CO2" but got nil./spec/eco_comparateur_spec.rb:23:
Finished in 0.186483 seconds
1 example, 1 failure
Failure
On se “mock” des autres classes!
Comment calculer la distance entre Paris et Marseille?
On s'en mock!
describe EcoComparateur do before(:each) do @geocoding_mock = mock("GeocodingHelper") @eco_comparateur = EcoComparateur.new(@geocoding_mock) end
describe "when it calculates CO2 emissions for a Paris-Marseille return," do before :each do @geocoding_mock.should_receive(:calculate_distance_in_km).
with("Paris", "Marseille").and_return(783) end
describe "travelling with a SUV:" do before :each do @suv = mock("Car") @suv.should_receive(:kg_of_co2_per_km).and_return(20) end it "should find 313kg of CO2" do @eco_comparateur.calculate_co2_emissions_for_an(@suv).
travelling("Paris", "Marseille").should find 313 }
end
it "should tell an inconvenient truth to the SUV owner ..."
class EcoComparateur def initialize(geocoding_helper) @geocoding_helper = geocoding_helper end
def calculate_co2_emissions_for_an(vehicle) chain do travelling do |from, to| distance = geocoding_helper. calculate_distance_in_km(from, to) 2 * distance * vehicle.kg_of_co2_per_km end end end
Implémentation
EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV:- should find 313kg of CO2
Finished in 0.061868 seconds
1 example, 0 failures
“success” !
Avantages
Design
● Le code de qualité est facile à tester● TDD = moins de code “mort”● Utilisation de “mocks” pour définir l'API
(Design incrémental)
Paris On Rail 2007 – Copyright (c) Garnier Jean-Michel. Licence: Creative Commons.
Définition API
Spécifications
La doc des classes s'écrit toute seule...
Documentation exécutable
↔ Documentation toujours à jour !
Tests d'acceptation
utilisateur
Tests IHM webet d'acceptation utilisateur
selenium-grid by Mr. Philippe Hanrigou
“grid” de machines avec selenium machines virtuels :
mac mini +Parrallel
SUPER RAPIDE! capture d'écrans (ALT + TAB)
Il y aussi : Watir / webrat
RSpec
selenium-grid
selenium-core (js)
GRID de machines (physiques/virtuelles)
Le format de “Story” de Dan North
Login d'un utilisateur
En tant qu'utilisateur
Je veux que l'accès à l'application nécessite un login / mot de passe
Afin de protéger mes données personnelles
Critères d'acceptation: scénarios
Scenario 1: L'utilisateur se trompe de password
Etant donné que je suis ds la page de '/member/login'Lorsque je tape '[email protected]'
dans le champ 'email'Lorsque je tape 'xpday2008' dans le champ 'password'Lorsque je clique sur le bouton 'signin-form-submit'Alors je devrais voir le texte 'Linqia Member Log-in'Alors je devrais voir le message d'erreur
'Invalid login or password'
Jean-Claude VanDamisation Scenario: 1. L'utilisateur se trompe de password
Given que je suis dans la page de '/member/login' When je tape '[email protected]'
dans le champ 'email' When je tape 'xpday2008' dans le champ 'password' When je clique sur le bouton 'signin-form-submit' Then je devrais voir le texte 'Linqia Member Log-in' Then je devrais voir le message d'erreur
'Invalid login or password'
Binding texte / RSpec
Given que je suis dans la la page de '/member/login'
steps_for(:login) do
Given "que je suis dans la la page de '$path'" do |path| $browser.open path end .......end
Binding (suite) When "je tape '$value' dans le champ '$field_name'"do|value, field_name| $browser.type field_name, value end When "je clique sur le bouton '$link'" do |link| $browser.click_and_wait link end
Then "je devrais voir le texte '$text'" do |text| text.should be_present end
Demo
54
Merci de votre attention!
Questions - Réponses
Bonus
Bonus (s'il reste du rab!)
L'équipe de RSpec: David, Aslak et Dan Autotest Couverture Heckle Mingle
David Chelimsky
Aslak Hellesøy (NO)
Dan North
+ Brian Takita Dave Astels Steve Baker Luke Redpath
jbehave (2004) rbehave Intégration dans RSpec
Autotest
● Problème: les specs s'executent pendant 10 min...● Solution: Autotest n'execute que les specs
nécessaires
Notifications visuelles (plugins Growl, Notify,...) et sonores
http://ph7spot.com/articles/getting_started_with_autotest
Paris On Rail 2007 – Copyright (c) Garnier Jean-Michel. Licence: Creative Commons.
Garantir la couverture avec rcov
● sudo gem install rcov● rake spec:rcov
http://eigenclass.org/hiki.rb?rcov
Paris On Rail 2007 – Copyright (c) Garnier Jean-Michel. Licence: Creative Commons.
Couverture détaillée
Heckle
Mingle