PHP + BDD = Behat, or the Tale of the Miracle Library

Anyone seriously involved in Ruby development knows about the wonderful Cucumber gem. In short, it is a library for automated testing, sharpened under BDD. More details can be found in the topic of the dapi habrayuzer , or better yet, see the podcast from Rain Bates. The main charm of the "cucumber" is that it allows you to write tests in a language that people understand, and not even necessarily English. It looks like this:

Feature: Addition 
  In order to avoid silly mistakes 
  As a math idiot 
  I want to be told the sum of two numbers 
  Scenario: Add two numbers 
    Given I have entered 50 into the calculator
      And I have entered 70 into the calculator
     When I press add
     Then The result should be 120 on the scree

Thanks to Cucumber, I got hooked on BDD on rails. But in PHP, which you have to work with most of the time, relations with BDD somehow did not work out. And first of all, due to the lack of decent tools. But once fate brought me to the Behat library page (written, by the way, by the everzet habrayuzer ). And happiness fell on me ...

Installation and Preset


Actually, Behat is "Cucumber from the PHP world." The same syntax (using the Gherkin language ), the same file structure, almost identical method for determining steps. It can be seen that the author was inspired by the "elder brother" of his library, which he does not hide. Everything looks beautiful, but we didn’t look at it. It's time to try the product in action.

I’ll make a reservation right away - Behat requires PHP 5.3.1 for its work. Therefore, if you have not got it yet, it's time to do it.

The author of the site offers several ways to install Behat. I will choose the very first and easiest - through Pear:
$ pear channel-discover pear.everzet.com
$ pear install everzet/behat-beta

Tadam! We are the happy owners of Behat :) In principle, you can even now go to the folder with the tested project and start writing scripts, but I prefer to “tune” the library a bit. So, first of all, let's create a native habitat for Behat in the project folder. It, as already mentioned, is borrowed from Cucumber and has the following form:

| - features
   `- steps
   | `- * _steps.php
   `- support
       `- env.php


More about what we have:
  • features - this is the folder where we will store the scripts (* .feature files, about them a little later);
  • features / steps - this is a folder with descriptions of the "steps" of testing;
  • features / support - folder with support scripts. Of particular importance here is the env.php file , which describes the configuration of the environment. We will open it.

The env.php file is initially empty. Its main importance for us is that it is executed every time before the execution of the next script. And it is here that it is convenient to connect all the libraries and project files we need. It is also convenient to define the variables and functions that we will need in the tests. To store them, by the way, it’s very convenient to use the $ world variable, which is supplied to us at the input, each time new. By and large, to use the basic functionality, we do not need to configure anything, it is enough that Behat and PHP itself provide us. But I still like to use the verification functions from PHPUnit, and I will connect them, with your permission.

If you do not have PHPUnit installed, it is installed very simply, through the same Pear:
$ pear channel-discover pear.phpunit.de
$ pear channel-discover components.ez.no
$ pear channel-discover pear.symfony-project.com
$ pear install phpunit/PHPUnit

Done, it remains only to register in env.php:

Also, in order for classes and functions from our project to be available, we need to connect the appropriate files to the environment. I do not like to clutter up the environment initialization with extraneous inclusions, and therefore I will combine them into one file - includes.php , and I’ll already connect it to the environment. As a result, the env.php file has the form:


We describe features and make the first launch


Well, that’s all, we are ready for testing, you can begin to describe features. As an example, I decided to take such a difficult thing to implement and test as adding two numbers. So, create the calc.feature file in the features folder and write:

Feature: Addition
    In order to avoid silly mistakes 
    As a math idiot 
    I want to be told the sum of two numbers
    Scenario:
       Given I have an calculator
        When I have entered 30 as first number
         And I have entered 20 as second number
         And I press 'Add'
        Then The result should be 50


We save and drive the command into the console.
$ behat features

At the output we get:
1 scenario (1 undefined)
5 steps (5 undefined)
0.091s
You can implement step definitions for undefined steps with these snippets:
$ steps-> Given ('/ ^ I have an calculator $ /', function ($ world) {
    throw new \ Everzet \ Behat \ Exception \ Pending ();
});
$ steps-> When ('/ ^ I have entered (\ d +) as first number $ /', function ($ world, $ arg1) {
    throw new \ Everzet \ Behat \ Exception \ Pending ();
});
$ steps-> And ('/ ^ I have entered (\ d +) as second number $ /', function ($ world, $ arg1) {
    throw new \ Everzet \ Behat \ Exception \ Pending ();
});
$ steps-> And ('/ ^ I press \' ([^ \ '] *) \' $ / ', function ($ world, $ arg1) {
    throw new \ Everzet \ Behat \ Exception \ Pending ();
});
$ steps-> Then ('/ ^ The result should be (\ d +) $ /', function ($ world, $ arg1) {
    throw new \ Everzet \ Behat \ Exception \ Pending ();
});

This tells us that we have not defined the "steps" of the scenario. But we know even more - we do not have a calculator class. Write, not a problem.

class Calc {
    protected $ first = 0;
    protected $ second = 0;
    protected $ result = 0;
    public function setFirst ($ num) {$ this-> first = $ num; }
    public function setSecond ($ num) {$ this-> second = $ num; }
    public function add () {$ this-> result = $ this-> first + $ this-> second; }
    public function getResult () {return $ this-> result; }
}

We describe the steps and conduct a successful test.


Well, now we can move on to the description of the steps. Yes, by the way, do not forget to include the file with the calculator class in the features / support / includes.php file. As we can see, Behat kindly offered us patterns for defining steps. We copy them, tweak them a bit and save them in the features / steps / calc_steps.php file . You should get something like the following:
Given ('/ ^ I have an calculator $ /', function ($ world) {
    $ world-> calc = new Calc ();
});
$ steps-> When ('/ ^ I have entered (\ d +) as first number $ /', function ($ world, $ num) {
    $ world-> calc-> setFirst ($ num);
});
$ steps-> When ('/ ^ I have entered (\ d +) as second number $ /', function ($ world, $ num) {
    $ world-> calc-> setSecond ($ num);
});
$ steps-> When ('/ ^ I press \' Add \ '$ /', function ($ world) {
    $ world-> calc-> add ();
});
$ steps-> Then ('/ ^ The result should be (\ d +) $ /', function ($ world, $ res) {
    assertEquals ($ res, $ world-> calc-> getResult ());
});
?>

We start the test again - voila! Test passed :)

Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers
  Scenario: # features / calc.feature: 6
    Given I have an calculator # features / steps / calc_steps.php: 5
    When I have entered 30 as first number # features / steps / calc_steps.php: 9
    And I have entered 20 as second number # features / steps / calc_steps.php: 13
    And I press 'Add' # features / steps / calc_steps.php: 17
    Then The result should be 50 # features / steps / calc_steps.php: 21
1 scenario (1 passed)
5 steps (5 passed)
0.114s

Trying to test the page.


Testing the operation of classes is, of course, an important part of the project. But the customer, as usual, wants not working classes from us, but a correctly functioning (in his opinion) application. Simply put - he needs the correct operation of the interface. Verification of what we now do. Ruby on Rails traditionally uses cucumer + webrat + nokogiri to test pages. In the PHP world, everything turned out to be a little more complicated ... Everzet on its page suggests using the Goutte library based on Symfony 2 for this. This library is much inferior in capabilities to the aforementioned bundle, but it was better to find nothing on the Internet (if anyone knows , please, unsubscribe). Goutte installs extremely simply - download the phar archive and connect it to env.php.

As an example for testing, I sketched a simple page:
"; echo "Checkbox =". $ _GET ['checkbox']. "
"; echo "Radio =". $ _GET ['radio']. "
"; echo "Select =". $ _GET ['selectbox']. "
"; } ?>

Task: check the operation of the form on it. Yes, the task is very synthetic, but this is just an example :) So, we’ll cover the list of tests:
Feature: tests
    After submit form all values ​​of fields
    Should be showed in top of page
    Scenario: fill field
        Given I'm on test page
         When I fill in 'textfield' with 'some text' in form 'Submit'
          And I submit form 'Submit'
         Then I should see 'Text = some text'
    Scenario: Checking checkbox
        Given I'm on test page
         When I tick checkbox 'checkbox' in form 'Submit'
          And I submit form 'Submit'
         Then I should see 'Checkbox = checkbox'
    Scenario: Selecting radio
        Given I'm on test page
         When I select 'radio2' in radio 'radio' in form 'Submit'
          And I submit form 'Submit'
         Then I should see 'Radio = radio2'
         Then I should not see 'Radio = radio1'
    Scenario: Selecting option in selectbox
        Given I'm on test page
         When I select 'option3' in selectbox 'selectbox' in form 'Submit'
          And I submit form 'Submit'
         Then I should see 'Select = option3'
         Then I should not see 'Select = option1'
    Scenario: Fill some fields
        Given I'm on test page
         When I fill in following in form 'Submit':
            | textfield | some text |
            | checkbox | true |
            | radio | radio3 |
            | selectbox | option2 |
          And I submit form 'Submit'
         Then I should see 'Text = some text'
          And I should see 'Checkbox = checkbox'
          And I should see 'Radio = radio3'
          And I should see 'Select = option2' within 'div'
    Scenario: Clicking on link
        Given I'm on test page
         When I click on link 'Click me' within '#linkdiv'
         Then I should see 'Text = text'
          And I should see 'Checkbox = checkbox'
          And I should see 'Radio = radio3'
          And I should see 'Select = option2' within 'div'
          And the 'textfield' field in form 'Submit' should be blank


A small explanation of why the form in the tests is called "Submit". In Goutte, the form is selected by the submit button on it. The button itself is searched by id or value. And the button on the test page is proudly named Submit, hence this wording.

There are many steps, the steps are different, and everyone is involved in working with Goutte. Fortunately, all these steps have already been described by me and collected in a file. You can pull the desired one from the git repository git: //github.com/DarthSim/behat_websteps.git . In the repository you will find:
  • File features / support / env.php with pre-configured to test the pages of the environment;
  • File features / support / paths.php to describe the paths (details below);
  • Features / support / includes.php file for connecting your classes;
  • File features / support / goutte.phar - actually Goutte;
  • File features / steps / custom.php with descriptions for testing pages of steps.

We copy the files to the folder with your project, preserving the hierarchy, and run the test. And we get an error ... The

Unknown path 'test page'. You can define it in [features_folder]/support/paths.php

error speaks for itself - the test does not know what kind of "test page" we want to check. The simplest fix is ​​to set the path for the “test page” page in the features / support / paths.php file . Suppose that the test page is located at tests.dev/behat.php , so we need to register it in the paths file.

$world->paths['test page'] = "http://tests.dev/behat.php";

Run the test - fine, the test passed! I leave the analysis of the remaining steps to the reader, since they are similar to the analogues from webrat. Of course, the resulting functionality is much poorer than webrat, but I think this is only the beginning, then it will be better.

Draw conclusions and provide links


So, it is safe to say that Behat is a worthy successor to Cucumber in the PHP world. The library has not yet grown to version 1.0, but it already represents a high-quality, finished product. We wish the developer success in the development of his brainchild and send him the rays of good. Below are some useful links:

Behat repository on GitHub
Behat WIKI
Behat API
Goutte
Symfony 2 API repository (helps with Goutte)

Also popular now: