Symfony2 Specification by examples

53
Specification by Examples for Symfony2 applications

description

BDD Symfony2 - Behat - Specification by Examples

Transcript of Symfony2 Specification by examples

Page 1: Symfony2   Specification by examples

Specification by Examplesfor Symfony2 applications

Page 2: Symfony2   Specification by examples

About meCofounder Cofounder

on Github on Twitter

- PHP & Cloud @ Genova - 24 Oct - Internet Of Things! @ Turin [CFP] - 15 Nov

- CloudComputing @ Turin [CFP ASAP]

Corley S.r.l. - @CorleyCloudUpCloo LTD - @UpCloo

wdalmutwalterdalmut

www.cloudparty.itinternetof.itwww.cloudconf.it

Page 3: Symfony2   Specification by examples

Repo with all examplesRepo on github

Page 4: Symfony2   Specification by examples

Software deliverycustomer wants Aproject manager understand Bdevelopers write down Cdevelopers effectively write down Dcustomer realize to get E

Page 5: Symfony2   Specification by examples

Successive refinements of EE'E''E'''E''''E'''''

The customer will never obtain an A

Page 6: Symfony2   Specification by examples

The problem is to achieve asingle communication domain

Page 7: Symfony2   Specification by examples

How many points does it have?Example from: Bridging the communication gap (Gojko Adzic)

Page 8: Symfony2   Specification by examples

10

Page 9: Symfony2   Specification by examples

5

Page 10: Symfony2   Specification by examples

14

Page 11: Symfony2   Specification by examples

9

Page 12: Symfony2   Specification by examples

Effectively, there is no rightanswer.

It depends on what you consider as a point

Page 13: Symfony2   Specification by examples

Behaviour-driven developmentis a software development process based on test-driven development(TDD). Behavior-driven development combines the general techniques

and principles of TDD with ideas from domain-driven design and object-oriented analysis and design to provide software development and

management teams with shared tools and a shared process tocollaborate on software development.

from wikipedia

Page 14: Symfony2   Specification by examples

BDDAlthough BDD is principally an idea about how software developmentshould be managed by both business interests and technical insight

from wikipedia

Page 15: Symfony2   Specification by examples

Dan North: What is a story?Behaviour-driven development is an “outside-in” methodology. It startsat the outside by identifying business outcomes, and then drills downinto the feature set that will achieve those outcomes. Each feature is

captured as a “story”, which defines the scope of the feature along withits acceptance criteria. This article introduces the BDD approach to

defining and identifying stories and their acceptance criteria.Read the article here

Page 16: Symfony2   Specification by examples

BDD is more than tools, but weare here in order to use BDD

methodology in our Symfony2projects

Page 17: Symfony2   Specification by examples

ToolsBehat - Behaviour-driven framework

Mink - Web Acceptance testing framework

Page 18: Symfony2   Specification by examples

Pay attention on Behat versionThe latest is 3.0 (at today) but there are so many examples and

docs for 2.5

We will work with 3.0

Page 19: Symfony2   Specification by examples

Here is a storyFeature: a description of this feature

        Scenario: A business situation            Given a precondition            And another precondition            When an actor execute an action            And another action appends            Then some testable result is in place            And another condition should be verified

        Scenario: Another business scenario starts

Page 20: Symfony2   Specification by examples

Here is a story in ItalianFunzionalità: una generica descrizione di questa funzionalità

        Scenario: una situazione di business            Data una precondizione            E ancora una nuova precondizione            Quando un attore esegue una azione            E succede anche un altra azione            Allora esite un risultato testabile            Ed un nuova condizione da verificare

        Scenario: Un altro scenario di business da verificare

Page 21: Symfony2   Specification by examples

Gherkin is a Business Readableand Domain Specific Languagethat let us describe software’s

behaviour without detailinghow that behaviour is

implemented.From the cucumber project

Page 22: Symfony2   Specification by examples

Gherkin supports so manylanguages

We are building a common language withdifferent actors involved into to design

process

Page 23: Symfony2   Specification by examples

There are at least two ways to use behat inyour Symfony2 applications

Per-bundle featuresSpecific features per bundle

Application featuresAll features in a single folder

Page 24: Symfony2   Specification by examples

Personally prefer a single place for allfeatures

Page 25: Symfony2   Specification by examples

Mainly because it is so difficult to share theapplication bundle strategies to a

customerKeep the focus on the communication not

how we implement the solution

Page 26: Symfony2   Specification by examples

behat.ymldefault:    suites:        default:            path: %paths.base%/features            contexts: [Behat\MinkExtension\Context\MinkContext]    extensions:        Behat\Symfony2Extension: ~        Behat\MinkExtension:            base_url: http://localhost:8000            sessions:                default:                    symfony2: ~

Page 27: Symfony2   Specification by examples

Behat uses a "FeatureContext"as a clue for join the Gherkin

language with the application

Page 28: Symfony2   Specification by examples

A featureFeature: An hello world feature example

    Scenario: Just say hello to me!        When I am on "/hello/walter"        Then I should see "Hello Walter"

Page 29: Symfony2   Specification by examples

Every step is a custom method in theFeatureContext

/** @Given /̂I am on "([̂"]*)"$/ */public function goOnPage($url){...}

Page 30: Symfony2   Specification by examples

Mink carries different predefined stepsI am on "a page"I press "Get me in!"I should see "Ok, you are in"and more... (CSS selectors etc.)

Page 31: Symfony2   Specification by examples

But remember that we are building acommon communication domain

Write down a new step that use a mink's steps instead force a newdefinition of something

Page 32: Symfony2   Specification by examples

By ExamplesScenario Outline: Just say hello to everyone!    When I am on <page>    Then I should see <hello>

    Examples:        | page              | hello            |        | "/hello/walter"   | "Hello Walter"   |        | "/hello/marco"    | "Hello Marco"    |        | "/hello/giovanni" | "Hello Giovanni" |        | "/hello/martina"  | "Hello Martina"  |

Page 33: Symfony2   Specification by examples

ExpectationsScenario Outline: Say a better hello to special guests    When I am on <page>    Then I should see <hello>

    Examples:        | page              | hello                   |        | "/hello/fabien"   | "Captain on the bridge" |        | "/hello/walter"   | "Hello Walter"          |        | "/hello/marco"    | "Hello Marco"           |        | "/hello/giovanni" | "Hello Giovanni"        |        | "/hello/martina"  | "Hello Martina"         |

Page 34: Symfony2   Specification by examples

If we run itFeature: An hello world feature example

Scenario Outline: Say a better hello to special guests    When I am on <page>    Then I should see <hello>

    Examples:    | page              | hello                   |    | "/hello/fabien"   | "Captain on the bridge" |        The text "Captain on the bridge" was not found        anywhere in the text of the current page. (Behat\Mink\Exception\ResponseTextException)    | "/hello/walter"   | "Hello Walter"          |    | "/hello/marco"    | "Hello Marco"           |    | "/hello/giovanni" | "Hello Giovanni"        |    | "/hello/martina"  | "Hello Martina"         |

‐‐‐ Failed scenarios:

    features/hello.feature:24

Page 35: Symfony2   Specification by examples

Fix the codeclass DefaultController extends Controller{    public function indexAction($name)    {        if ($name == "fabien") {            $name = "Captain on the bridge";        }

        return $this‐>render('CorleyBaseBundle:Default:index.html.twig', array('name' => $name));    }}

Page 36: Symfony2   Specification by examples

My steps interacts with the entity managerdefault:    suites:        default:            path: %paths.base%/features            contexts:                ‐ Behat\MinkExtension\Context\MinkContext                ‐ HelloFeatureContext:                    entityManager: '@doctrine.orm.entity_manager'    extensions:        Behat\Symfony2Extension: ~        Behat\MinkExtension:            base_url: http://localhost:8000            sessions:                default:                    symfony2: ~

Page 37: Symfony2   Specification by examples

Create contextsbin/behat ‐‐init

Page 38: Symfony2   Specification by examples

Context constructor/** * Behat context class. */class HelloFeatureContext implements SnippetAcceptingContext{    private $entityManager;

    /**     * Initializes context.     *     * Every scenario gets its own context object.     * You can also pass arbitrary arguments to the context constructor through behat.yml.     */    public function __construct(EntityManager $entityManager)    {        $this‐>entityManager = $entityManager;    }                        

Page 39: Symfony2   Specification by examples

My context clears all before runs/** * @BeforeScenario */public function clearDatabase(){    $purger = new ORMPurger($this‐>entityManager);    $purger‐>purge();}

Page 40: Symfony2   Specification by examples

Load books for this scenarioFeature: My books features

    Scenario: List my books        Given there are books            | title                          | author          |            | Specification by Examples      | Gojko Adzic     |            | Bridging the communication gap | Gojko Adzic     |            | The RSpec Book                 | David Chelimsky |        When I am on "/books"        Then I should see "Specification by Examples"        And I should see "Bridging the communication gap"        And I should see "The RSpec Book"

Page 41: Symfony2   Specification by examples

Run it!(and get the missing step definition)

‐‐‐ HelloFeatureContext has missing steps. Define them with these snippets:

    /**    * @Given there are books    */    public function thereAreBooks(TableNode $table)    {        throw new PendingException();    }

Page 42: Symfony2   Specification by examples

My custom step/** * @Given there are books */public function thereAreBooks(TableNode $table){    foreach ($table‐>getHash() as $row) {        $book = new Book();

        $book‐>setTitle($row["title"]);        $book‐>setAuthor($row["author"]);

        $this‐>entityManager‐>persist($book);    }    $this‐>entityManager‐>flush();}

Page 43: Symfony2   Specification by examples

Scenarios can share the same backgroundFeature: My books features

    Background:        Given there are books            | title                          | author          |            | Specification by Examples      | Gojko Adzic     |            | Bridging the communication gap | Gojko Adzic     |            | The RSpec Book                 | David Chelimsky |

    Scenario: List my books        When I am on "/books"        Then I should see "Specification by Examples"        And I should see "Bridging the communication gap"        And I should see "The RSpec Book"

Page 44: Symfony2   Specification by examples

A storyScenario: An interested user becomes an active user    Given an interested user with email "[email protected]"    When he goes to homepage    And he fills all personal fields    Then he confirms the registration    And he should be registered as an unconfirmed user    And he should receive the registration email    And he should be in the reserved area

Page 45: Symfony2   Specification by examples

Signup Feature Contextclass SignupContext implements SnippetAcceptingContext, MinkAwareContext    {        private $mink;        private $minkParameters;        private $entityManager;

    ...

        public function setMink(Mink $mink)        {            $this‐>mink = $mink;        }

        public function setMinkParameters(array $parameters)        {            $this‐>minkParameters = $parameters;        }    ...

Page 46: Symfony2   Specification by examples

GIVEN i am an interested user with email/** * @Given I am an interested user with email :arg1 */public function anInterestedUserWithEmail($email){    // shared in the feature context    $this‐>email = $email;

    // Check that is an interested user and not an existing one    Assert::assertNull(        $this‐>entityManager            ‐>getRepository("CorleyBaseBundle:User")‐>findOneByEmail($email)    );}

Page 47: Symfony2   Specification by examples

WHEN i fill all personal fields/** * @When I fill all personal fields */public function heFillsAllPersonalFields(){    $this‐>mink‐>getSession()‐>getPage()        ‐>find("css", "#corley_bundle_basebundle_user_email")        ‐>setValue($this‐>email);

    $this‐>mink‐>getSession()‐>getPage()        ‐>find("css", "#corley_bundle_basebundle_user_name")        ‐>setValue(rand(0,100000));}

Page 48: Symfony2   Specification by examples

THEN I confirm my registration/** * @Then I confirm my registration */public function iConfirmTheRegistration(){    $client = $this‐>mink        ‐>getSession()        ‐>getDriver()        ‐>getClient();    $client‐>followRedirects(false);

    $this‐>mink‐>getSession()‐>getPage()        ‐>find("css", "#corley_bundle_basebundle_user_submit")‐>click();}

Page 49: Symfony2   Specification by examples

THEN I should be registered as anunconfirmed user

/** * @Then I should be registered as an unconfirmed user */public function heShouldBeRegisteredAsAnUnconfirmedUser(){    $this‐>entityManager‐>clear();

    $entity = $this‐>entityManager        ‐>getRepository("CorleyBaseBundle:User")        ‐>findOneByEmail($this‐>email);

    Assert::assertNotNull($entity);    Assert::assertFalse($entity‐>isConfirmed());}

Page 50: Symfony2   Specification by examples

THEN I should receive the reg. email/** * @Then I should receive the registration email */public function heShouldReceiveTheRegistrationEmail(){    $driver = $this‐>mink‐>getSession()‐>getDriver();    if (!$driver instanceof KernelDriver) {        throw new \RuntimeException("Only kernel drivers");    }

    $profile = $driver‐>getClient()‐>getProfile();    if (false === $profile) {        throw new \RuntimeException("Profiler is disabled");    }

    $collector = $profile‐>getCollector('swiftmailer');    Assert::assertCount(1, $collector‐>getMessages());}

You can wrap a "getProfiler" in a separate method

Page 51: Symfony2   Specification by examples

THEN I should be in the reserved area/** * @Then I should be in the reserved area */public function heShouldBeInTheReservedArea(){    $client = $this‐>mink        ‐>getSession()        ‐>getDriver()        ‐>getClient();    $client‐>followRedirects(true);    $client‐>followRedirect();

    $this‐>mink‐>assertSession()‐>pageTextContains("Hello reserved area!");}

You can wrap the redirection in seperate, reusable steps

Page 52: Symfony2   Specification by examples

There are so many features in BehatBut, remember that the goal is to bridging the communication gap

between actors of the same project with different backgroundsDo not use it for functional testing

(use LiipFunctionalTestBundle for that or anything else)

Page 53: Symfony2   Specification by examples

Thanks for listening

Any question?