Intro a RSpec, BDD, webapps User Acceptance Testing

Preview:

DESCRIPTION

 

Citation preview

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 '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'

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'

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

Recommended