Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Behat: Beyond the Basics

1,891 views

Published on

An in-depth look at intermediate to advanced level Behat topics. We'll cover writing better features, regular expressions in steps, Context files, changes from Behat 2 to 3, and more,

Published in: Technology
  • Login to see the comments

Behat: Beyond the Basics

  1. 1. Behat: Beyond the Basics @jessicamauerhan 10-13-15 Dallas PHP User Group http://joind.in/event/view/4808
  2. 2. My Introduction to Behat
  3. 3. Our Admin Panel
  4. 4. Moving Forward
  5. 5. Topics ● Writing Better .feature Files ● Drivers ● Hooks ● Step Definitions ● Page Objects & Elements ● Unusual Interactions
  6. 6. Code Examples ● Mostly Behat 2.5 ● Some Behat 3.0
  7. 7. Writing Better .Feature Files Scenario: Visit Seminar Page before Broadcast Time Given I want to watch the video called "Future Seminar" When I visit that seminar's page Then I should see "Future Seminar" on the page And I should see "Future Seminar Author" on the page And I should see "This seminar begins at 6:00 pm EST" on the page And I should see a countdown timer
  8. 8. “What is behavior-driven development, you ask? It’s the idea that you start by writing human-readable sentences that describe a feature of your application and how it should work, and only then implement this behavior in software.” - Behat Documentation
  9. 9. Writing Better .Feature Files Scenario: Visit Seminar Page before Broadcast Time Given there is a seminar scheduled for the future When I visit that seminar's page Then I should see the seminar's name And I should see the seminar's author’s name And I should see "This seminar begins at" And I should see the seminar’s start time in EST And I should see a countdown timer
  10. 10. Why? ● Easier for Programmers to Understand ● Helps Prevent Regression ● Easier for Business Users to Understand ● Easier for Business Users to Write ● Can Identify Bad Code!
  11. 11. How Better .Feature Files Can Identify Bad Code Original: Scenario: Display Local Services in Product Catalog Given I view the catalog When I select "Texas" from the states list Then I should see the list of services offered Rewritten: Scenario: Display Local Services in Product Catalog Given I view the catalog When I select a state from the states list Then I should see the list of services offered
  12. 12. How Better .Feature Files Can Identify Bad Code Original: Scenario: Display Local Services in Product Catalog Given I view the catalog When I select "Texas" from the states list Then I should see the list of services offered Rewritten: Scenario: Display Local Services in Product Catalog Given we have a regional office offering local services in a state When I view the catalog And I select that state from the states list Then I should see the list of services offered for that state
  13. 13. Feature Description - As A, In Order To, I Need... Feature: Display Texas-Specific Local Services As a company, we offer specific services only in Texas In order to sell these services to the right people We need to display the services when users are browsing our catalog for Texas Scenario: Display Local Services When Texas is Selected Given I view the catalog When I select Texas from the states list Then I should see the list of services offered
  14. 14. When I select Texas from the states list /** * @When /^I select Texas from the states list$/ */ public function iSelectTexasFromTheStatesList(){} When I select "Texas" from the states list /** * @When /^I select "([^"]*)" from the states list$/ */ public function iSelectFromTheStatesList($arg1){} A Clear Behavior
  15. 15. Negative Cases Scenario: Display Product Catalog Given I view the catalog When I select a state from the list Then I should see the list of products for sale in that state Scenario: Display Local Services Given we have a regional office that offers local services in a state When I view the catalog And I select that state from the states list Then I should see the list of services offered for that state
  16. 16. Negative Cases echo '<h1>Products</h1>'; foreach ($products AS $product) { echo '<p>' . $product->getName() . '</p>'; } echo '<h1>Services</h1>'; foreach ($services AS $service) { echo '<p>' . $service->getName() . '</p>'; }
  17. 17. Negative Cases Scenario: Display Product Catalog Given I view the catalog When I select a state from the list Then I should see the list of products for sale in that state Scenario: Display Local Services Given we have a regional office that offers local services in a state When I view the catalog And I select that state from the states list Then I should see the list of services offered for that state Scenario: Don’t Display Local Services When No Regional Office Given a state has no regional office offering local services When I view the catalog And I select that state from the states list Then I should not see a list of services
  18. 18. Negative Cases catalogecho '<h1>Products</h1>'; foreach ($products AS $product) { echo '<p>' . $product->getName() . '</p>'; } if(count($services) > 0) { echo '<h1>Services</h1>'; foreach ($services AS $service) { echo '<p>' . $service->getName() . '</p>'; } }
  19. 19. Gerkhin
  20. 20. Auto-Generated Steps You can implement step definitions for undefined steps with these snippets: /** * @When /^I select Texas from the states list$/ */ public function iSelectTexasFromTheStatesList() { throw new PendingException(); } /** * @Then /^I should see the list of services offered$/ */ public function iShouldSeeTheListOfServicesOffered() { throw new PendingException(); }
  21. 21. Gerkhin Given: Set up When: Action Then: Outcome But/And: More of the same...
  22. 22. IDEs & Plugins
  23. 23. Writing Better .Features ● Don’t Make Assumptions ● Scenarios should run independently ● Follow the Flow: Given, When, Then
  24. 24. Drivers
  25. 25. Driver Capabilities http://mink.behat.org/en/latest - Drivers - Driver Feature Support
  26. 26. Hooks
  27. 27. Capturing Screenshot on Error /** @AfterScenario */ public function afterScenario($event) { if ($event->getResult() == EventStepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents($imagePath, $imageData); } }
  28. 28. Hooks & Tags /** @AfterScenario @javascript */ public function afterScenario($event) { if ($event->getResult() == EventStepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents($imagePath, $imageData); } }
  29. 29. Hooks & Tags (Multiple Tags) <?php /** @AfterScenario @javascript,@screenshot */ public function afterScenario($event) { if ($event->getResult() == EventStepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir () . time() . '.png'; file_put_contents ($imagePath, $imageData); } } /** @AfterScenario @javascript,@screenshot*/ public function afterScenario($event) { if ($event->getResult() == EventStepEvent::FAILED) { $imageData = $this->getSession()->getDriver()->getScreenshot(); $imagePath = $this->getArtifactsDir() . time() . '.png'; file_put_contents($imagePath, $imageData); } }
  30. 30. Dealing with AJAX (jQuery & Angular) /** @BeforeStep @javascript */ public function beforeStep($event) { $waitTime = 5000; $jqDefined = "return (typeof jQuery != 'undefined')" ; $active = '(0 === jQuery.active && 0 === jQuery( ':animated'). length)'; if ($this->getSession()->evaluateScript ($jqDefined)) { $this->getSession()->wait($waitTime, $active); } } //Angular: https://gist.github.com/jmauerhan/f839926ea527ff5e74e7
  31. 31. Fixture Data namespace AcmeAppBundleDataFixtures; use DoctrineCommonPersistenceObjectManager; use DoctrineCommonDataFixturesFixtureInterface; class UserFixtureLoader implements FixtureInterface { public function load(ObjectManager $manager) { $user = new User(); $user->setUsername('admin'); $user->setPassword('password'); $manager->persist($user); $manager->flush(); } }
  32. 32. Load Fixture Data Hook /** @BeforeFeature */ public function beforeFeatureReloadDatabase($event) { $loader = new Loader(); $directory = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'DataFixtures'; $loader->loadFromDirectory($directory); $entityManager = $this->getEntityManager(); $purger = new ORMPurger(); $executor = new ORMExecutor($entityManager, $purger); $executor->execute($loader->getFixtures()); }
  33. 33. Steps
  34. 34. Multiple Regular Expressions /** * @Given /^I view the catalog$/ * @Given /^I am viewing the catalog$/ */ public function iViewTheCatalog(){ $this->getPage('Catalog')->open(); }
  35. 35. Case Insensitive - Flag When I view the catalog When I view the Catalog /** * @Given /^I view the catalog$/i */ public function iViewTheCatalog(){ $this->getPage('Catalog')->open(); }
  36. 36. Case Insensitive - Inline When I view the catalog When I view the Catalog /** * @Given /^I view the (?i)catalog$/ */ public function iViewTheCatalog(){ $this->getPage('Catalog')->open(); }
  37. 37. Unquoted Variables Then I should see an "error" message /** * @Given /^I should see an "([^"])" message$/ */ public function iShouldSeeAnMessage($arg1){ }
  38. 38. Unquoted Variables Then I should see an error message /** * @Given /^I should see an (.*) message$/ */ public function iShouldSeeAnMessage($arg1){ }
  39. 39. Unquoted Variables with List of Options Then I should see an error message Then I should see a success message Then I should see a warning message /** * @Given /^I should see an? (error|success|warning) message$/ */ public function iShouldSeeAnMessage($messageType){ $class = '.alert-'.$messageType; $this->assertElementExists($class, 'css'); }
  40. 40. Optional Variables Then I should see an error message Then I should see an error message that says " Stop!" /** * @Given /^I should see an? (error|success|warning) message$/ * @Given /^I should see an? (error|success|warning) message that says "([^"])"$/ */ public function iShouldSeeAnMessageThatSays ($messageType, $message = null) { $class = '.alert -' . $messageType; $this->assertElementExists ($class, 'css'); if ($message !== null) { $this->assertElementContainsText ($class, 'css', $message); } }
  41. 41. Non-Capturing Groups Then I view the catalog for "Texas" Then I am viewing the catalog for "Texas" /** * @Given /^I view the catalog for "([^"]*)"$/ * @Given /^I am viewing the catalog "([^"]*)"$/ */ public function iViewTheCatalogForState($stateName) { $args = ['stateName' => $stateName]; $this->getPage('Catalog')->open($args); }
  42. 42. Non-Capturing Groups Given I am viewing the catalog for “Texas” Given I viewing the catalog for “Texas” <?php /** * @Given /^I (?:am viewing|view) the catalog for "([^"]*)"$/ */ public function iViewTheCatalogForState($stateName){ $this->getPage(‘Catalog’)->open([‘stateName’=>$stateName); } Then I view the catalog for "Texas" Then I am viewing the catalog for "Texas" /** * @Given /^I (?:am viewing|view) the catalog for "([^"]*)"$/ */ public function iViewTheCatalogForState($stateName) { $args = ['stateName' => $stateName]; $this->getPage('Catalog')->open($args); }
  43. 43. Step Definition Changes in Behat 3.x Given I am viewing the catalog for “Texas” Given I viewing the catalog for “Texas” <?php /** * @Given /^I (?:am viewing|view) the catalog for "([^"]*)"$/ */ public function iViewTheCatalogForState($stateName){ $this->getPage(‘Catalog’)->open([‘stateName’=>$stateName); } Then I view the catalog for "Texas" Then I view the catalog for Texas /** * @Given I view the catalog for :stateName */ public function iViewTheCatalogForState($stateName) { $args = ['stateName' => $stateName]; $this->getPage('Catalog')->open($args); }
  44. 44. Contexts
  45. 45. SubContext (Behat 2.x) <?php namespace AcmeAppBundleContext; use BehatMinkExtensionContextMinkContext; class FeatureContext extends MinkContext { public function __construct(array $parameters) { $this->parameters = $parameters; $this->useContext('MessageContext', new MessageContext()); } }
  46. 46. Several SubContexts (Behat 2.x) [...] public function __construct(array $parameters) { $this->parameters = $parameters; $this->useContext('AdminContext', new AdminContext()); $this->useContext('FormContext', new FormContext()); $this->useContext('EditUserContext', new EditUserContext()); $this->useContext('ApiContext', new ApiContext()); }
  47. 47. Alias All SubContexts Automatically (Behat 2.x) private function loadSubContexts () { $finder = new Finder(); $finder->name('*Context.php' ) ->notName('FeatureContext.php' ) ->notName('CoreContext.php' ); $finder->files()->in(__DIR__);
  48. 48. Alias All SubContexts Automatically (Behat 2.x) private function loadSubContexts () { $finder = new Finder(); $finder->name('*Context.php' ) ->notName('FeatureContext.php' ) ->notName('CoreContext.php' ); $finder->files()->in(__DIR__); foreach ($finder as $file) { $className = $file->getBaseName('.php'); $namespace = __NAMESPACE__ . '' . $file->getRelativePath (); if (substr($namespace, -1) !== '') { $namespace .= ''; } $reflectionClass = new ReflectionClass ($namespace . $className); $this->useContext($className, $reflectionClass ->newInstance()); } }
  49. 49. <?php namespace AcmeAppBundleContext; class FeatureContext extends CoreContext { /** @Given /^I should see an? (error|success|warning) message that says "([^"])"$/ */ public function iShouldSeeAnMessageThatSays ($messageType, $message = null) { $class = '.alert -' . $messageType; $element = $this->getPage()->find('css', $class); $actualMessage = $element->getText(); $this->assertEqual($actualMessage , $message); } } Message Context
  50. 50. Find Required Element Shortcut public function findRequiredElement ($locator, $selector = 'xpath', $parent = null) { if (null === $parent) { $parent = $this->getPage(); } $element = $parent->find($selector, $locator); if (null === $element) { throw new ElementNotFoundException ($this->getSession(), null, $selector, $locator); } return $element; }
  51. 51. Message Context <?php namespace AcmeAppBundleContext; class FeatureContext extends CoreContext { /** @Given /^I should see an? (error|success|warning) message that says "([^"])"$/ */ public function iShouldSeeAnMessageThatSays ($messageType, $message = null) { $class = '.alert -' . $messageType; $element = $this->findRequiredElement ($class, 'css'); $actualMessage = $element->getText(); $this->assertEqual($actualMessage , $message); } }
  52. 52. CoreContext with Step Annotation causes Error [BehatBehatExceptionRedundantException] Step "/^I should be redirected to "([^"]*)"$/" is already defined in AcmeAppBundleContextFeatureContext::iShouldBeRedirectedTo() AcmeAppBundleContextFeatureContext::iShouldBeRedirectedTo() AcmeAppBundleContextMessageContext::iShouldBeRedirectedTo()
  53. 53. Reusing Multiple Steps
  54. 54. Reusing Multiple Steps Then I should see an error message Then I should see a success message Then I should see a warning message <?php /** * @Given /^I should see an? (error|success|warning) message$/ */ public function iShouldSeeAnMessage($messageType){ $class = ‘.alert-’.$messageType; $this->assertElementExists($class, ‘css’); } Scenario: Upload a csv file Given I am viewing the csv import form When I attach a csv to "Import File" And I submit the form Then I should see a success message And I should see the file review screen Scenario: Review and Confirm the csv file Given I have uploaded a csv And I am viewing the file review screen When I select a property for each column And I submit the form Then I should see a success message
  55. 55. Meta-Steps use BehatBehatContextStep; class FeatureContext { /** * @Given /^I have uploaded a csv$/ */ public function iHaveUploadedACsv() { return [ new StepGiven('I am viewing the csv import form'), new StepWhen('I attach a csv to "Import File"'), new StepWhen('I submit the form') ]; }
  56. 56. Meta-Steps With Multi-line Arguments use BehatBehatContextStep; use BehatGherkinNodePyStringNode; class FeatureContext { /** * @Given /^I should see the file review screen$/ */ public function iShouldSeeTheFileReviewScreen() { $content = 'Please review your file .' . PHP_EOL . 'Press Submit to continue'; $pyString = new PyStringNode($content); return new StepGiven('I should see', $pyString); }
  57. 57. Direct Method Call - Same Context /** * @Given /^I should see an error about the file type$/ */ public function iShouldSeeAnErrorAboutTheFileType () { $message = 'This file type is invalid' ; $this->iShouldSeeAnMessageThatSays ('error', $message); } /** * @Given /^I should see an? (.*) message that says "([^"])"$/ */ public function iShouldSeeAnMessageThatSays ($messageType, $message = null) { $class = '.alert -' . $messageType; $this->assertElementExists ($class, 'css'); if ($message !== null) { $this->assertElementContainsText ($class, 'css', $message); } }
  58. 58. Direct Method Call to Another Context (Behat 2.x) /** * @Given /^I should see an error about the file type$/ */ public function iShouldSeeAnErrorAboutTheFileType() { $message = "This file type is invalid"; $this->getMainContext() ->getSubContext('MessageContext') ->iShouldSeeAnMessageThatSays('error', $message); }
  59. 59. Direct Method Call to Another Context (Behat 2.x) /** * @return MessageContext */ public function getMessageContext () { return $this->getMainContext ()->getSubContext('messageContext' ); } /** * @Given /^I should see an error about the file type$/ */ public function iShouldSeeAnErrorAboutTheFileType () { $message = "This file type is invalid" ; $this->getMessageContext ()->iShouldSeeAnMessageThatSays ('error', $message); }
  60. 60. Suite Contexts (Behat 3.x) default: suites: default: paths: [ %paths.base%/features/core ] contexts: [FeatureContext, MessageContext]
  61. 61. Store Other Contexts (Behat 3.x) use BehatBehatContextContext; use BehatBehatHookScopeBeforeScenarioScope ; class FeatureContext implements Context { /** @var MessageContext */ private $messageContext ; /** @BeforeScenario */ public function gatherContexts (BeforeScenarioScope $scope) { $environment = $scope->getEnvironment (); $this->messageContext = $environment->getContext('MessageContext' ); } } // http://docs.behat.org/en/v3.0/cookbooks/context_communication.html
  62. 62. Direct Method Call to Another Context (Behat 3.x) use BehatBehatContextContext ; use BehatBehatHookScopeBeforeScenarioScope ; class FeatureContext implements Context { /** @var BehatMinkExtensionContextMinkContext */ private $minkContext; /** @BeforeScenario */ public function gatherContexts (BeforeScenarioScope $scope) { $environment = $scope->getEnvironment (); $this ->minkContext = $environment ->getContext('BehatMinkExtensionContextMinkContext' ); } } // http://docs.behat.org/en/v3.0/cookbooks/context_communication.html /** * @Given /^I should see an error about the file type$/ */ public function iShouldSeeAnErrorAboutTheFileType () { $message = "This file type is invalid" ; $this->messageContext ->iShouldSeeAnMessageThatSays ('error', $message); }
  63. 63. Reusing Steps Meta-Steps ● Return a Step or array of Steps ● Hooks will fire (could be slow) ● Moving Step definitions does not break ● Removed in 3.0 Calling Methods ● Like any other method call ● Hooks do not fire (typically faster) ● Moving Step definitions might require refactor
  64. 64. Page Objects
  65. 65. Page Objects Extension php composer require "sensiolabs/behat-page-object-extension" default: extensions: SensioLabsBehatPageObjectExtensionExtension: ~
  66. 66. Seminar Page Object Scenario: Visit Seminar Page before Broadcast Time Given there is a seminar scheduled for the future When I visit that seminar's page Then I should see the seminar's name And I should see the seminar's author’s name And I should see " This seminar begins at " And I should see the seminar’s start time in EST And I should see a countdown timer Scenario: Visit Seminar Page before Broadcast Time Given there is a seminar scheduled When I visit that seminar's page during the broadcast time Then I should see the seminar's name And I should see the seminar video And I should not see a countdown timer
  67. 67. Seminar Page Object <?php namespace AcmeAppBundlePageObjects; use SensioLabsBehatPageObjectExtensionPageObjectPage; class Seminar extends Page { protected $path = '/seminar/{id}'; }
  68. 68. Seminar Page Object <?php namespace AcmeAppBundlePageObjects; use SensioLabsBehatPageObjectExtension PageObjectPage; class Seminar extends Page { protected $path = '/seminar/{id}' ; protected $elements = [ 'Author Info' => ['css' => "#author"], 'Video' => ['xpath' => "//div[contains(@class, 'video')]" ], 'Countdown Timer' => ['css' => ".timer"], ]; }
  69. 69. Element <?php namespace AcmeAppBundlePageObjectsElements; use SensioLabsBehatPageObjectExtensionPageObjectElement; class AuthorInformation extends Element { protected $selector = ['css' => "#author"]; public function getAuthorName() { return $this->find('css', '.name'); } public function getAuthorPhoto() { return $this->find('xpath', '//img'); }
  70. 70. Interactions
  71. 71. CSV Report @javascript Scenario: View Summary Report Given a user in a group has registered for a seminar with a company And I am logged in as an admin And I am viewing the reports area When I download the "Summary" report Then I should see the following columns: | column | | Group | | Company | | Total | And I should see that user in the report
  72. 72. File Download Test /** * @When /^I download the "([^"]*)" report$/ */ public function iDownloadTheReport ($reportName) { $xpath = "//a[normalize-space()=' {$reportName}']"; $link = $this->findRequiredElement ($xpath); $this->getSession()->visit('view-source:' . $link->getAttribute('href')); $content = $this->getSession()->getPage()->getContent(); $lines = explode(PHP_EOL, $content); $this->csvRows = []; foreach ($lines as $line) { if (strlen(trim($line))) { $this->csvRows[] = str_getcsv($line); } } }
  73. 73. Zip File Download @javascript Scenario: View Large Report Given I am viewing the reports area When I click "Export" for the "Extremely Large Report" report Then a zip file should be downloaded When I unzip the file and I open the extracted csv file Then I should see the following columns: | column | | Group | | Company | | Total |
  74. 74. File Download Test /** * @When /^I click "Export" for the "([^"]*)" report$/ */ public function iExportTheReport($reportName) { $this->file = $this->getArtifactsDir() . 'download-' . time() . '.zip'; file_put_contents($this->file, $this->getSession()->getDriver() > getContent()); } /** * @Then /^a zip file should be downloaded$/ */ public function aZipFileShouldBeDownloaded() { $header = $this->getSession()->getDriver()->getResponseHeaders(); $this->assertContains($header['Content-Type'][0], 'application/forced-download'); $this->assertContains($header['Content-Disposition'][0], "zip"); }
  75. 75. File Download Test /** * @When /^I unzip the file and I open the extracted csv file$/ */ public function iUnzipTheFileAndOpenCsvFile () { $zip = new ZipArchive; $unzipped = $zip->open($this->file); $csv = $zip->getNameIndex(1); $zip->extractTo($this->getArtifactsDir() ); $zip->close(); $fileRef = fopen($this->getArtifactsDir().$csv , 'r'); $this->csvContents = []; while (($data = fgetcsv($fileRef)) !== false) { $this->csvContents[] = $data; } fclose($fileRef); }
  76. 76. Confirm Text In PDF /** * @Given /^I should see a PDF with the order total$/ */ public function iShouldSeeAPdfWithTheOrderTotal() { $total = 'Order Total: ' . $this->orderTotal; $this->getMainContext()->assertPageContainsText($total); } @javascript Scenario: View PDF Receipt Given I am viewing my order history When I click "View Receipt" for an order Then I should see a PDF with the order total
  77. 77. Testing a Command Line Process
  78. 78. Testing a Command Line Process with Behat Scenario: Test User Import With a Large Data Set Given the system already has 100000 users And there is a company with 10000 of the users assigned to it And an admin has uploaded a spreadsheet for the company with 10000 rows When the system has begun to process the spreadsheet And I have waited 1 minute Then the batch process status should be set to "Running" or "Completed" And I should see at least 100 new users in the company
  79. 79. The System Already Has 100000 Users /** @Given /^the system already has (d+) users$/ */ public function theSystemAlreadyHasUsers ($numUsers) { $userSQL = "INSERT INTO `user`(firstname, lastname, username) VALUES" ; $userValues = []; $faker = $this->getFaker(); for ($i = 0; $i < $numUsers; $i++) { $firstname = addslashes($faker->firstName); $lastname = addslashes($faker->lastName); $username = $faker->username . $i; //unique $userValues[] = "('{$firstname}', '{$lastname}', '{$username}')"; } $userQuery = $userSQL . implode(', ', $userValues); $this->getEntityManager ()->getConnection()->exec($userQuery); }
  80. 80. There is a company with 10000 users assigned to it /** @Given /^there is a company with (d+) of the users assigned to it$/ */ public function thereIsACompanyWithOfTheUsersAssignedToIt ($num) { $company = $this->generateCompany (); $conn = $this->getEntityManager ()->getConnection(); $userCompanySQL = "INSERT INTO `user_company`(user_id, company_id) SELECT `user`.id, {$company->getId()} FROM `user` LIMIT {$num}"; $conn->exec($userCompanySQL ); $this->getEntityManager ()->refresh($company); $companyUsersCount = $company ->getUserCompanies ()->count(); $this->assertGreaterThanOrEqual ($num, $companyUsersCount ); $this->company = $company; $this->companyNumUsers = $companyUsersCount ; }
  81. 81. An Admin Has Uploaded a Spreadsheet /** @Given /^an admin has uploaded a spreadsheet for the company with (d*) rows$/ */ public function adminHasUploadedSpreadsheetForTheCompanyWithRows($numRows) { $faker = $this->getFaker(); $this->filePath = $this->getUploadsDirectory() . 'import -' . $numRows . '.csv'; $fh = fopen($this->filePath, "w"); $rows = 'firstname, lastname, username' . PHP_EOL; for ($i = 0; $i < $numRows; $i++) { $firstname = addslashes($faker->firstName); $lastname = addslashes($faker->lastName); $username = $faker->username . $i; //add $i to force unique $rows .= "{$firstname}, {$lastname}, {$username}" . PHP_EOL; } fwrite($fh, $rows); fclose($fh); $repository = $this->getRepository('BatchProcess'); $this->batchProcess = $repository->create()->setFilename($this->filePath); $repository->save($this->batchProcess); }
  82. 82. The System Has Begun To Process The Spreadsheet /** * @When /^the system has begun to process the spreadsheet$/i */ public function theSystemHasBegunToProcessTheSpreadsheet() { $command = 'php app' . DIRECTORY_SEPARATOR; $command .= 'console batch:process --batch_id='; $command .= $this->batchProcess->getId(); if (substr(php_uname(), 0, 7) == "Windows") { return pclose(popen("start /B " . $command, "r")); } return exec($command . " > /dev/null &"); }
  83. 83. I Have Waited 1 Minute /** * @When /^I have waited (d+) minutes?$/ */ public function iHaveWaitedSomeMinutes($num) { $seconds = 60; $outputEvery = 30; $cycles = ($num * $seconds) / $outputEvery; for ($i = 0; $i < $cycles; $i++) { sleep($outputEvery); echo '.'; } echo PHP_EOL; }
  84. 84. The Batch Process Status Should Be /** * @Given /^the batch process status should be set to "(.*)" or "(. *)"$/ */ public function theBatchProcessStatusShouldBeSetTo($statusA, $statusB) { $this->getEntityManager()->refresh($this->batchProcess); $statusName = $this->batchProcess->getStatus()->getName(); if ($statusName !== $statusA && $statusName !== $statusB) { throw new Exception("Status is currently: {$statusName}"); } }
  85. 85. I should see at least 100 new users /** * @Then /^I should see at least (d+) new users in the company$/ */ public function iShouldSeeAtLeastNewUsersInTheCompany($num) { $company = $this->company; $this->getEntityManager()->refresh($company); $companyNumUsersNow = $company->getUserCompanies()->count(); $originalNumUsers = $this->companyNumUsers; $difference = ($companyNumUsersNow - $originalNumUsers); $this->assertGreaterThanOrEqual($num, $difference); }
  86. 86. Thank You! @jessicamauerhan 10-13-15 Dallas PHP User Group http://joind.in/event/view/4808
  87. 87. Resources & Tools Drivers: http://mink.behat.org/en/latest/guides/drivers.html Angular AJAX check: https://gist.github.com/jmauerhan/f839926ea527ff5e74e7 Doctrine Data Fixtures: https://github.com/doctrine/data-fixtures Faker: https://github.com/fzaninotto/Faker Symfony Finder: http://symfony.com/doc/current/components/finder.html Page Object Extension: https://github.com/sensiolabs/BehatPageObjectExtension PHP Zip Archive: http://php.net/manual/en/class.ziparchive.php

×