Advertisement
Advertisement

More Related Content

Advertisement
Advertisement

I put on my mink and wizard behat (talk)

  1. I put on my mink and wizard behat Questing in the world of front end testing
  2. Booking.com @thomas_shone W E AR E H IR IN G
  3. Hoare Logic {P} C {Q}
  4. Hodor Logic {P} C {Q}
  5. Why? What's the benefit?
  6. Meet The Party Don’t feed the druid after midnight
  7. Task Each test has a different approach
  8. Barbarian Quality Assurance
  9. Ranger Unit Test
  10. Cleric Continuous Integration
  11. Wizard Front End Test
  12. Dreaded Bugbear
  13. Teamwork Wizards are squishy
  14. The glue Behat (cucumber syntax) Mink (browser emulation) Goutte (web driver) Selenium (web driver) Zombie (web driver) Guzzle (curl) Selenium RC (java) Zombie.js (node.js)
  15. Feature: Party harmony As a leader, I want to ensure harmony and mutual trust, so that we work as a team Scenario: Teach members to respect others’ property Given that the Wizard has 10 cookies And the Bard eats 1 cookie Then the Bard is mysteriously on fire Cucumber Syntax Readable testing language
  16. class FeatureContext … { /** * @Given that the wizard has :num cookies */ public function wizardHasCookies($num) { // $this->wizard is a pre-existing condition... like syphilis $this->wizard->setNumberOfCookies($num); } } and converts it into FeatureContext.php
  17. Feature: Party harmony As a leader, I want to ensure harmony and mutual trust, so that we work as a team Scenario: Teach members to respect others’ property Given that the Wizard has 10 cookies And the Bard eats 1 cookie Then the Bard is mysteriously on fire Cucumber Syntax What’s missing?
  18. Scenario: Given that the wizard has 10 cookies And the Bard eats 1 cookie # The triggered fire spell fizzled due to OutOfManaException Then the Bard is mysteriously on fire 1 scenario (1 failed) 3 steps (2 passed, 1 failed) 0m0.03s (14.19Mb) Remember {P} C {Q} Set your starting states
  19. Feature: Party harmony As a leader, I want to ensure harmony and mutual trust, so that we work as a team Background: The Wizard’s fire spell is fully charged And the Bard is currently not on fire Scenario: Teach members to respect others’ property Given that the Wizard has 10 cookies And the Bard eats 1 cookie Then the Bard is mysteriously on fire Remember {P} C {Q} Set your starting states
  20. ??? As a leader, I want to ensure harmony and mutual trust, so that we work as a team
  21. User stories As a <role>, I want to <desire> so that <benefit>
  22. Features are your contract with the stakeholders User stories Features Backgrounds are your restrictions or global constraints Background Scenarios are the use cases that outline the user story Scenarios
  23. Front end testing is “code coverage” for your user stories
  24. Legend has it... … that someone once convinced their PO to write all their front end tests.
  25. class MinkContext … { /** * Clicks link with specified id|title|alt|text. * * @When /^(?:|I )follow "(?P<link>(?:[^"]|")*)"$/ */ public function clickLink($link) { $link = $this->fixStepArgument($link); $this->getSession()->getPage()->clickLink($link); } } Mink provides... MinkContext.php
  26. OK... Dropping the party
  27. https://github.com/opencfp/opencfp
  28. $ composer require behat/behat="~3.0,>=3.0.5" Getting started $ composer require behat/mink-extension="~2.0" Behat (cucumber syntax) Mink (browser emulator) Web drivers $ composer require behat/mink-goutte-driver="~1.0" $ composer require behat/mink-selenium2-driver="~1.2"
  29. $ ./vendor/bin/behat --init +d features - place your *.feature files here +d features/bootstrap - place your context classes here +f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here Initialize Create a new test suite
  30. use BehatMinkExtensionContextMinkContext; class FeatureContext extends MinkContext … { … } Context FeatureContext.php
  31. $ ./vendor/bin/behat -dl Given /^(?:|I )am on "(?P<page>[^"]+)"$/ When /^(?:|I )reload the page$/ When /^(?:|I )move backward one page$/ When /^(?:|I )move forward one page$/ When /^(?:|I )press "(?P<button>(?:[^"]|")*)"$/ When /^(?:|I )follow "(?P<link>(?:[^"]|")*)"$/ When /^(?:|I )fill in "(?P<field>(?:[^"]|")*)" with "(?P<value>(?: [^"]|")*)"$/ Context What does Mink bring to the table?
  32. default: suites: default: paths: [ %paths.base%/features/ ] contexts: [ FeatureContext ] extensions: BehatMinkExtension: base_url: "[your website]" sessions: default: goutte: ~ Configuration behat.yml
  33. Feature: Authentication and authorisation As a security conscious developer I wish to ensure that only valid users can access our website. Scenario: Attempt to login with invalid details Given I am on "/login" When I fill in "email" with "some@guy.com" And I fill in "password" with "invalid" And I press "Login" Then I should see "Invalid Email or Password" Our first feature auth.feature
  34. $ ./vendor/bin/behat --config behat.yml features/auth.feature Scenario: Attempt to login with an invalid account Given I am on "/login" When I fill in "email" with "some@guy.com" And I fill in "password" with "invalid" And I press "Login" Then I should see "Invalid Email or Password" 1 scenarios (1 passed) 5 steps (5 passed) Victory output
  35. Feature: Authentication and authorisation As a security conscious developer I wish to ensure that only valid users can access our website. Scenario: Attempt to login with invalid details Given I login as "some@guy.com" with password "invalid" Then I should see "Invalid Email or Password" Simplify auth.feature
  36. Scenario: Attempt to login with an invalid account Given I login as "bob@smith.com" with password "invalid" Then I should see "Invalid Email or Password" 1 scenario (1 undefined) /** * @Given I login as :arg1 with password :arg2 */ public function iLoginAsWithPassword($arg1, $arg2) { throw new PendingException(); } Simplify output
  37. class FeatureContext … { /** * @Given I login as :username with password :password */ public function iLoginAsWithPassword($username, $password) { $this->visit("/login"); $this->fillField("email", $username); $this->fillField("password", $password); $this->pressButton("Login"); } } Simplify FeatureContext.php
  38. Scenario: Attempt to login with an invalid account Given I login as "bob@smith.com" with password "invalid" Then I should see "Invalid Email or Password" 1 scenarios (1 passed) 2 steps (2 passed) Simplify output
  39. Feature: Authentication and authorisation As a security conscious developer I wish to ensure that only valid users can access our website. Scenario: Attempt to register a new user Given I am on "/signup" When I fill in "email" with "some@guy.com" And I fill in "password" with "valid" And I fill in "password2" with "valid" And I fill in "first_name" with "some" And I fill in "last_name" with "guy" And I press "Create my speaker profile" Then I should see "You’ve successfully created your account" Our first hurdle This ones easy, you do…. oh….
  40. Migration and seeding Doctrine, Propel, Laravel, Phinx
  41. Phinx to the rescue SID E N O TE
  42. $ composer require robmorgan/phinx="~0.4" Phinx to the rescue Install $ php vendor/bin/phinx init Phinx by Rob Morgan - https://phinx.org. version 0.4.3 Created ./phinx.xml Configuration $ php vendor/bin/phinx create InitialMigration Creating SID E N O TE
  43. #!/usr/bin/env bash DATABASE="opencfp" mysql -e "DROP DATABASE IF EXISTS $DATABASE" -uroot -p123 mysql -e "CREATE DATABASE $DATABASE" -uroot -p123 vendor/bin/phinx migrate vendor/bin/behat A bit extreme? run-behat-tests.sh
  44. SAVEPOINT identifier; # Run tests ROLLBACK TO SAVEPOINT identifier; RELEASE SAVEPOINT identifier; Transaction/Rollback Roll your own solution
  45. Activation emails? smtp-sink, FakeSMTP, etc
  46. # Stop the currently running service sudo service postfix stop # Dumps outgoing emails to file as "day.hour.minute.second" smtp-sink -d "%d.%H.%M.%S" localhost:2500 1000 & vendor/bin/behat smtp-sink run-behat-test.sh
  47. Or…. actually send the email and read it via SMTP
  48. Or…. you could just read the activation code from the database directly
  49. class DatabaseContext { public function __construct($dsn, $user, $pass) { $this->dbh = new PDO($dsn, $user, $pass); } /** * @When /^there is no user called :user$/ */ public function removeUser($user) { $this->dbh->prepare("DELETE FROM `users` WHERE username=?") ->query([$user]); } } A new context DatabaseContext.php SID E N O TE
  50. default: suites: default: paths: [ %paths.base%/features/ ] contexts: - FeatureContext - DatabaseContext: - mysql:host=localhost;dbname=opencfp - root - 123 Configuration behat.yml SID E N O TE
  51. How far is too far? What are your priorities?
  52. Taking it too far This one is actually a true story
  53. // Make sure your server and your behat client have the same time set // Share the secret key between the two. The code should be valid for // 30 second periods $code = sha1($secret_key . floor(time() / 30)); if ($request->get("code") === $code) { // Bypass captcha } Easier way Simple but safe bypass
  54. Our first talk Set the stage Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions Background: There is a speaker registered as "some@guy.com" with a password "secrets" I login as "some@guy.com" with password "secrets" Scenario: Add a new talk to our submissions ...
  55. Our first talk Talk submission in 3, 2, 1... Scenario: Add a new talk to our submissions Given I am on "talk/create" And I fill in the following: | title | Behat Talk | | description | Awesome | | type | regular | | category | testing | | level | mid | And I check "desired" And I press "Submit my talk!" Then I should see "Success: Successfully added talk."
  56. Tyranny of JavaScript Deleting a talk
  57. Well that won’t work talks.feature Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions Scenario: Delete a talk Given create a talk called "Behat Talk" And I am on "/dashboard" When I follow "Delete" And I should not see "Behat Talk Changed" The text "Behat Talk Changed" appears in the text of this page, but it should not. (BehatMinkExceptionResponseTextException)
  58. // Guzzle using web scraper behat/mink-goutte-driver // Java-based distributed browser workers (support JavaScript) behat/mink-selenium2-driver behat/mink-sahi-driver // node.js headless browser proxy (support JavaScript) behat/mink-zombie-driver Drivers Some take the scenic route
  59. default: # … extensions: BehatMinkExtension: base_url: "[your website]" sessions: default: goutte: ~ javascript: selenium2: ~ Configuration Setting up for Selenium
  60. $ java -jar selenium-server-standalone-2.*.jar Selenium @javascript Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions Start Selenium Server Specify javascript requirement
  61. $ ./vendor/bin/behat --tags speaker,talk Tags Run specific tags @speaker Feature: Manage paper submissions In order to ensure that speakers can submit their papers As an speaker I need to be able to manage my own submissions @talk Scenario: Create a new talk Given I am logged in as a speaker ... SID E N O TE
  62. Feature: Submitting and managing talks As a speaker I wish be able to submit talks so I can get a chance to talk at a conference. @javascript Scenario: Delete a talk Given create a talk called "Behat Talk" And I am on "/dashboard" When I fill "Delete" And I accept alerts And I should not see "Behat Talk" Enable JavaScript talks.feature
  63. Run as JavaScript talks.feature Feature: Submitting and managing talks As a speaker I wish be able to submit talks so I can get a chance to talk at a conference. Scenario: Delete a talk Given create a talk called "Behat Talk" And I am on "/dashboard" When I follow "Delete" And I accept alerts And I should not see "Behat Talk"
  64. Advanced Usage with extra bells and whistles
  65. class MemoryContext { /** * @Transform /^memory:(.*)$/ */ public function fromMemory($key) { if (!isset($this->memory[$key])) { throw new LogicException("Entry $key does not exist"); } return $this->memory[$key]; } } Transformations MemoryContext.php
  66. class FeatureContext … { public function takeAScreenshotCalled($filename) { $driver = get_class($this->getSession()->getDriver()); if ($driver == 'BehatMinkDriverSelenium2Driver') { $ss = $this->getSession() ->getDriver() ->getScreenshot(); file_put_contents($filename, $ss); } } } Screenshot FeatureContext.php
  67. use BehatBehatHookScopeAfterFeatureScope; // @AfterFeature AfterScenarioScope; // @AfterScenario AfterStepScope; // @AfterStep BeforeFeatureScope; // @BeforeFeature BeforeScenarioScope; // @BeforeScenario BeforeStepScope; // @BeforeStep FeatureScope; // @Feature ScenarioScope; // @Scenario StepScope; // @Step Hooks Listen in close
  68. class FeatureContext … { /** * @AfterScenarioScope */ public function afterScenario(AfterScenarioScope $scope) { $scenario = $scope->getScenario()->getTitle(); $filename = make_safe_filename($scenario); // Take a screenshot and put it on a dashboard // where people can see it } } Hooks FeatureContext.php
  69. class FeatureContext … { /** * @AfterStep */ public function afterStep(AfterStepScope $scope) { $code = $event->getTestResult()->getResultCode(); if ($code == TestResult::FAILED) { // Take a screenshot } } } Hooks FeatureContext.php
  70. Some days everything is made of glass Common Gotchas
  71. Expect breakages And that’s a good thing
  72. Speed vs Coverage Find the right balance
  73. Keep Selenium updated Browsers change faster than fashion trends
  74. Behat documents http://docs.behat.org points to v2.5 docs but doesn’t tell you. Use http://docs.behat.org/en/v3.0/
  75. Questions? or ask me later via @thomas_shone
  76. Thank you Photo from Flickr by John Morey, TrojanRat, Gerry Machen, USFS Region 5, Peregrina Tyss and Thomas Hawk
  77. Permutations The reason why testing is painful
  78. default: suites: web: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, WebContext ] api: paths: [ %paths.base%/features/api ] contexts: [ BaseContext, ApiContext ] Configuration Multiple contexts
  79. default: suites: admin: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, AdminContext ] filters: role: admin speaker: paths: [ %paths.base%/features/web ] contexts: [ BaseContext, SpeakerContext ] filters: tags: @speaker Configuration Grouping and filtering
  80. $ ./vendor/bin/behat --suite admin Suites Run a specific suite Feature: Managing the CFP In order to ensure that speakers can submit their papers As an admin I need to be able to open the call for papers
  81. $ ./vendor/bin/behat --suite speaker Suites Run a specific suite @speaker Feature: Submitting to the CFP In order to ensure that the conference has papers As an speaker I need to be able to submit papers
  82. $ java -jar selenium-server-standalone-2.*.jar -role hub Selenium Grid $ java -jar selenium-server-standalone-2.*.jar -role node -hub http:// [gridserver]:4444/grid/register Start the grid Add a node
  83. default: extensions: BehatMinkExtension: sessions: javascript: selenium2: wd_host: "http://127.0.0.1:4444/wb/hub" capabilities: version: "" Configuration Because magic...
Advertisement