Slide 1

Slide 1 text

BDD, Behat and Flow A practical guide

Slide 2

Slide 2 text

Christopher Hlubek TYPO3 Neos & Flow team CEO of networkteam GmbH Lives in Kiel, Germany Enjoys being outdoor and at the beach

Slide 3

Slide 3 text

But first, let‘s talk about...

Slide 4

Slide 4 text

Communication

Slide 5

Slide 5 text

And how software projects work

Slide 6

Slide 6 text

How the customer explained it projectcartoon.com

Slide 7

Slide 7 text

How the project leader understood it How the customer explained it projectcartoon.com

Slide 8

Slide 8 text

How the analyst designed it How the customer explained it How the project leader understood it projectcartoon.com

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

What the customer really needed How the programmer wrote it ≠

Slide 11

Slide 11 text

What the customer really needed How the customer explained it ≠

Slide 12

Slide 12 text

What‘s going wrong?

Slide 13

Slide 13 text

Can we fix it?

Slide 14

Slide 14 text

Project input

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Where is the spec?

Slide 17

Slide 17 text

Cool, we have a ( issue | pdf | excel | word | wiki | post-it | napkin )

Slide 18

Slide 18 text

Is it in sync? Who verifies it? And when? For every little change?

Slide 19

Slide 19 text

BDD?

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Great, now you know BDD. Do you?

Slide 22

Slide 22 text

Let‘s specify what we want to do.

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

We just wrote a BDD story!

Slide 25

Slide 25 text

Behavior-driven development BDD

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

A simple text document Following a defined format Using a domain specific language Readable by humans Executable by a program

Slide 30

Slide 30 text

Stories are code!

Slide 31

Slide 31 text

Remember Stories are for humans.

Slide 32

Slide 32 text

A story Is targeted for a role Shows business value and effect Communicates the cases

Slide 33

Slide 33 text

A possible BDD cycle Specify BDD Unit / Functional tests Develop Verify Refactor Red Green

Slide 34

Slide 34 text

Tools?

Slide 35

Slide 35 text

B!"#$

Slide 36

Slide 36 text

A BDD story runner for PHP B!"#$

Slide 37

Slide 37 text

Adding Behat to Flow $ composer require flowpack/behat $ ./flow behat:setup $ ./flow behat:kickstart My.Package http://project.url/

Slide 38

Slide 38 text

Bookshop Example

Slide 39

Slide 39 text

Note: we don't follow the cycle here.

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

$ 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!

Slide 42

Slide 42 text

The FeatureContext A class that contains step definitions Holds the context for each scenario

Slide 43

Slide 43 text

Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php useContext('flow', new \Flowpack\Behat\Tests\Behat\FlowContext($parameters)); ! } }

Slide 44

Slide 44 text

Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php 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(); ! } }

Slide 45

Slide 45 text

Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php 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

Slide 46

Slide 46 text

$ 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

Slide 47

Slide 47 text

M%&'

Slide 48

Slide 48 text

Web acceptance framework M%&' PHP API for browser control Different browsers through drivers Standalone or integrated in Behat

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

Now add some books.

Slide 51

Slide 51 text

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?

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

$ 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!

Slide 54

Slide 54 text

Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php 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(); ! } ! ... }

Slide 55

Slide 55 text

Packages/Application/RobertLemke.Example.Bookshop/Tests/Behavior/Features/Bootstrap/FeatureContext.php 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(); ! } }

Slide 56

Slide 56 text

$ 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

Slide 57

Slide 57 text

JavaScript, AJAX?

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Behat @tags Mark Scenarios or Features for filtering Configure the Mink driver per Scenario Apply hooks depending on tags

Slide 60

Slide 60 text

Event dispatcher BeforeSuite BeforeFeature BeforeScenario BeforeStep AfterStep AfterScenario AfterFeature AfterSuite Listener Listener Listener

Slide 61

Slide 61 text

Examples

Slide 62

Slide 62 text

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"

Slide 63

Slide 63 text

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"

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Experience

Slide 66

Slide 66 text

Writing good specifications Always write them for humans first Respect the role of a specification Create reusable domain specific steps

Slide 67

Slide 67 text

Where can Behat be applied? Web applications PHP code Anything that can be verified by PHP

Slide 68

Slide 68 text

Writing robust specifications Handle asynchronous behaviour All scenarios have to be isolated Always have a consistent setup

Slide 69

Slide 69 text

Does it fix the issue?

Slide 70

Slide 70 text

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