How to test with Behat

by Mario Blažek -

A very important, but often overlooked, part of a software development process is testing. The thing is, if you don’t test your website, it could happen that you deploy the finished product and don’t notice any existing bugs until the production. That would be a very unpleasant experience both for you and your client. Here is where Behat comes in handy.

Imagine this situation: you've built a website, and a very nice one at that, with a lot of neat features and a design that could only have come from the mind of an exquisite designer. Then comes Day Zero, you launch it and forget about it. Many, many projects later, the client comes back to you with new features. In the meantime, new software, like Symfony 3 and eZ Platform CMS, gets released. Like any good developer, you want to use the most up-to-date and proven tools.

You start by reading reference and migration guides, best practices, tons of Slack conversations and you feel ready to tackle migration to a newer version. Finally, you take your courage with both hands, start updating composer.json lines and go ahead with composing. Eventually, everything goes smoothly (in an ideal world) and you are prepared for the phase two.

In phase two, you want to assure that the code does not break, so you begin clicking on the website, following links, then checking menu items, login, etc. You find minor bugs, fix them, and then go all over again to check if the last fix broke something. Can you be sure that you checked every feature after every change? Can you assume that there isn’t a goblin living somewhere in the dark corners of your website that will bite you when you least expect it? Finally, can you sleep at night not having the right answer to these questions?

I sleep well at night and will give you a recipe on solving those issues.

Automated testing to the rescue

Why would anyone want to do repetitive tasks (except computers, of course)? It is boring and error-prone. So, let’s take a look at some automated testing categories:
 * Unit testing - this tests if a small piece of code, function or a method works as expected
 * Integration testing - combines Unit tests into groups and ensures they work together properly
 * System testing - tests the system as a whole
 * Behavior testing - this involves clicking all over the website and user interactions. This kind of testing is also referred to as BDD or Behavior Driven Development and today we will investigate it more deeply.

Behavior-Driven Development

Behavior-driven development, or BDD, is the extension of Test-Driven Development (TDD). It’s similar to TDD except that, instead of writing tests first, we’ll create written descriptions of the behavior of a feature. Writing tests is great, but before we do any work, we need to understand the exact behavior of the feature we’re building.

We have two styles of BDD - SpecBDD and StoryBDD. The Spec is used for writing smaller tests that cover simple parts of the code (methods), like unit tests. There is a very helpful library called PHPSpec that helps writing these tests and is available for PHP programming language.

We’ll be talking about the second type of BDD - StoryBDD, used for functional testing. Every development process has many goals to accomplish, hence we have developers, project managers, designers, clients involved, and StoryBDD aims to solve the communication problems between them by using a standard language for describing these features. It gives you a simple workflow:
 * Define the features
 * Prioritize the features
 * Write readable scenarios
 * Implement the features

There is a great PHP library for StoryBDD style of testing which I like very much, called Behat. I will show you how to use it in your development process in a moment.

What is Behat

Behat is an open-source behavior-driven development framework (BDD) and also an automated testing system. It was developed by Konstantin Kudryashov who found inspiration in Ruby's Cucumber project and Gherkin syntax. Kudos to Konstantin!

Gherkin syntax is probably the most appealing part of Behat. Most Behat tests are understandable to anyone, whether they’re a developer, a project manager, or a business owner because they are written in plain English phrases which are then combined into human readable scenarios.

Adding up multiple tests together into a feature forms a solid group of the user acceptance tests. When the tests pass, you know you are facilitating the user stories (Behat scenarios) of the features you were asked to implement. You don’t have to go back to your project manager and say you’re done and wait for them to click around for hours, telling you things are still missing.

Behat is capable of testing console commands, REST APIs, etc. To enable Behat to test a website, you need to add Mink and a browser emulator to the mix. Mink methods are the connector between Behat and a big list of available drivers, and they provide a consistent testing API.

There are several commonly used browser libraries that extend Behat functionalities. Some, like Goutte, are very fast, but do not support JavaScript. Others, like Selenium, Selenium2, and Zombie, are full-featured browsers, but will run more slowly. I mean, really slowly.

So, when you hear people talking about Behat, they're usually talking about all three components: Behat, Mink, and browser libraries.

Installation

First of all, in order to start testing, we need to install Behat. Composer can help us here, simply run:

php composer.phar require --dev behat/behat

This will install the newest version of the core library. To give Behat wings we will also run these:

php composer.phar require --dev behat/mink
php composer.phar require --dev behat/mink-extension
php composer.phar require --dev behat/mink-goutte-driver
php composer.phar require --dev behat/mink-selenium2-driver

When the installation finishes, we need to do some configuration.

Configuration

Because Behat is an awesome library, it gives us a simple command for bootstrapping configuration.

php vendor/bin/behat --init

It will create features directory and bootstrap directory inside it, with basic FeatureContext class. Basically Context class is place where nice and readable Gherkin syntax is translated to PHP code. Both locations and directory name can be customized, but this change should be addressed in the main configuration file. By default, FeatureContext class will implement Context and SnippetAcceptingContext interfaces.
Context interface will make your class a valid context class
SnippetAcceptingContext enables appending code snippets of unknown steps to your class

You should also make your class extend “Behat\MinkExtension\Context\MinkContext”. It will bring quite a big list of predefined steps into your Context class. You can easily check that list by entering this simple command:

php vendor/bin/behat -dl

It should give you a list similar to this one, but with a lot more predefined steps:

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 )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should be on (?:|the )homepage$/
default |  Then /^the response status code should be (?P<code>\d+)$/
default |  Then /^the response status code should not be (?P<code>\d+)$/
default |  Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/

If you are interested in more predefined Contexts, you can check out KnpLabs repo. eZ Systems BehatBundle is also a good reference, as well as Sylius project led by Paweł Jędrzejewski, one of the prominent users of Behat.

Create a new main configuration file named “behat.yml” in the root of your project. I’ve added more extensive configuration to enable testing with Selenium by using Chrome web browser. For this, we will need to download Selenium server and Chrome driver.

default:
    extensions:
        Behat\MinkExtension:
            default_session: selenium2
            goutte: ~
            selenium2:
                # chrome
                wd_host: "http://127.0.0.1:4444/wd/hub"
                # chrome
                capabilities: { "browserName": "chrome", "browser": "chrome", "version":  "25", 'chrome': {'switches':['--no-sandbox']}}
            base_url: 'http://project.local/'
            # chrome
            browser_name: chrome
            files_path: 'files'
    suites:
        default:
            contexts:
                - FeatureContext

We don't have any features yet, but you can try to run Behat:

php vendor/bin/behat

To enable browser emulation we need to run Selenium with Chrome driver first:

java -jar selenium-server-standalone-2.46.0.jar -Dwebdriver.chrome.driver="chromedriver"

Let’s start with a simple feature

Now, we have a simple registration process on our website and we want to make sure that all the updating did not break it.

In the features directory, we will create a new file called “register.feature”. We should start with describing our feature:

Feature: Customer registration
    In order to perform customer registration
    As a website user
    I need to be able to open customer registration, fill in and submit data

The first line starts with “Feature” followed by a short title. It highlights the purpose of this feature. The “In order to” line defines the value. Basically, it should answer the questions: Why should we build this feature? Why is it important? The next line - starting with “As a” defines who will use this value. Maybe an admin user or some unknown website visitor? Finally, the last line - starting with “I need to” - is a short description of actions the user will be able to take once this feature is complete.

Let’s write our first scenario:

Feature: Customer registration
    # ...

    Scenario: Open customer registration page
      Given I am on homepage
      When I follow "Customer registration"
      Then I should be at "/customer-registration"
      And I should see "Welcome to customer registration page!"

The scenario is made up of three different parts: Given, When and Then. The first is Given, which describes our starting point (current system state) and in this step we cannot describe the user action because this is something that developer needs to prepare. In our case, a website visitor starts from the homepage.

The second part of every scenario is When, which describes user actions. The last step, Then, is used to describe the result of user action.

Let’s add another feature that will actually test the registration process (we have a two step form):

Scenario: Fill in data and submit customer registration form
    Given I am on "/register-customer"
    # first page
    When I fill in "First name" with "Jane"
    And I fill in "Last name" with "Doe"
    And I fill in "Email" with "[email protected]"
    And I fill in "Mobile" with "00-000-0000"
    And I fill in "Password" with "password"
    And I fill in "Confirm password" with "password"
    And I press "Go to second page"
    # Second page
    And I fill in "Date of birth" with "20/02/1981"
    And I select "Female" from "Gender
    And I fill in "LinkedIn" with "unknown"
    And I attach the file "logo.jpeg" to "Logo
    And I check "I agree with Terms"
    And I press "Register"
    # Check for successful registration
    Then I wait for "10" seconds
    And I should be at URL "/login"
    And I should see "Email"
    And I should see "Password"
    And I should see "Log in"
    And I should see "Keep me logged in"
    And I should see "Forgotten your password?"

Running tests

All features can be run by:

php vendor/bin/behat

To run only specific feature file enter:

php vendor/bin/behat features/register.feature

There is also a way to run a single scenario. Suppose we want to run only the form registration part, just add a line number where the scenario is defined:

php vendor/bin/behat features/register.feature:11

There is also a convenient way of tagging scenarios with @ tag. Imagine we have more than one registration form and we want to test only the tagged ones. First we should add @ tag before a Scenario keyword:

@registration
Scenario: Fill in data and submit customer registration form
#...

and run them with:

php vendor/bin/behat --tags=registration

Features running in browser mode

Notice that the current features were running in “headless” mode with Goutte driver. Basically, Behat was running curl requests against URLs on your website, which makes the testing process quite fast, but also makes Behat lose the ability to test features that require Javascript. We’ve been well prepared since the beginning, so to test features we only need to do a small change - add @javascript tag before every scenario that requires Javascript, like this:

@javascript
Scenario: Fill in data and submit customer registration form
#...

Now you can run only Javascript scenarios by:

php vendor/bin/behat --tags=javascript

And you should notice the new Chrome browser window popping out and starting fiddling with your site. Amazing! Looks like some invisible goblin doing magic.

In conclusion

This was a simple introduction to Behat. Behat has many benefits that accommodate all those who are included in the software development process, starting with developers themselves over project managers to clients. It enables you to run the features all over again, and again, and again. There is no need to manually go over the website and click around. So, test with Behat and sleep well at night.

To learn more about BDD please take some time and check these links:

Behat Basics by awesome guys Ryan Weaver and Saša Stamenković (Video from PHP Summer Camp 2014) - Part 1 & Part 2
https://sites.google.com/site/unclebobconsultingllc/the-truth-about-bdd
http://dannorth.net/whats-in-a-story/
https://github.com/cucumber/cucumber/wiki/Cucumber-Backgrounder

Photo credit: Kenny Matic / Foter / CC BY-SA

Comments

This site uses cookies. Some of these cookies are essential, while others help us improve your experience by providing insights into how the site is being used.

For more detailed information on the cookies we use, please check our Privacy Policy.

  • Necessary cookies enable core functionality. The website cannot function properly without these cookies, and can only be disabled by changing your browser preferences.