Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flow, BDD and Behat

2a244c5ed94d92d288444604360a919a?s=47 hlubek
March 29, 2014

Flow, BDD and Behat

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

2a244c5ed94d92d288444604360a919a?s=128

hlubek

March 29, 2014
Tweet

Transcript

  1. BDD, Behat and Flow A practical guide

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

    GmbH Lives in Kiel, Germany Enjoys being outdoor and at the beach
  3. But first, let‘s talk about...

  4. Communication

  5. And how software projects work

  6. How the customer explained it projectcartoon.com

  7. How the project leader understood it How the customer explained

    it projectcartoon.com
  8. How the analyst designed it How the customer explained it

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

    How the project leader understood it How the analyst designed it projectcartoon.com
  10. What the customer really needed How the programmer wrote it

  11. What the customer really needed How the customer explained it

  12. What‘s going wrong?

  13. Can we fix it?

  14. Project input

  15. 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); } }
  16. Where is the spec?

  17. Cool, we have a ( issue | pdf | excel

    | word | wiki | post-it | napkin )
  18. Is it in sync? Who verifies it? And when? For

    every little change?
  19. BDD?

  20. 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
  21. Great, now you know BDD. Do you?

  22. Let‘s specify what we want to do.

  23. 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
  24. We just wrote a BDD story!

  25. Behavior-driven development BDD

  26. 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
  27. 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
  28. 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
  29. A simple text document Following a defined format Using a

    domain specific language Readable by humans Executable by a program
  30. Stories are code!

  31. Remember Stories are for humans.

  32. A story Is targeted for a role Shows business value

    and effect Communicates the cases
  33. A possible BDD cycle Specify BDD Unit / Functional tests

    Develop Verify Refactor Red Green
  34. Tools?

  35. B!"#$

  36. A BDD story runner for PHP B!"#$

  37. Adding Behat to Flow $ composer require flowpack/behat $ ./flow

    behat:setup $ ./flow behat:kickstart My.Package http://project.url/
  38. Bookshop Example

  39. Note: we don't follow the cycle here.

  40. 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."
  41. $ 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!
  42. The FeatureContext A class that contains step definitions Holds the

    context for each scenario
  43. 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)); ! } }
  44. 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(); ! } }
  45. 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
  46. $ 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
  47. M%&'

  48. Web acceptance framework M%&' PHP API for browser control Different

    browsers through drivers Standalone or integrated in Behat
  49. None
  50. Now add some books.

  51. 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?
  52. 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
  53. $ 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!
  54. 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(); ! } ! ... }
  55. 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(); ! } }
  56. $ 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
  57. JavaScript, AJAX?

  58. 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
  59. Behat @tags Mark Scenarios or Features for filtering Configure the

    Mink driver per Scenario Apply hooks depending on tags
  60. 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 ! } }
  61. Examples

  62. 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 "admin@example.com" And I am not authenticated And I am on the Homepage page And I follow "Passwort vergessen?" And I fill in "E-Mail" with "admin@example.com" And I press "Link zusenden" When I follow the reset password link in the email to "admin@example.com" 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"
  63. 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"
  64. 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
  65. Experience

  66. Writing good specifications Always write them for humans first Respect

    the role of a specification Create reusable domain specific steps
  67. Where can Behat be applied? Web applications PHP code Anything

    that can be verified by PHP
  68. Writing robust specifications Handle asynchronous behaviour All scenarios have to

    be isolated Always have a consistent setup
  69. Does it fix the issue?

  70. Q(!)$%*&)? Twitter @hlubek Google+ plus.google.com/ +ChristopherHlubek Web networkteam.com/ christopher.hlubek