Slideshow transcript
Slide 1: Introduction à RSpec Framework de BDD & Tests d'Acceptation Jean-Michel Garnier | 21croissants.com XP Day France | 6 mai 2008
Slide 2: Bonjour! Hi! Hola! Mon blog: 21croissants.com linqia Mind map: http://www.21croissants.com/bdd 2
Slide 3: Mind map de la présentation 3
Slide 4: Origine du BDD
Slide 5: Origine du BDD
Slide 6: 1975: The Mythical Man-Month ● ¼ Spécificier ● 1/6 Coder ● Relire le code! ● ½ Tests !!!
Slide 7: 1994: 1er framework Tests Unitaires Class: SetTestCase superclass: TestCase instance variables: empty full SetTestCase>>setUp empty := Set new. SetTestCase>>testAdd empty add: 5. self should: [empty includes: 5] Kent Beck, article “Simple Smalltalk Testing”
Slide 8: 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); } }
Slide 9: 2002: TDD Ecrire les tests avant le code
Slide 10: 2003: TestDox (Junit) public class FooTest extends TestCase { public void testIsASingleton() {} public void testAReallyLongNameIsAGoodThing() {} } génère la doc de la classe Foo - is a singleton - a really long name is a good thing
Slide 11: 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
Slide 12: BDD is cool 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 }
Slide 13: TDD is old school BDD is cool!
Slide 14: 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)
Slide 15: 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)
Slide 16: 2006 - Présent: javascript ● JSSpec ● RSpec ● Screw.Unit ● expectation ● bacon ● easyj ● phpSpec ● shoulda ● Instinct ● .net ● jtestr ● erlang ● ...
Slide 17: Livres pour 2008
Slide 18: RSpec Framework de BDD
Slide 19: RSpec
Slide 20: Anatomie d'une “spec” describe Spec do before :each do @spec = Spec.new end describe "après avoir été créée" do Comportements before :each do end it "devrait spécifier une seule classe" it "devrait avoir 0 comportements décrits" Exemples # ... end describe "Lors de l'execution:" do # ...
Slide 21: 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
Slide 22: 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
Slide 23: Plus de diff entre spec & code 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
Slide 24: 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)
Slide 25: 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
Slide 26: Exemple: EcoComparateur PARIS → MARSEILLE
Slide 27: 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 exemples scénario 1 scénario 1 bla bla bla bla blabla bla bla bla4x4 Paris → Marseille bla bla bla
Slide 28: er 1 exemple EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV: should find 313kg of CO2
Slide 29: Identifier les “classes” EcoComparateur when it calculates CO2 emissions for a 'Paris-Marseille return', travelling with a SUV: should find 313kg of CO2
Slide 30: Conversion a RSpec 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" ...
Slide 31: “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
Slide 32: 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 ...
Slide 33: Implémentation du squelette class EcoComparateur def calculate_co2_emissions_for_an(vehicle) chain do travelling do |from, to| # TODO Implementation goes HERE! end end end
Slide 34: Failure 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' FAILED expected "find 313 kg of CO2" but got nil ./spec/eco_comparateur_spec.rb:23: Finished in 0.186483 seconds 1 example, 1 failure
Slide 35: On se “mock” des autres classes! Comment calculer la distance entre Paris et Marseille? On s'en mock!
Slide 36: 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 ..."
Slide 37: Implémentation 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
Slide 38: “success” ! 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
Slide 39: Avantages
Slide 40: 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.
Slide 41: Définition API
Slide 42: Spécifications
Slide 43: La doc des classes s'écrit toute seule... Documentation exécutable ↔ Documentation toujours à jour !
Slide 44: Tests d'acceptation utilisateur
Slide 45: Tests IHM web et d'acceptation utilisateur
Slide 46: selenium-grid by Mr. Philippe Hanrigou “grid” de machines avec selenium machines virtuels : mac mini +Parrallel SUPER RAPIDE! capture d'écrans (ALT + TAB)
Slide 47: GRID de machines (physiques/virtuelles) selenium -core (js) selenium-grid RSpec
Slide 48: 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
Slide 49: 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 'jean-michel@21croissants.fr' 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'
Slide 50: Jean-Claude VanDamisation Scenario: 1. L'utilisateur se trompe de password Given que je suis dans la page de '/member/login' When je tape 'jean-michel@21croissants.fr' 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'
Slide 51: 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
Slide 52: 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
Slide 53: Demo
Slide 54: Merci de votre attention! Questions - Réponses 54
Slide 55: Bonus
Slide 56: Bonus (s'il reste du rab!) L'équipe de RSpec: David, Aslak et Dan Autotest Couverture Heckle Mingle
Slide 57: David Chelimsky
Slide 58: Aslak Hellesøy (NO)
Slide 59: Dan North jbehave (2004) rbehave Intégration dans RSpec + Brian Takita Dave Astels Steve Baker Luke Redpath
Slide 60: 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.
Slide 61: 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.
Slide 62: Couverture détaillée
Slide 63: Heckle
Slide 64: Mingle




Add a comment on Slide 1
If you have a SlideShare account, login to comment; else you can comment as a guest- Favorites & Groups
Showing 1-50 of 0 (more)