SlideShare uma empresa Scribd logo
1 de 87
Baixar para ler offline
Behat: Beyond the Basics
@jessicamauerhan
10-13-15 Dallas PHP User Group
http://joind.in/event/view/4808
My Introduction to Behat
Our Admin Panel
Moving Forward
Topics
● Writing Better .feature Files
● Drivers
● Hooks
● Step Definitions
● Page Objects & Elements
● Unusual Interactions
Code Examples
● Mostly Behat 2.5
● Some Behat 3.0
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
“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
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
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!
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
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
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
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
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
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>';
}
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
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>';
}
}
Gerkhin
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();
}
Gerkhin
Given: Set up
When: Action
Then: Outcome
But/And: More of the same...
IDEs & Plugins
Writing Better .Features
● Don’t Make Assumptions
● Scenarios should run independently
● Follow the Flow: Given, When, Then
Drivers
Driver Capabilities
http://mink.behat.org/en/latest
- Drivers
- Driver Feature Support
Hooks
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);
}
}
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);
}
}
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);
}
}
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
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();
}
}
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());
}
Steps
Multiple Regular Expressions
/**
* @Given /^I view the catalog$/
* @Given /^I am viewing the catalog$/
*/
public function iViewTheCatalog(){
$this->getPage('Catalog')->open();
}
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();
}
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();
}
Unquoted Variables
Then I should see an "error" message
/**
* @Given /^I should see an "([^"])" message$/
*/
public function iShouldSeeAnMessage($arg1){
}
Unquoted Variables
Then I should see an error message
/**
* @Given /^I should see an (.*) message$/
*/
public function iShouldSeeAnMessage($arg1){
}
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');
}
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);
}
}
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);
}
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);
}
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);
}
Contexts
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());
}
}
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());
}
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__);
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());
}
}
<?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
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;
}
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);
}
}
CoreContext with Step Annotation causes Error
[BehatBehatExceptionRedundantException]
Step "/^I should be redirected to "([^"]*)"$/" is already defined in
AcmeAppBundleContextFeatureContext::iShouldBeRedirectedTo()
AcmeAppBundleContextFeatureContext::iShouldBeRedirectedTo()
AcmeAppBundleContextMessageContext::iShouldBeRedirectedTo()
Reusing Multiple Steps
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
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')
];
}
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);
}
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);
}
}
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);
}
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);
}
Suite Contexts (Behat 3.x)
default:
suites:
default:
paths: [ %paths.base%/features/core ]
contexts: [FeatureContext, MessageContext]
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
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);
}
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
Page Objects
Page Objects Extension
php composer require "sensiolabs/behat-page-object-extension"
default:
extensions:
SensioLabsBehatPageObjectExtensionExtension: ~
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
Seminar Page Object
<?php
namespace AcmeAppBundlePageObjects;
use SensioLabsBehatPageObjectExtensionPageObjectPage;
class Seminar extends Page
{
protected $path = '/seminar/{id}';
}
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"],
];
}
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');
}
Interactions
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
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);
}
}
}
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 |
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");
}
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);
}
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
Testing a Command Line Process
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
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);
}
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 ;
}
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);
}
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 &");
}
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;
}
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}");
}
}
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);
}
Thank You!
@jessicamauerhan
10-13-15 Dallas PHP User Group
http://joind.in/event/view/4808
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

Mais conteúdo relacionado

Mais procurados

Outside-in Development with Cucumber and Rspec
Outside-in Development with Cucumber and RspecOutside-in Development with Cucumber and Rspec
Outside-in Development with Cucumber and RspecJoseph Wilk
 
Cucumber: How I Slice It
Cucumber: How I Slice ItCucumber: How I Slice It
Cucumber: How I Slice Itlinoj
 
Behavior Driven Development - How To Start with Behat
Behavior Driven Development - How To Start with BehatBehavior Driven Development - How To Start with Behat
Behavior Driven Development - How To Start with Behatimoneytech
 
CUCUMBER - Making BDD Fun
CUCUMBER - Making BDD FunCUCUMBER - Making BDD Fun
CUCUMBER - Making BDD FunSQABD
 
5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniternicdev
 
Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01Jason Noble
 
A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...
A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...
A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...Ho Chi Minh City Software Testing Club
 

Mais procurados (9)

Outside-in Development with Cucumber and Rspec
Outside-in Development with Cucumber and RspecOutside-in Development with Cucumber and Rspec
Outside-in Development with Cucumber and Rspec
 
Vb.Net Web Forms
Vb.Net  Web FormsVb.Net  Web Forms
Vb.Net Web Forms
 
BDD with cucumber
BDD with cucumberBDD with cucumber
BDD with cucumber
 
Cucumber: How I Slice It
Cucumber: How I Slice ItCucumber: How I Slice It
Cucumber: How I Slice It
 
Behavior Driven Development - How To Start with Behat
Behavior Driven Development - How To Start with BehatBehavior Driven Development - How To Start with Behat
Behavior Driven Development - How To Start with Behat
 
CUCUMBER - Making BDD Fun
CUCUMBER - Making BDD FunCUCUMBER - Making BDD Fun
CUCUMBER - Making BDD Fun
 
5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter5 Reasons To Love CodeIgniter
5 Reasons To Love CodeIgniter
 
Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01Cart creation-101217222728-phpapp01
Cart creation-101217222728-phpapp01
 
A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...
A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...
A Universal Automation Framework based on BDD Cucumber and Ruby on Rails - Ph...
 

Semelhante a Behat: Beyond the Basics

Maintaining sanity in a large redux app
Maintaining sanity in a large redux appMaintaining sanity in a large redux app
Maintaining sanity in a large redux appNitish Kumar
 
Writing Software not Code with Cucumber
Writing Software not Code with CucumberWriting Software not Code with Cucumber
Writing Software not Code with CucumberBen Mabey
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patternsSamuel ROZE
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium AppsNate Abele
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overviewYehuda Katz
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumberBachue Zhou
 
How to perform debounce in react
How to perform debounce in reactHow to perform debounce in react
How to perform debounce in reactBOSC Tech Labs
 
Using and reusing CakePHP plugins
Using and reusing CakePHP pluginsUsing and reusing CakePHP plugins
Using and reusing CakePHP pluginsPierre MARTIN
 
Working with Javascript in Rails
Working with Javascript in RailsWorking with Javascript in Rails
Working with Javascript in RailsSeungkyun Nam
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the FinishYehuda Katz
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgetsvelveeta_512
 
Working With Sharepoint 2013 Apps Development
Working With Sharepoint 2013 Apps DevelopmentWorking With Sharepoint 2013 Apps Development
Working With Sharepoint 2013 Apps DevelopmentPankaj Srivastava
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
UPenn on Rails pt 2
UPenn on Rails pt 2UPenn on Rails pt 2
UPenn on Rails pt 2Mat Schaffer
 

Semelhante a Behat: Beyond the Basics (20)

Maintaining sanity in a large redux app
Maintaining sanity in a large redux appMaintaining sanity in a large redux app
Maintaining sanity in a large redux app
 
Event Sourcing with php
Event Sourcing with phpEvent Sourcing with php
Event Sourcing with php
 
Catalyst MVC
Catalyst MVCCatalyst MVC
Catalyst MVC
 
Writing Software not Code with Cucumber
Writing Software not Code with CucumberWriting Software not Code with Cucumber
Writing Software not Code with Cucumber
 
How I started to love design patterns
How I started to love design patternsHow I started to love design patterns
How I started to love design patterns
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
 
Rails 3 overview
Rails 3 overviewRails 3 overview
Rails 3 overview
 
Introduce cucumber
Introduce cucumberIntroduce cucumber
Introduce cucumber
 
How to perform debounce in react
How to perform debounce in reactHow to perform debounce in react
How to perform debounce in react
 
Using and reusing CakePHP plugins
Using and reusing CakePHP pluginsUsing and reusing CakePHP plugins
Using and reusing CakePHP plugins
 
Working with Javascript in Rails
Working with Javascript in RailsWorking with Javascript in Rails
Working with Javascript in Rails
 
Rails 3: Dashing to the Finish
Rails 3: Dashing to the FinishRails 3: Dashing to the Finish
Rails 3: Dashing to the Finish
 
Intro To Sammy
Intro To SammyIntro To Sammy
Intro To Sammy
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 
Getting the Most Out of jQuery Widgets
Getting the Most Out of jQuery WidgetsGetting the Most Out of jQuery Widgets
Getting the Most Out of jQuery Widgets
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Working With Sharepoint 2013 Apps Development
Working With Sharepoint 2013 Apps DevelopmentWorking With Sharepoint 2013 Apps Development
Working With Sharepoint 2013 Apps Development
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
UPenn on Rails pt 2
UPenn on Rails pt 2UPenn on Rails pt 2
UPenn on Rails pt 2
 

Último

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessPixlogix Infotech
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?Antenna Manufacturer Coco
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 

Último (20)

EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 

Behat: Beyond the Basics

  • 1. Behat: Beyond the Basics @jessicamauerhan 10-13-15 Dallas PHP User Group http://joind.in/event/view/4808
  • 5. Topics ● Writing Better .feature Files ● Drivers ● Hooks ● Step Definitions ● Page Objects & Elements ● Unusual Interactions
  • 6. Code Examples ● Mostly Behat 2.5 ● Some Behat 3.0
  • 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. “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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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>'; } }
  • 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. Gerkhin Given: Set up When: Action Then: Outcome But/And: More of the same...
  • 23. Writing Better .Features ● Don’t Make Assumptions ● Scenarios should run independently ● Follow the Flow: Given, When, Then
  • 26. Hooks
  • 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. 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. 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. 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. 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. 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. Steps
  • 34. Multiple Regular Expressions /** * @Given /^I view the catalog$/ * @Given /^I am viewing the catalog$/ */ public function iViewTheCatalog(){ $this->getPage('Catalog')->open(); }
  • 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. 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. Unquoted Variables Then I should see an "error" message /** * @Given /^I should see an "([^"])" message$/ */ public function iShouldSeeAnMessage($arg1){ }
  • 38. Unquoted Variables Then I should see an error message /** * @Given /^I should see an (.*) message$/ */ public function iShouldSeeAnMessage($arg1){ }
  • 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. 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. 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. 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. 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); }
  • 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. 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. 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. 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. <?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. 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. 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. CoreContext with Step Annotation causes Error [BehatBehatExceptionRedundantException] Step "/^I should be redirected to "([^"]*)"$/" is already defined in AcmeAppBundleContextFeatureContext::iShouldBeRedirectedTo() AcmeAppBundleContextFeatureContext::iShouldBeRedirectedTo() AcmeAppBundleContextMessageContext::iShouldBeRedirectedTo()
  • 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. 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. 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. 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. 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. 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. Suite Contexts (Behat 3.x) default: suites: default: paths: [ %paths.base%/features/core ] contexts: [FeatureContext, MessageContext]
  • 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. 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. 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
  • 65. Page Objects Extension php composer require "sensiolabs/behat-page-object-extension" default: extensions: SensioLabsBehatPageObjectExtensionExtension: ~
  • 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. Seminar Page Object <?php namespace AcmeAppBundlePageObjects; use SensioLabsBehatPageObjectExtensionPageObjectPage; class Seminar extends Page { protected $path = '/seminar/{id}'; }
  • 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. 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'); }
  • 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. 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. 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. 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. 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. 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. Testing a Command Line Process
  • 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. 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. 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. 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. 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. 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. 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. 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. Thank You! @jessicamauerhan 10-13-15 Dallas PHP User Group http://joind.in/event/view/4808
  • 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