@ataylorme • bit.ly/ataylorme-behat-testing-slides
Testing Business Critical Features With Behat
Hi, I'm Andrew
Developer Programs Engineer at Pantheon.
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Maintainer of Websites
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Changes Happen Frequently
Client Requests
Security Updates
Bug Fixes
New Features
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Who Does QA On A
Staging Environment?
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Typical Staging Environment Workflow
New Feature
Dependency Updates
Bug Fix
QA on Staging
Deploy To Production
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Acceptance Testing
Also called Integration Testing. Testing the entire application to ensure the defined business requirements are met in an acceptable way.
Acceptance Testing
Unit Testing
Integration Testing
System Testing
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Checking Everything
Is Time Consuming
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Doing It For Every�Change Is Boring
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Sometimes QA�Doesn't Go Well
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Find (And Fix)
The Issues
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Probably Out Of Time
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
There's Got To Be
A Better Way
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Can We Test More Often Without Doing More Work?
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Make The Robots �Do The Work
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Automated Testing
Feature: Visibility of the homepage
In order to have confidence that my site is accessible
As a site administrator
I want to verify I can visit the homepage�� Scenario: Verify the homepage� When I am on the homepage� Then the response status code should be 200�
1 scenario (1 passed)�2 steps (2 passed)�0m0.13s (4.16Mb)
So Many Testing�Tools, Why Behat?
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Battle Tested
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Established Community
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Robust Ecosystem
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Laravel
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Symfony
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Drupal
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
WordPress
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Behat API Extension
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Browserstack
Extension
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Human Readable Tests
@no_auth @privacy
Feature: Visibility of the privacy policy
In order to be in compliance with privacy laws
As a site administrator
I want to verify the privacy policy
Scenario: Verify the privacy policy is accessible from the homepage
When I am on the homepage
And I follow "Privacy Policy"
Then the response status code should be 200
And I should see "This website is the personal, non-commercial website of Andrew Taylor."
And I should see "Contact forms"
And I should see "Cookies"
And I should see "Comments"
And I should see "User accounts"
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Backed By PHP
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Run Tests Locally
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Integrate With Version Control
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Test On Pull Requests
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Always
Be
Testing
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
There Is A Lot To Learn
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Why Are Tests Written In Gherkin?
@javascript
Feature: Visibility of the latest posts
In order to have confidence that published articles show up
As a content author
I want to see the latest post on the home page
Background:
Given there are users:
| user_login | user_pass | user_email | role |
| test_author | test | test@example.com | author |
And there are posts:
| post_title | post_content | post_status | post_author |
| Author article | The content of my author article | publish | test_author |
And the Pantheon cache has been cleared
Scenario: Verify new post on the front-end and in the admin
Given I am an anonymous user
When I go to "/blog"
Then I should not be logged in
And I should see "Not an Author article"
The text "Author article" was not found anywhere in the text of the current page. (Behat\Mink\Exception\ResponseTextException)
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Most importantly, the stories are the result of conversations between the project stakeholders, business analysts, testers and developers. BDD is as much about the interactions between the various people in the project as it is about the outputs of the development process.
Behavior Driven Development (BDD)
Dan North
https://dannorth.net/whats-in-a-story/
BDD is all about communication
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
We need a
privacy policy
Feature: Visibility of the privacy policy
Scenario: Verify the privacy policy exists
When I am on "/privacy"
Then the response status code should be 200
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Why do we need a
privacy policy?
It is a legal requirement and will help avoid potential litigation
@no_auth @privacy
Feature: Visibility of the privacy policy
In order to be in compliance with privacy laws
As a site administrator
I want to verify the privacy policy
Scenario: Verify the privacy policy is accessible from the homepage
When I am on the homepage
And I follow "Privacy Policy"
Then the response status code should be 200
And I should see "This website is the personal, non-commercial website of Andrew Taylor."
And I should see "Contact forms"
And I should see "Cookies"
And I should see "Comments"
And I should see "User accounts"
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Ogres Have Layers �Behat Has Layers
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Behat is the foundation
Behat
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Example ls test
Feature: ls� In order to see the directory structure� As a UNIX user� I need to be able to list the current directory's contents�� Scenario: List 2 files in a directory� Given I am in a directory "test"� And I have a file named "foo"� And I have a file named "bar"� When I run "ls"� Then I should get:� """� bar� foo� """
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Backed by PHP
# features/bootstrap/FeatureContext.php
<?php
class FeatureContext extends BehatContext
{
/**
* @Given /^I am in a directory "([^"]*)"$/
*/
public function iAmInADirectory($dir)
{
if ( !file_exists($dir) ) {
mkdir($dir);
}
chdir($dir);
}
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Mink Focuses
On The Web
Behat
Mink
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
A browser is the window through which web users interact with web applications and other users.
So, in order to test that our web application behaves correctly, we need a way to simulate this interaction between the browser and the web application in our tests.
Mink
Mink
http://mink.behat.org/en/latest/
An open source browser controller written in PHP
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Selenium2 Driver
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Headless Chrome Driver
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Check a radio button
/**
* @When /^I check the "([^"]*)" radio button$/
*/
public function iCheckTheRadioButton($labelText)
{
$page = $this->getSession()->getPage();
$radioButton = $page->find('named', ['radio', $labelText]);
if ($radioButton) {
$select = $radioButton->getAttribute('name');
$option = $radioButton->getAttribute('value');
$page->selectFieldOption($select, $option);
return;
}
throw new \Exception(
"Radio button with label {$labelText} not found"
);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Framework extensions �add focus
Behat
Mink
Framework Extension
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Remember WordHat?
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
General Mink
When I to go "/login.php"��And I fill in
"user_login" with "admin"
And I fill in
"user_pass" with "admin"
And I press "submit"
WordHat
Given I am logged in as an administrator
VS
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Framework Focused
Feature: Managing themes
As an administrator I should be able to switch themes
Background:
Given I am logged in as an administrator
Scenario: Activate a theme
When I switch the theme to "twentynineteen"
And I am on the dashboard
Then I should see "Twenty Nineteen" in the "#wp-version a" element
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Let's Do This
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: goo.gl/V7QtNw
Download Dependencies
composer require --dev \
behat/behat \
behat/mink-extension \
behat/mink-goutte-driver \
behat/mink-selenium2-driver
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Initialize Behat
./vendor/bin/behat --init
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Create behat.yml
default:
suites:
default:
contexts:
- FeatureContext
- Behat\MinkExtension\Context\MinkContext
extensions:
Behat\MinkExtension:
base_url: https://ataylorme-wordpress.lndo.site
goutte: ~
selenium2: ~
sessions:
default:
goutte:
guzzle_parameters:
verify: false # Allow self-signed SSL certificates
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
List Available Steps
./vendor/bin/behat -dl
default | Given /^(?:|I )am on (?:|the )homepage$/
default | When /^(?:|I )go to (?:|the )homepage$/
default | Given /^(?:|I )am on "(?P<page>[^"]+)"$/
default | When /^(?:|I )go to "(?P<page>[^"]+)"$/
default | When /^(?:|I )reload the page$/
default | When /^(?:|I )move backward one page$/
default | When /^(?:|I )move forward one page$/
default | Then /^the response status code should be (?P<code>\d+)$/
...
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Write a feature file
./features/visit-homepage.feature
@no_auth
Feature: Visibility of the homepage
In order to have confidence that my site is accessible
As a site administrator
I want to verify I can visit the homepage
Scenario: Verify the homepage
When I am on the homepage
Then the response status code should be 200
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Run Behat
./vendor/bin/behat --strict --colors \
--format-settings='{"paths": false}'
Feature: Visibility of the homepage
In order to have confidence that my site is accessible
As a site administrator
I want to verify I can visit the homepage�� Scenario: Verify the homepage� When I am on the homepage� Then the response status code should be 200�
1 scenario (1 passed)�2 steps (2 passed)�0m0.08s (11.12Mb)
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Add WordHat
composer require paulgibbs/behat-wordpress-extension:^3
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Update behat.yml
default:
suites:
default:
contexts:
- PaulGibbs\WordpressBehatExtension\Context\WordpressContext
- FeatureContext
- Behat\MinkExtension\Context\MinkContext
- PaulGibbs\WordpressBehatExtension\Context\ContentContext
- PaulGibbs\WordpressBehatExtension\Context\DashboardContext
- PaulGibbs\WordpressBehatExtension\Context\SiteContext
- PaulGibbs\WordpressBehatExtension\Context\UserContext
- PaulGibbs\WordpressBehatExtension\Context\EditPostContext
- PaulGibbs\WordpressBehatExtension\Context\WidgetContext
- PaulGibbs\WordpressBehatExtension\Context\ToolbarContext
extensions:
Behat\MinkExtension:
base_url: https://ataylorme-wordpress.lndo.site
browser_name: chrome
default_session: default
javascript_session: selenium2
sessions:
default:
goutte:
guzzle_parameters:
verify: false # Allow self-signed SSL certificates
selenium2:
selenium2: ~
PaulGibbs\WordpressBehatExtension:
default_driver: wpcli
path: /Users/andrewtaylor/Development/ataylorme-wordpress/web/wp
site_url: https://ataylorme-wordpress.lndo.site/wp/
users:
-
roles:
- administrator
username: admin
password: admin
database:
restore_after_test: false
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
extensions:
Behat\MinkExtension:
base_url: https://ataylorme-wordpress.lndo.site
browser_name: chrome
default_session: default
javascript_session: selenium2
sessions:
default:
goutte:
guzzle_parameters:
verify: false # Allow self-signed SSL certificates
selenium2:
selenium2: ~
PaulGibbs\WordpressBehatExtension:
default_driver: wpcli
path: /Users/andrewtaylor/Development/ataylorme-wordpress/web/wp
site_url: https://ataylorme-wordpress.lndo.site/wp/
users:
-
roles:
- administrator
username: admin
password: admin
database:
restore_after_test: false
Site front-end URL
Path to WordPress and
Back-end URL
Admin username and password
List Available Steps
./vendor/bin/behat -dl
default | When I activate the :name plugin
default | When I deactivate the :name plugin
default | When I switch the theme to :name
default | Given /^(?:there are|there is a) users?:/
default | Given /^(?:I am|they are) an anonymous user/
default | Given /^(?:I am|they are) logged in as an? (.+)$/
default | Given /^I am on the edit screen for "(?P<title>[^"]*)"$/
default | When /^I change the title to "(?P<title>[^"]*)"$/
default | When /^I publish the (post|changes?)$/
...
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Write a WordPress enabled feature file
./features/blogname.feature
@auth
Feature: Change blogname
As a maintainer of the site
I want to be able to change basic settings
So that I have control over my site
Scenario: Saving blogname
Given I am logged in as an administrator
Given I am on the dashboard
When I go to the "Settings > General" menu
And I fill in "blogname" with "Testing with Behat Rocks!"
And I press "submit"
Then I should see "Settings saved."
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Run Behat
./vendor/bin/behat --strict --colors \
--format-settings='{"paths": false}'
@auth
Feature: Change blogname
As a maintainer of the site
I want to be able to change basic settings
So that I have control over my site
Scenario: Saving blogname
Given I am logged in as an administrator
Given I am on the dashboard
When I go to the "Settings > General" menu
And I fill in "blogname" with "Testing with Behat Rocks!"
And I press "submit"
Then I should see "Settings saved."
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Simple Behat Example Repository
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
What Do We Do With This?
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Avoid 3 AM Phone Calls
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Privacy Policy Example
As seen on my personal site https://ataylor.me/privacy-policy
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Write a Test With Built-In Steps
./features/privacy-policy.feature
@no_auth @privacy
Feature: Visibility of the privacy policy
In order to be in compliance with privacy laws
As a site administrator
I want to verify the privacy policy
Scenario: Verify the privacy policy is accessible from the homepage
When I am on the homepage
And I follow "Privacy Policy"
Then the response status code should be 200
And I should see "This website is the personal, non-commercial website of Andrew Taylor."
And I should see "Contact forms"
And I should see "Cookies"
And I should see "Comments"
And I should see "User accounts"
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Contact Form Example
As seen on my personal site https://ataylor.me/contact
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Contact Form QA Process
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Testing Manually Is Tedious
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Write a Test From The Manual QA Steps
Background:
Given I am on the contact page
Given I am an anonymous user
Scenario: Confirm contact form submissions
When I set the contact form first name to "Andrew"
And I set the contact form last name to "Taylor"
And I set the contact form email to "andrew@pantheon.io"
And I set the contact form message to "Testing the contact form with Behat!"
When I submit the contact form
Then I should see "Thanks for reaching out! I will be in touch with you shortly."
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Behat Isn't Magic
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
We Need To Do�Some Work
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Generic Mink
When I fill in
"wpforms-117-field_0" with "Andrew"
And I fill in
"wpforms-117-field_0-last" with "Taylor"
And I fill in "wpforms-117-field_1"
with "andrew@pantheon.io"
Custom Definitions
When I set the contact form first name to "Andrew"
And I set the contact form last name to "Taylor"
And I set the contact form email to "andrew@pantheon.io"
VS
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Contexts
Contexts are PHP Objects to encapsulate a set of functionality
<?php
declare(strict_types=1);
namespace ataylorme\WordHatHelpers\Contexts;
use ataylorme\WordHatHelpers\Contexts\RawWPContext;
use PaulGibbs\WordpressBehatExtension\Context\Traits\UserAwareContextTrait;
use PaulGibbs\WordpressBehatExtension\PageObject\LoginPage;
use Behat\Mink\Exception\ExpectationException;
use RuntimeException;
use FailAid\Context\FailureContext;
/**
* Define WordPress specific features from the specific context.
*/
class WordPressContext extends RawWPContext�{� use UserAwareContextTrait;
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Step Definitions
Step definitions are methods of the context classes
/**
* Sets the contact form first name field
* Example: When I set the contact form first name to "Andrew"
*
* @When I set the contact form first name to :first_name
*
* @param string $first_name
*/
public function fillContactFormFirstNameField(string $first_name)
{
$this->getSession()->getPage()->fillField('wpforms-117-field_0', $first_name);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
RawContexts
Contexts are meant to be extended. To avoid having steps defined multiple times keep helpers in their own Context and extend it to create other contexts
/**
* Log into WordPress as an admin
*
* @throws \RuntimeException
*
* @return void
*/
protected function loginAsWordPressAdmin()
{
// Get the admin user
$found_user = $this->getAdminUser();
// Stash the current URL to redirect to
$previous_url = $this->getSession()->getCurrentUrl();
$this->logIn(
$found_user['username'],
$found_user['password'],
$previous_url
);
FailureContext::addState('source_url', $previous_url);
FailureContext::addState('username', $found_user['username']);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Human
Readable�Gherkin
@javascript @contact @no_auth
Feature: Verify the contact form
As the site owner who likes to hear from users
I want the contact form to submit properly
Background:
Given I am on the contact page
Given I am an anonymous user
Scenario: Confirm contact form submissions
When I set the contact form first name to "Andrew"
And I set the contact form last name to "Taylor"
And I set the contact form email to "andrew@pantheon.io"
And I set the contact form message to "Testing the contact form with Behat!"
When I submit the contact form
Then I should see "Thanks for reaching out! I will be in touch with you shortly."
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Backed by PHP
/**
* Submit the contact form
* Example: When I submit the contact form
*
* @When I submit the contact form
*/
public function submitContactForm()
{
$session = $this->getSession();
$page = $session->getPage();
$page->pressButton('wpforms-submit-117');
// Looks for the '#wpforms-confirmation-117' element, giving up after 5 seconds.
$session->wait( 5000, "document.getElementById('wpforms-confirmation-117')" );
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Look Under The Hood
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Mink Source Code
/**
* Fills in form field with specified id|name|label|value
* Example: When I fill in "username" with: "bwayne"
* Example: And I fill in "bwayne" for "username"
* @When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
* @When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/
* @When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
*/
public function fillField($field, $value)
{
$field = $this->fixStepArgument($field);
$value = $this->fixStepArgument($value);
$this->getSession()->getPage()->fillField($field, $value);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Modify To Fit �Your Needs
/**
* Sets the contact form last name field
* Example: I set the contact form last name to "Taylor"
*
* @When I set the contact form last name to "Taylor"
*
* @param string $last_name
*/
public function fillGiveLastNameField(string $last_name)
{
$this->getSession()->getPage()->fillField(
'wpforms-117-field_0', $last_name
);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
WordHat �Source Code
public function getContentFromTitle(string $title, string $post_type = ''): array
{
$post = $this->getDriver()->content->get(
$title, ['by' => 'title', 'post_type' => $post_type]
);
return array(
'id' => (int) $post->ID,
'slug' => $post->post_name,
'url' => $post->url,
);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Modify To Fit �Your Needs
/**
* Go to a specific post by ID
* Example: Given I am viewing the post with an ID of 1234
* @Given I am viewing the post with an ID of :post_id
* @param string $post_id
* @throws \UnexpectedValueException
*/
public function iAmViewingPostID($post_id)
{
$post = $this->getDriver()->content->get(
$post_id, ['by' => 'ID']
);
$this->visitPath($post->url);
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Iterate Until Tests Are Complete
@javascript @contact @no_auth
Feature: Verify the contact form
As the site owner who likes to hear from users
I want the contact form to submit properly
Background:
Given I am on the contact page
Given I am an anonymous user
Scenario: Confirm contact form submissions
When I set the contact form first name to "Andrew"
And I set the contact form last name to "Taylor"
And I set the contact form email to "andrew@pantheon.io"
And I set the contact form message to "Testing the contact form with Behat!"
When I submit the contact form
Then I should see "Thanks for reaching out! I will be in touch with you shortly."
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Test Locally
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Test On Pull Requests
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Benefits of Adopting Automation
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Reduced Overhead
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: goo.gl/oXqt1a
Consistency
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: goo.gl/oXqt1a
Risk Mitigation
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: goo.gl/oXqt1a
Confidence
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: goo.gl/oXqt1a
Automation is �a State of Mind
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Resources
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
@ataylorme • Slides: goo.gl/oXqt1a
Simple Behat Example Repo
}
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
ataylor.me Repository
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Resource Links
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Framework Extensions
@ataylorme • Slides: bit.ly/ataylorme-behat-testing-slides
Questions?�Slides: bit.ly/ataylorme-behat-testing-slides�@ataylorme
@ataylorme • Slides: goo.gl/oXqt1a