$30 off During Our Annual Pro Sale. View Details »

Flow, BDD and Behat

hlubek
March 29, 2014

Flow, BDD and Behat

A practial guide to BDD with Behat and TYPO3 Flow at the Inspiring Conference 2014

hlubek

March 29, 2014
Tweet

More Decks by hlubek

Other Decks in Programming

Transcript

  1. Christopher Hlubek TYPO3 Neos & Flow team CEO of networkteam

    GmbH Lives in Kiel, Germany Enjoys being outdoor and at the beach
  2. How the analyst designed it How the customer explained it

    How the project leader understood it projectcartoon.com
  3. How the programmer wrote it How the customer explained it

    How the project leader understood it How the analyst designed it projectcartoon.com
  4. Developer output <?php namespace TYPO3\Neos\Tests\Unit\Domain\Service; /* * * This script

    belongs to the TYPO3 Flow package "TYPO3.Neos". * * * * It is free software; you can redistribute it and/or modify it under * * the terms of the GNU General Public License, either version 3 of the * * License, or (at your option) any later version. * * * * The TYPO3 project - inspiring people to share! * * */ /** * Testcase for the Content Service * */ class DomainMatchingStrategyTest extends \TYPO3\Flow\Tests\UnitTestCase { /** * @test */ public function getSortedMatchesReturnsOneGivenDomainIfItMatchesExactly() { $mockDomains = array($this->getMock('TYPO3\Neos\Domain\Model\Domain', array(), array(), '', FALSE)); $mockDomains[0]->expects($this->any())->method('getHostPattern')->will($this->returnValue('www.typo3.org')) $expectedDomains = array($mockDomains[0]); $strategy = new \TYPO3\Neos\Domain\Service\DomainMatchingStrategy(); $actualDomains = $strategy->getSortedMatches('www.typo3.org', $mockDomains); $this->assertSame($expectedDomains, $actualDomains); } /** * @test */ public function getSortedMatchesFiltersTheGivenDomainsByTheSpecifiedHostAndReturnsThemSortedWithBestMatchesFirst( $mockDomains = array( $this->getMock('TYPO3\Neos\Domain\Model\Domain', array('dummy'), array(), '', FALSE), $this->getMock('TYPO3\Neos\Domain\Model\Domain', array('dummy'), array(), '', FALSE), $this->getMock('TYPO3\Neos\Domain\Model\Domain', array('dummy'), array(), '', FALSE), $this->getMock('TYPO3\Neos\Domain\Model\Domain', array('dummy'), array(), '', FALSE), ); $mockDomains[0]->setHostPattern('*.typo3.org'); $mockDomains[1]->setHostPattern('flow.typo3.org'); $mockDomains[2]->setHostPattern('*'); $mockDomains[3]->setHostPattern('yacumboolu.typo3.org'); $expectedDomains = array( $mockDomains[1], $mockDomains[0], $mockDomains[2] ); $strategy = new \TYPO3\Neos\Domain\Service\DomainMatchingStrategy(); $actualDomains = $strategy->getSortedMatches('flow.typo3.org', $mockDomains); $this->assertSame($expectedDomains, $actualDomains); } /** * @test */ public function getSortedMatchesReturnsNoMatchIfDomainIsLongerThanHostname() { $mockDomains = array( $this->getMock('TYPO3\Neos\Domain\Model\Domain', array('dummy'), array(), '', FALSE), ); $mockDomains[0]->setHostPattern('flow.typo3.org'); $expectedDomains = array(); $strategy = new \TYPO3\Neos\Domain\Service\DomainMatchingStrategy(); $actualDomains = $strategy->getSortedMatches('typo3.org', $mockDomains); $this->assertSame($expectedDomains, $actualDomains); } }
  5. Cool, we have a ( issue | pdf | excel

    | word | wiki | post-it | napkin )
  6. BDD is a second-generation, outside-in, pull- based, multiple-stakeholder, multiple-scale, high-automation,

    agile methodology. It describes a cycle of interactions with well- defined outputs, resulting in the delivery of working, tested software that matters. wikipedia.org
  7. features/learn-bdd.feature Feature: Learn about BDD In order to make better

    software As a listener of this talk I want to learn about BDD Scenario: Developer with TDD knowledge Given I already heard or used TDD And I am a developer When I watch the next slides And I listen carefully Then I should know what BDD is about And I should see how it can help me in my projects And I should be excited to see how I can apply it
  8. features/learn-bdd.feature Feature: Learn about BDD In order to make better

    software As a listener of this talk I want to learn about BDD Scenario: Developer with TDD knowledge Given I already heard or used TDD And I am a developer When I watch the next slides And I listen carefully Then I should know what BDD is about And I should see how it can help me in my projects And I should be excited to see how I can apply it Clear, explicit title Motivation and context
  9. features/learn-bdd.feature Feature: Learn about BDD In order to make better

    software As a listener of this talk I want to learn about BDD Scenario: Developer with TDD knowledge Given I already heard or used TDD And I am a developer When I watch the next slides And I listen carefully Then I should know what BDD is about And I should see how it can help me in my projects And I should be excited to see how I can apply it Steps Scenario title
  10. features/learn-bdd.feature Feature: Learn about BDD In order to make better

    software As a listener of this talk I want to learn about BDD Scenario: Developer with TDD knowledge Given I already heard or used TDD And I am a developer When I watch the next slides And I listen carefully Then I should know what BDD is about And I should see how it can help me in my projects And I should be excited to see how I can apply it Precondition Trigger Expected outcome
  11. A simple text document Following a defined format Using a

    domain specific language Readable by humans Executable by a program
  12. A story Is targeted for a role Shows business value

    and effect Communicates the cases
  13. Adding Behat to Flow $ composer require flowpack/behat $ ./flow

    behat:setup $ ./flow behat:kickstart My.Package http://project.url/
  14. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Basket.feature Feature: Basket details In order to get an overview

    of items in my basket As a buyer in the roebooks store I need a detailed view of the basket Scenario: Empty basket Given I have an empty shopping basket When I go to the Basket page Then I should see "Your shopping basket is empty."
  15. $ bin/behat -c Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/behat.yml Feature: Basket details In order to

    get an overview of items in my basket As a buyer in the roebooks store I need a detailed view of the basket Scenario: Empty basket # Features/Basket.feature:7 Given I have an empty shopping basket When I go to the Basket page Then I should see "Your shopping basket is empty." # FeatureContext::assertPageContainsText() 1 scenario (1 undefined) 3 steps (1 skipped, 2 undefined) 0m0.031s You can implement step definitions for undefined steps with these snippets: /** * @Given /^I have an empty shopping basket$/ */ public function iHaveAnEmptyShoppingBasket() { throw new PendingException(); } /** * @When /^I go to the Basket page$/ */ public function iGoToTheBasketPage() { throw new PendingException(); } Run it!
  16. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php <?php use Behat\Behat\Exception\PendingException; use Behat\MinkExtension\Context\MinkContext; require_once(__DIR__ . '/../../../../../Flowpack.Behat/Tests/Behat/FlowContext.php'); class

    FeatureContext extends MinkContext { ! public function __construct(array $parameters) { ! ! $this->useContext('flow', new \Flowpack\Behat\Tests\Behat\FlowContext($parameters)); ! } ! /** ! * @Given /^I have an empty shopping basket$/ ! */ ! public function iHaveAnEmptyShoppingBasket() { ! ! throw new PendingException(); ! } ! /** ! * @When /^I go to the Basket page$/ ! */ ! public function iGoToTheBasketPage() { ! ! throw new PendingException(); ! } }
  17. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php <?php use Behat\Behat\Exception\PendingException; use Behat\MinkExtension\Context\MinkContext; require_once(__DIR__ . '/../../../../../Flowpack.Behat/Tests/Behat/FlowContext.php'); class

    FeatureContext extends MinkContext { ! public function __construct(array $parameters) { ! ! $this->useContext('flow', new \Flowpack\Behat\Tests\Behat\FlowContext($parameters)); ! } ! /** ! * @Given /^I have an empty shopping basket$/ ! */ ! public function iHaveAnEmptyShoppingBasket() { ! ! // Don't do anything, the session will be clean ! } ! /** ! * @When /^I go to the Basket page$/ ! */ ! public function iGoToTheBasketPage() { ! ! $this->visit('/robertlemke.example.bookshop/basket/index'); ! } } This is a Mink function
  18. $ bin/behat -c Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/behat.yml Feature: Basket details In order to

    get an overview of items in my basket As a buyer in the roebooks store I need a detailed view of the basket Scenario: Empty basket # Features/Basket.feature:7 Given I have an empty shopping basket # FeatureContext::iHaveAn[...]ppingBasket() When I go to the Basket page # FeatureContext::iGoToTheBasketPage() Then I should see "Your shopping basket is empty." # FeatureContext::assertPageContainsText() 1 scenario (1 passed) 3 steps (3 passed) 0m3.834s Run again
  19. Web acceptance framework M%&' PHP API for browser control Different

    browsers through drivers Standalone or integrated in Behat
  20. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Basket.feature Feature: Basket details [...] Scenario: One book in basket

    Given I have a book in my basket When I go to the Basket page Then I should see "These books are in my basket:" And I should see a book Not very specific Which book?
  21. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Basket.feature Feature: Basket details [...] Scenario: A book in the

    basket Given the following books exist: | Title | | TYPO3 Neos | And I have the book "TYPO3 Neos" in my basket When I go to the Basket page Then I should see "These books are in my basket:" And I should see the book "TYPO3 Neos" This is a table
  22. $ bin/behat -c Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/behat.yml [...] You can implement step definitions

    for undefined steps with these snippets: /** * @Given /^the following books exist:$/ */ public function theFollowingBooksExist(TableNode $table) { throw new PendingException(); } /** * @Given /^I have the book "([^"]*)" in my basket$/ */ public function iHaveTheBookInMyBasket($arg1) { throw new PendingException(); } /** * @Given /^I should see the book "([^"]*)"$/ */ public function iShouldSeeTheBook($arg1) { throw new PendingException(); } Run it!
  23. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php <?php class FeatureContext extends MinkContext { ! public function

    __construct(array $parameters) { ! ! $this->useContext('flow', new \Flowpack\Behat\Tests\Behat\FlowContext($parameters)); ! } ! /** ! * @Given /^the following books exist:$/ ! */ ! public function theFollowingBooksExist(TableNode $table) { ! ! $objectManager = $this->getSubcontext('flow')->getObjectManager(); ! ! $bookRepository = $objectManager->get('RobertLemke\Example\Bookshop\Domain\Repository\BookRepository'); ! ! $rows = $table->getHash(); ! ! foreach ($rows as $row) { ! ! ! $book = new \RobertLemke\Example\Bookshop\Domain\Model\Book(); ! ! ! $book->setTitle($row['Title']); ! ! ! $book->setPrice(16); ! ! ! $book->setDescription(''); ! ! ! $book->setIsbn(''); ! ! ! $bookRepository->add($book); ! ! } ! ! $this->getSubcontext('flow')->persistAll(); ! } ! ... }
  24. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php <?php class FeatureContext extends MinkContext { ! ... !

    /** ! * @Then /^I should see the book "([^"]*)"$/ ! */ ! public function iShouldSeeTheBook($bookTitle) { ! ! return $this->assertSession()->elementExists( ! ! ! 'xpath', ! ! ! sprintf('//*[contains(text(), "%s")]/ancestor::*[contains(@class, "caption")]', $bookTitle) ! ! ); ! } ! /** ! * @Given /^I have the book "([^"]*)" in my basket$/ ! */ ! public function iHaveTheBookInMyBasket($bookTitle) { ! ! $this->visit('/books'); ! ! $bookContainer = $this->iShouldSeeTheBook($bookTitle); ! ! $bookContainer->findButton('Add to Basket')->press(); ! } }
  25. $ bin/behat -c Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/behat.yml Feature: Basket details In order to

    get an overview of items in my basket As a buyer in the roebooks store I need a detailed view of the basket Scenario: Empty basket # Features/Basket.feature:7 Given I have an empty shopping basket # FeatureContext::iHaveAn[...]Basket() When I go to the Basket page # FeatureContext::iGoToTheBasketPage() Then I should see "Your shopping basket is empty." # FeatureContext::assertPageContainsText() Scenario: A book in the basket # Features/Basket[...] Given the following books exist: # FeatureContext::[...] | Title | | TYPO3 Neos | And I have the book "TYPO3 Neos" in my basket # FeatureContext::[...] When I go to the Basket page # FeatureContext::[...] Then I should see "These books are currently in your shopping basket:" # FeatureContext::[...] And I should see the book "TYPO3 Neos" # FeatureContext::[...] 2 scenarios (2 passed) 8 steps (8 passed) 0m9.222s Run again
  26. Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Basket.feature Feature: Basket details [...] @javascript Scenario: A book in

    the basket Given the following books exist: | Title | | TYPO3 Neos | And I have the book "TYPO3 Neos" in my basket When I go to the Basket page Then I should see "These books are [...] basket:" And I should see the book "TYPO3 Neos" That will use Selenium2
  27. Behat @tags Mark Scenarios or Features for filtering Configure the

    Mink driver per Scenario Apply hooks depending on tags
  28. Event dispatcher BeforeSuite BeforeFeature BeforeScenario BeforeStep AfterStep AfterScenario AfterFeature AfterSuite

    Listener Listener Listener <?php class FeatureContext extends MinkContext { ! ... ! /** ! * @BeforeScenario @fixtures ! */ ! public function resetDatabase($event) { ! ! // Reset the database ! } }
  29. Private/PasswordReset.feature Feature: Password reset In order to allow access in

    case of forgotten password As a user of the system I need a way to reset my password safely with email confirmation @fixtures @email Scenario: Perform password reset from confirmation email Given an administrator "admin" with email "[email protected]" And I am not authenticated And I am on the Homepage page And I follow "Passwort vergessen?" And I fill in "E-Mail" with "[email protected]" And I press "Link zusenden" When I follow the reset password link in the email to "[email protected]" Then I should see a form to reset the password When I fill in "Neues Passwort" with "test1234" And I fill in "Passwort (Bestätigung)" with "test1234" And I press "Passwort zurücksetzen" Then I should be able to log in with "admin" and "test1234"
  30. Packages/Application/TYPO3.Neos/Tests/Behavior/Features/Content/InlineEditing.feature Feature: Content module / Inline editing In order to

    edit content easily As an editor I need a way to edit content inline @fixtures @javascript Scenario: Edit text of a content element with automatic save Given I imported the site "TYPO3.NeosDemoTypo3Org" And the following users exist: | username | password | firstname | lastname | roles | | jdoe | password | John | Doe | Editor | And I am authenticated with "jdoe" and "password" for the backend Then I should be in the "Content" module When I select the first headline content element And I set the content to "NewContent" And I wait for the changes to be saved And I reload the page Then I should see "NewContent"
  31. Packages/Application/TYPO3.TYPO3CR/Tests/Behavior/Basic/Content/RemoveNode.feature Feature: Remove node In order to remove nodes As

    an API user of the content repository I need support to remove nodes and child nodes Background: Given I have the following nodes: | Identifier | Path | Node Type | Properties | | ecf40ad1-... | /sites | unstructured | | | fd5ba6e1-... | /sites/neosdemotypo3 | TYPO3.Neos.NodeTypes:Page | {"title": "Home"} | | 68ca0dcd-... | /sites/neosdemotypo3/company | TYPO3.Neos.NodeTypes:Page | {"title": "Company"} | | 52540602-... | /sites/neosdemotypo3/company/about | TYPO3.Neos.NodeTypes:Page | {"title": "About"} | @fixtures Scenario: Remove a node in user workspace and publish removes the node itself When I get a node by path "/sites/neosdemotypo3/company" with the following context: | Workspace | | user-admin | And I remove the node And I publish the workspace "user-admin" And I get a node by path "/sites/neosdemotypo3/company" with the following context: | Workspace | | live | Then I should have 0 nodes
  32. Writing good specifications Always write them for humans first Respect

    the role of a specification Create reusable domain specific steps