Behat is a testing framework that allows testing an entire website from the perspective of a user or tester. The document provides an overview of how to get started with Behat, including installing necessary components, writing feature files with scenarios and steps, and implementing custom steps. It also discusses best practices for writing good Behat tests and provides solutions to common problems encountered when using Behat.
1. Concise guide to starting
with Behat
Berend de Boer, Xplain Hosting
awesome and affordable Drupal hosting
2. About me
● First Drupal programmer in New Zealand (2004).
● Host small Drupal sites on Xplain Hosting.
● Large sites: custom setup on AWS; interest.co.nz / LJHooker New
Zealand
3. Ever touched an old website
and you had no way of telling
everything still worked without
testing everything?
4. In this talk
● What is Behat?
● How do you get started with Behat?
● How to write tests?
● How to write good tests!
● How to write portable tests, and other common problems.
5.
6. What is Behat?
Behat allow you to test your entire website.
Behat sees your site just as a customer or tester would. You can even use
an actual browser.
Techie definition:
A php framework
for autotesting
your business expectations.
7. So how does it work?
Run first test:
behat features/test1.feature
8. First peek at script test1.feature
Feature: Gain privileged access to site
Scenario: User logs in as admin
Given I am on the homepage
When I follow "Log in"
And I fill in "Username" with "berend"
And I fill in "Password" with "abc123"
And I press "Log in"
Then I should be on "/user/1"
And I should see "Member for"
9. Goal
● Test entire website.
● As a visitor.
● Readable test: readable by stakeholders.
● Test should not be brittle: if you break your tests all the time, they
won’t be used.
● If your test passes, you can promise the minimum (possibly maximum!)
your website can do.
● Automated.
13. Other pieces of the magic
1. Selenium: you must download the “Selenium Standalone Server”
server: http://www.seleniumhq.org/download/
2. And run that with:
java -jar selenium-server-standalone-3.14.0.jar
3. You need a browser specific driver:
a. chromedriver: https://sites.google.com/a/chromium.org/chromedriver/
b. geckodriver: https://github.com/mozilla/geckodriver/releases
4. geckodriver/chromedriver must be in your PATH.
14.
15. How to write a Behat test
Feature: Gain privileged access to site
Scenario: User logs in as admin
Given I am on the homepage
When I follow "Log in"
And I fill in "Username" with "berend"
And I fill in "Password" with "abc123"
And I press "Log in"
Then I should be on "/user/1"
And I should see "Member for"
16. .feature file
● File starts with keyword Feature.
● A feature contains one or more Scenarios.
● A scenario contains one or more Steps.
● A step starts with Given, When or Then.
● Extend a step with And or But.
This is actually a language: the Gherkin language.
Goal when writing Gherkin: Business Readable, Domain Specific Language
18. Scenario
All scenarios follow this pattern:
1. Get the system into a particular state.
2. Poke it (or tickle it, or …).
3. Examine the new state.
19. Steps
We use Given to set up the context where the scenario happens, When to
interact with the system somehow, and Then to check that the outcome of
that interaction was what we expected.
Cucumber doesn’t technically distinguish between these three kind of
steps. However, I strongly recommend that you do!
20. What steps are there?
Behat + Mink come with certain “out of the box” steps:
behat -dl
You can filter them:
behat -d message
21. Login example
Feature: Gain privileged access to site
Scenario: User logs in as admin
Given I am on the homepage
When I follow "Log in"
And I fill in "Username" with "berend"
And I fill in "Password" with "abc123"
And I press "Log in"
Then I should be on "/user/1"
And I should see "Member for"
22. Background
Repeating log in steps for every scenario is not good practice: don’t repeat
yourself in programming.
One way to improve this is by using Background: follows Feature, comes
before Scenario.
Background is run before every Scenario in a Feature.
23. Background example
1. Feature: Adding content
2. Background:
3. When I go to "/user/login"
4. And I fill in "Username" with "berend"
5. And I fill in "Password" with "abc123"
6. And I press "Log in"
7. Then I should be on "/user/1"
8. Scenario: Admin can add content
9. Given I go to "/node/add/page"
10. Then I should see "Not saved yet"
24. Custom step
Feature: Gain privileged access to site
Scenario: User logs in as admin
Given I am on the homepage
And I follow "Log in"
And I fill in "Username" with "berend"
And I fill in "Password" with "abc123"
And I press "Log in"
When I login
Then I should be on "/user/1"
25. Problems:
● We need to repeat the login steps for every scenario that needs login.
● username/password hard-coded.
Enter the Drupal driver.
Drupal driver
26. Drupal driver
Remember this bit from behat.yml?
DrupalDrupalExtension:
blackbox: ~
blackbox is the default driver: no special permissions. Two other drivers:
● Drupal API driver: full programmatic access to Drupal in your own steps.
Only local site.
● Drush driver: uses Drush to access site.
Can work against a remote site.
27. Install Drupal driver
Add driver to our composer requirements:
composer require --dev drupal/drupal-driver:dev-master
You can now pick your driver:
DrupalDrupalExtension:
blackbox: ~
api_driver: ["drush" | "drupal" ]
Almost there!
28. @api tag
Finally you need the @api tag in your .feature:
@api
Feature: Gain privileged access to site
If you forget you get a nice message:
No ability to generate random in DrupalDriverBlackboxDriver. Put `@api`
into your feature and add an API driver (ex: `api_driver: drupal`) in behat.yml.
(DrupalDriverExceptionUnsupportedDriverActionException)
29. Example with Drupal driver
1. @api
2. Feature: Gain privileged access to site
3. Scenario: User logs in as admin
4. Given I am logged in as an "administrator"
A lot shorter! And shorter is better.
30. Create custom step
Take a look at this:
Scenario: User logs in as admin
Given I am logged in as an "Authenticated user"
And my profile has been filled in
When ...
Behat doesn’t know about this step, but nicely generates a snippet.
32. Steps: doc
Behat uses php-doc annotations to bind patterns to FeatureContext
methods:
1. Comment must start with: /**
2. Write definition keyword: @Given/@When/@Then.
3. The text after the keyword is the step text pattern: I do something
with :argument.
4. All token values of the pattern (e.g. :argument) will be captured and
passed to the method argument with the same name : $argument.
33. DrupalContext.php
All step definitions are done that way, even the “built-in” steps.
/**
* Creates and authenticates a user with the given role(s).
*
* @Given I am logged in as a user with the :role role(s)
* @Given I am logged in as a/an :role
*/
public function assertAuthenticatedByRole($role) {
}
34. Back to pending step
use BehatBehatTesterExceptionPendingException;
/**
* @Given my profile has been filled in
*/
public function myProfileHasBeenFilledIn() {
throw new PendingException();
}
35. Implement step
Possible implementation (simplified):
public function myProfileHasBeenFilledIn() {
$this->assertClick('Edit');
$this->assertUncheckBox('Personal contact form');
$this->selectOption('Time zone', 'UTC');
$this->pressButton('Save');
$this->assertSuccessMessage('The changes have been
saved.');
}
36. Finding methods
What existing Mink methods to call exactly is outside the scope of this talk.
But use this a lot:
behat -d click
default | [When|*] I click :link
| at
`DrupalDrupalExtensionContextMinkContext::assertClick()`
38. Twitter test
Scenario: Retweet
Given I am on the homepage.
When I fill in “What’s happening” with “Hello World”
And I press “Tweet 1”
And I click “Reply”
And I fill in “Tweet your reply” with “Tweet 2”
And press “Reply”
Then “Replies” should be “1”
39. Better Twitter test
Scenario: Retweet
When I tweet “Hello World”
And I reply to my last tweet with “Tweet 2”
Then “Replies” should be “1”
40. Account creation steps
But sometimes writing out all the clicks is good! Would you sign up for this
site?
Scenario:
Given I am on a news article page
When I click “comment”
Then I should see “You must be logged in.”
41. Account creation steps
When I click on “Create account”
And fill in “Email” with “berend@example.com”
And I fill in “Password” with “abc123”
And I check “I am not a robot”
And I click all the street signs
And I click all the street signs again
And again
And I wait for the confirmation email to arrive
42. Account creation steps
And I click on the link in the confirmation email
And I click on “Login”
And I fill in “Email” with “berend@example.com”
And I fill in “Password” with “abc123”
And I click “Login”
And I find my original news article again.
And I click on “comment”
And I fill in “Comment” with “Finally”
And I press “Save”
Then I appear to be very keen to comment
43. A better way
Using Behat well:
● Seldom use built-in steps.
● Write your own steps.
● Avoid brittle steps.
● Write steps business analysts can understand.
46. Get started
● When a bug is reported
● Then write the test that replicates it.
● And the test should fail.
1 scenario (1 failed)
2 steps (1 passed, 1 failed)
● When the bug if fixed.
● Then test should pass.
1 scenario (1 passed)
2 steps (2 passed)
47. Gherkin’s purposes
Gherkin serves three purposes:
1. documentation and,
2. acceptance tests
3. Bonus: when it yells in red it's talking to you, telling you what code you
should write.
48. New projects
For new projects:
● Write all requirements as scenarios.
● Implement all steps as deferred.
And my profile has been filled in # FeatureContext::myProfile()
TODO: write pending definition
1 scenario (1 pending)
2 steps (1 passed, 1 pending)
● When they all pass, your project is done. Allows progress tracking!
49. Not a bug, just a missing scenario
One of the wonderful things I discovered when I first used Cucumber to build a
complete application from the outside-in was when we started manual
exploratory testing and discovered some bugs. Almost without exception, every
one of those bugs could be traced back to a gap in the Cucumber scenarios we’d
written for that part of the system.
Because we’d written the scenarios collaboratively, with businesspeople and
developers working together to get them right, it wasn’t anybody’s fault that
there was a bug. It was just an edge case we hadn’t originally anticipated
50. Not a bug, just a missing scenario
In my experience, bugs are a big source of friction and unhappiness in software
teams.
Businesspeople blame them on careless developers, and developers blame
them on inadequate requirements from the businesspeople. Actually they’re just
a natural consequence of software development being a really complex
endeavor.
Using Cucumber really helped the team see that closing these gaps is everyone’s
job. The blaming just didn’t happen, and we were a much happier team as a
result.
51. Who writes features?
Why not let analysts and stakeholders write features?
You as a developer can implement the steps.
53. How to run a single feature
You seldom want to run all features in all scenarios.
1. Run all scenarios of a feature:
behat features/test.feature
2. Run a single scenario:
behat features/test.feature:35
where 35 is the line number of the scenario you want to run.
54. Transform
1. Scenario: user must validate email before he can login
2. Given the user has created an account
3. When the user attempts to login
4. Then he is informed that his email is not yet validated
5. When the validation email is received
6. And the user visits the “the first link in the email”
7. Then the user can login successfully
55. Transform function
/**
* @Transform /^the first link in the last email$/
*/
public function castFirstLinkInLastEmail() {
return $this->first_link_in_last_email;
}
56. Tags - @javascript
Normally you don’t see the browser coming up.
In my live demos that happened, because I used this tag:
@javascript
Feature: ...
can be used both for all scenarios (specify above feature), or per scenario.
58. Tags - in FeatureContext
FeatureContext is the default class for your steps.
With these tags you can write initialisation and cleanup code:
● @BeforeSuite: run once
● @AfterSuite: run once
● @BeforeScenario: once every scenario
● @AfterScenario: once every scenario
About hooks: http://behat.org/en/latest/user_guide/context/hooks.html
59. A copy of your production environment may have modules enabled that
make testing hard. Disable them before a run:
use BehatTestworkHookScopeBeforeSuiteScope;
/**
* @BeforeSuite
*/
public static function setup(BeforeSuiteScope $scope) {
module_disable (array ('mollom', 'honeypot'), FALSE);
}
Disable modules - D7
60. Uninstall modules - D8
In Drupal 8 you can only uninstall:
use BehatTestworkHookScopeBeforeSuiteScope;
/**
* @BeforeSuite
*/
public static function setup(BeforeSuiteScope $scope) {
Drupal::service('module_installer')->uninstall(['mollom']);
}
61. Regions - behat.yml
Often you want to check for the existence of text or links in particular
“regions” (main region, sidebar, header, footer).
Define them in behat.yml with:
extensions:
DrupalDrupalExtension:
region_map:
content: ".block-system-main-block"
footer: ".site-footer"
62. Regions - scenario
Scenario: User logs in as admin
Given I am on the homepage
Then I should see "No front page content has been created
yet." in the content region
63. Portable tests - behat.yml
Write portable tests, don’t hard-code:
One way is to put variables in behat.yml:
default:
suites:
default:
contexts:
- FeatureContext:
inbox:
server: mail.example.com
user: behat
password: abc123
arg2: “This”
64. Portable tests - __construct()
Your parameters are passed in order you defined, make sure argument
names match:
class FeatureContext extends RawDrupalContext {
public function __construct($inbox, $arg2, $arg3) {
$this->inbox = $parameters['inbox'];
$this->arg2 = $parameters['arg2'];
$this->arg3 = $parameters['arg3'];
}
65. Environment settings
Environment specific settings can go in environment variable:
BEHAT_PARAMS.
This is a JSON string, more: http://behat-drupal-
extension.readthedocs.io/en/3.1/environment.html
66. Another Context
Your FeatureContext can become very big very quickly.
You can put code in another context, and new steps will become available.
For example create features/bootstrap/CKEditorContext.php.
class CKEditorContext extends RawMinkContext {
/**
* @Given I fill in the rich text editor :arg1 with :arg2
*/
public function iFillInTheRichTextEditorWith($arg1, $arg2) {
}
67. Context - behat.yml
Make sure we can find the context by adding it to behat.yml:
default:
suites:
default:
contexts:
- FeatureContext
- ...
- CKEditorContext
68. Subcontexts - behat.yml
Modules can also make steps available. Setup subcontexts in behat.yml
so behat can find them:
DrupalDrupalExtension:
...
subcontexts:
paths:
- "modules/custom/behat_test"
69. Subcontexts - php
Define a .behat.inc file in your module such
modules/custom/behat_test/behat_test.behat.inc.
use DrupalDrupalExtensionContextDrupalSubContextBase;
use DrupalDrupalExtensionContextDrupalSubContextInterface;
class BehatTest extends DrupalSubContextBase implements
DrupalSubContextInterface {
/**
* @Given I do something interesting
*/
public function iDoSomethingInteresting() {
}
70. Finding elements
Often you will need to find elements on a page (input fields, buttons).
Two ways to find them:
● CSS: easier to write.
● XPath: more powerful.
$el = $this->elementTextContains('css', 'h1', 'Payment
processing');
$el = $this->elementTextContains('xpath, '//h1',
'Payment processing');
71. Headless
Mink provides the web site integration for Behat. Drivers:
● Headless, without support for JavaScript: Goutte.
● Headless, with support for JavaScript: ZombieDriver.
● Headless actual browser: PhantomJS.
● Actual browser: Selenium2Driver.
And more differences: http://mink.behat.org/en/latest/guides/drivers.html
72. Show failed test in browser
Test in actual browser can be quite slow. But if headless fails, what that?
What happpened?
extensions:
BehatMinkExtension:
zombie:
show_auto: true
show_cmd: "firefox-trunk %s"
73. Execute javascript
Our UIs can get very fancy with javascript controls. Hard to test.
Run JavaScript from your step definition:
/**
* @Given I fill in the rich text editor :arg1 with :arg2
*/
public function iFillInTheRichTextEditorWith($arg1, $arg2) {
$field = $this->getSession()->getPage()->findField($arg1);
$id = $field->getAttribute('id');
$args = ['ckeditor_instance_id' => $id, 'value' => $arg2];
$args_as_js_object = json_encode($args);
$this->getSession()->executeScript(
"args = {$args_as_js_object};" .
"CKEDITOR.instances[args.ckeditor_instance_id].setData(args.value);"
74. Tests stop running for no reason
When suddenly your Selenium tests don’t run anymore, it’s most likely
because you received a browser update.
What to do:
1. Update to latest chromedriver/geckodriver.
2. May need to update Selenium.
77. Continuous integration
● When a change is committed
● Then spin up a new machine
● And deploy the code.
● And run all (or tagged) scenarios.
● Then there should be no failures.
79. Benefits
1. How often has an unmaintained website become the gate through
which secrets were stolen? (Equifax)
You can upgrade old websites, and be sure it still works.
2. You can tell how far a project is from being finished.
3. It’s very helpful when maintaining/extending a site.
4. Great tool for specifying what a site needs to do.
80. Resources
● Read the first 6 chapters of the book!
● Drupal Behat extension: http://behat-drupal-
extension.readthedocs.io/en/3.1/index.html
● Me: berend@xplainhosting.com
● Contact me when your business wants to get started with Behat!
Any questions?
Hinweis der Redaktion
What happens is obviously is that old websites don’t get maintained.
Intend to be a complete guide. Not just an intro. Hope to inspire you to really use Behat during development and maintenance.
Everything I know I got from this book!
Module tests just test a module. Very limited view.
Did you note you never have to click on end user agreements with free software?
Following a bit boring, just download the slides if you want to follow it.
For techies. Note the dev-master, very important!
Behat has nothing to do with web site testing. Behat\MinkExtension provides access to a browser: either headless, or an actual one.
We’re going back to our first script.
Again, not limited to web testing, can test anything.
Demonstration
Back to the same feature, let’s improve this.
Say you can have multiple scenarios.
Back to the same feature, let’s improve this.
Second improvement: basically pulling in more steps.
Note the master branch again!
No new steps gained, they were already present, but didn’t work. Also no hard-coded username/password.
Highlight the following in: bit.
This is intro to third way.
Run test5.feature to show the snippet that is generated. Probably have been waiting to see how the magic works!
So contrast a feature with previous slide: how hard is it to sign up?
No wonder this site gets only trolls.
https://pt.slideshare.net/lhridley/php-dev-behat
Expand on previous slides: he let’s analysts and stakeholders write features.
Introduce that my Behat environment probably worked a bit differently then people had seen if they had tried Behat.