Anyone who is seriously involved in Ruby development knows about the wonderful Cucumber gem. In short, it is a library for automated testing, honed under BDD. You can read more in the
topic of dapi
habrauser , and even better watch the
podcast from Rain Bates. The main charm of the "cucumber" is that it allows you to write tests in a language understandable to man, 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
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 the tracks. But here on PHP, with which one has to work most of the time, the relationship with BDD somehow did not develop. And first of all because of the lack of decent tools. But one day fate brought me to the page of the
Behat library (written, by the way, by the habrayuzer
everzet ). And happiness fell on me ...
Installation and pre-setting
Actually, Behat is “Cucumber from the world of PHP”. The same syntax (
Gherkin language is
used ), the same file structure, almost identical method of determining steps. It is evident that the author was inspired by the "elder brother" of his library, which he does not hide. It looks beautiful, but we didn’t look at her. It's time to try the product in action.
Immediately make a reservation - Behat requires PHP 5.3.1 for its work. Therefore, if you have not yet acquired it, it's time to do it.')
The author on the site offers several ways to install Behat. I will choose the very first and easiest one through Pear:
$ pear channel-discover pear.everzet.com
$ pear install everzet/behat-beta
Tadam! We are happy owners of Behat :) In principle, you can even now go to the folder with the project under test and start writing scripts, but I prefer to “tune” the library a little. So, first of all we will create a habitat native to the Behat in the project folder. She, as already mentioned, borrowed from Cucumber and has the following form:
| - features
`- steps
| `- * _steps.php
`- support
`- env.php
More about what we have:
- features is a folder where scripts will be stored (files * .feature, about them a bit later);
- features / steps is a folder with descriptions of the "steps" of testing;
- features / support - folder with support scripts. Of particular importance here is the file env.php , which describes the configuration of the environment. We will open it.
Initially, the env.php file is empty. Its main importance for us is that it is executed each time before executing the next script. And it is here that it is convenient to connect all the libraries and project files we need. Here it is convenient to determine the variables and functions that we need in the tests. For their storage, by the way, it is very convenient to use the $ world variable, which is fed to us at the entrance, each time new. By and large, to use the basic functionality we don’t have any need to tweak something, it’s enough that PHP provides us with Behat. But I still like to use the functions of checks 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:
<? php
require_once 'PHPUnit / Autoload.php';
require_once 'PHPUnit / Framework / Assert / Functions.php';
?>
Also, in order for us to have classes and functions available from our project, we need to connect the appropriate files to the environment. I do not like to litter the initialization of the environment with extraneous inclusions, and therefore I will combine them into one file -
includes.php , and I will connect it to the environment. As a result, the env.php file has the form:
<? php
require_once 'PHPUnit / Autoload.php';
require_once 'PHPUnit / Framework / Assert / Functions.php';
include 'includes.php';
?>
We describe the features and make the first launch
Well, that's all, we are ready to test, you can begin to describe the features. As an example, I decided to take such a difficult thing to implement and test as the addition of 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
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
Save, and drive into the console command
$ 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. We will 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 go to the description of the steps.
Yes, by the way, do not forget to connect the file with the calculator class to the file features / support / includes.php. As we can see, Behat kindly offered us patterns for defining steps. Copy them, tweak them a bit and save them in the
features / steps / calc_steps.php file . You should have something like this:
<? php
$ steps-> 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
Scenario: # features / calc.feature: 6
Given I have an calculator # features / steps / calc_steps.php: 5
When I have entered 30 as the 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 features 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 class performance is certainly an important part of the project. But the customer, as usual, does not want the working classes from us, but a properly functioning (in his opinion) application. Simply put - it needs the correct operation of the interface. Checking what we now do. In Ruby on Rails, cucumer + webrat + nokogiri is traditionally used to test pages. In the world of PHP, everything turned out to be somewhat more complicated ...
Everzet himself suggests using the
Goutte library, based on Symfony 2, for this business. This library is much inferior in its capabilities to the above link, but you couldn’t find anything better on the Internet (if someone knows Please, unsubscribe, please). Goutte is installed very simply - download the phar-archive and connect it to env.php.
As an example for testing, I sketched a simple page:
<! DOCTYPE html>
<html>
<head>
</ head>
<body>
<div> <?
if ($ _GET ["submit"]) {
echo "Text =". $ _GET ['textfield']. "<br />";
echo "Checkbox =". $ _GET ['checkbox']. "<br />";
echo "Radio =". $ _GET ['radio']. "<br />";
echo "Select =". $ _GET ['selectbox']. "<br />";
}
?> </ div>
<form method = "get" action = "behat.php">
<div>
<input type = "text" name = "textfield" value = "">
</ div>
<div>
<input type = "checkbox" name = "checkbox" value = "checkbox">
</ div>
<div>
<input type = "radio" name = "radio" value = "radio1" checked = "checked">
<input type = "radio" name = "radio" value = "radio2">
<input type = "radio" name = "radio" value = "radio3">
</ div>
<div>
<select name = "selectbox">
<option value = "option1" selected = "selected"> option1 </ option>
<option value = "option2"> option2 </ option>
<option value = "option3"> option3 </ option>
</ select>
</ div>
<div>
<input type = "submit" name = "submit" value = "Submit">
</ div>
<div id = "linkdiv">
<a href="behat.php?textfield=text&checkbox=checkbox&radio=radio3&selectbox=option2&submit=Submit"> Click me </a>
</ div>
</ form>
</ body>
</ html>
Task: check the form on it. Yes, the task is very synthetic, but this is just an example :) So, we can list the 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 the following form:
| 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 the 'Submit' should be blank
A small explanation of why the form in the tests is named “Submit”. In Goutte, the form is selected using the submit button on it. The button itself is searched by id or value. And the button on the test page bears the proud name of Submit, hence the wording.There are many steps, different steps, and everyone is involved in working with Goutte. Fortunately, all these steps have already been described by me and collected into a file. You can
pull off the search from the git-repository
git: //github.com/DarthSim/behat_websteps.git . In the repository you will find:
- Features / support / env.php file with environment already configured for testing pages;
- The file features / support / paths.php to describe the paths (details below);
- The features / support / includes.php file for connecting your classes;
- The features / support / goutte.phar file is Goutte itself;
- The features / steps / custom.php file with steps to test the pages.
Copy the files to the folder with your project, preserving the hierarchy, and run the test. And we get the error ...
Unknown path 'test page'. You can define it in [features_folder]/support/paths.php
The error speaks for itself - the test does not know what we want to check for the “test page”. The easiest way to
fix this is to set the path for the “test page” page in the
features / support / paths.php file . Suppose that we have a test page located at
tests.dev/behat.php , then we need to register in the path file
$world->paths['test page'] = "http://tests.dev/behat.php";
We start the test - great, the test is passed! I leave the analysis of the remaining steps to the reader, since they are similar to those from the webrat. Of course, the resulting functionality is much poorer than that of the webrat, but I think this is only the beginning, it will be better further.
Draw conclusions and provide links
So, it's 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 is already a high-quality, complete product. We wish the developer success in the development of his offspring and send him the rays of good. Below are some useful links:
Behat repository on githubBehat WIKIBehat APIGoutte repositorySymfony 2 API (will help with Goutte)