symfony2 specification by examples

Post on 31-May-2015

863 Views

Category:

Technology

5 Downloads

Preview:

Click to see full reader

DESCRIPTION

BDD Symfony2 - Behat - Specification by Examples

TRANSCRIPT

Specification by Examplesfor Symfony2 applications

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

Repo with all examplesRepo on github

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

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

The customer will never obtain an A

The problem is to achieve asingle communication domain

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

10

5

14

9

Effectively, there is no rightanswer.

It depends on what you consider as a point

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

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

from wikipedia

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

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

methodology in our Symfony2projects

ToolsBehat - Behaviour-driven framework

Mink - Web Acceptance testing framework

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

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

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

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

behaviour without detailinghow that behaviour is

implemented.From the cucumber project

Gherkin supports so manylanguages

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

process

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

Personally prefer a single place for allfeatures

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

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: ~

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

language with the application

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"

Every step is a custom method in theFeatureContext

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

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.)

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

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

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

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

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));    }}

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: ~

Create contextsbin/behat ‐‐init

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;    }                        

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

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"

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();    }

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();}

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"

A storyScenario: An interested user becomes an active user    Given an interested user with email "walter.dalmut@gmail.com"    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

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;        }    ...

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)    );}

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));}

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();}

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());}

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

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

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)

Thanks for listening

Any question?

top related