Behat est un framework de test Behavior-Driven Development pour PHP.
Les tests écrits avec Gherkin permettent une meilleure communication entre les développeurs et le business, et une meilleure fiabilité de l’application.
Behat est développé en utilisant une grande partie des composants Symfony 2.
If you are a stakeholder, this is your proof that developers understand exactly how you want this feature to work. If you are a developer, this is your proof that the stakeholder expects you to implement this feature exactly in the way you’re planning to implement it.
Les scénarios des tests s’écrivent dans ce qui s’appelle des features.
Une feature = un fichier xxxxx.feature
Un scénario contient habituellement les étapes suivantes : Given / When / Then / And (alias de Then)
Une traduction des mots clefs est disponible en français mais il préférable de s’en tenir à l’anglais (des soucis peuvent survenir avec certains caractères), si besoin :
sudo apt install cucumber
cucumber --i18n fr
Exemple de feature
Feature: Product basket
In order to buy products
As a customer
I need to be able to put interesting products into a basket
Scenario: Buying a single product under 10€
Given there is a "Sith Lord Lightsaber", which costs 5€
When I add the "Sith Lord Lightsaber" to the basket
Then I should have 1 product in the basket
And the overall basket price should be 9€
Il est possible d’utiliser des table-like ou paragraphe en utilisant les PyString """
.
Exemple avec un tableau :
Feature: Get users
In order to check users
As an admin
I must have a list of all of them
Scenario: List users
Given there are users:
| username | password | email |
| foo | 123456 | foo@bar.com |
| bar | 22@222 | bar@foo.com |
When I search the user "foo"
Then I should find it and get the email "foo@bar.com"
Tags
Il est possible de renseigner un tag au niveau de la feature, cela permet de préciser des contextes seulement pour certains tags, de lancer les tests sur une liste de tags etc.
@backoffice
Feature: Get users
In order to check users
...
Installation
composer require --dev behat/behat
vendor/bin/behat --init
La deuxième commande créer les fichiers d’init de Behat.
Coloration
Behat est nativement supporté par PHPStorm, pour Sublime Text il existe un package afin d’avoir la coloration syntaxique.
Les valeurs en jaunes sont récupérées dans le code afin d’être testées.
Lancer les tests
Une fois les features créées dans le dossier features/
, on peut lancer les tests :
vendor/bin/behat # toutes les features
vendor/bin/behat features/basket.feature # une feature en particulier
vendor/bin/behat --tags backoffice # toutes les features qui ont le tag backoffice
Pour le moment pas grand chose à tester, afin de créer automatiquement les fonctions et leurs arguments, voici la commande magique proposée :
vendor/bin/behat --dry-run --append-snippets
Behat créer les fonctions comme ceci :
/**
* @Given there is a :arg1, which costs :arg2€
*/
public function thereIsAWhichCostsEur($arg1, $arg2)
{
}
/**
* @When I add the :arg1 to the basket
*/
public function iAddTheToTheBasket($arg1)
{
}
/**
* @Then I should have :arg1 product in the basket
*/
public function iShouldHaveProductInTheBasket($arg1)
{
}
/**
* @Then the overall basket price should be :arg1€
*/
public function theOverallBasketPriceShouldBeEur($arg1)
{
}
Il ne reste plus qu’à remplir le contenu des fonctions avec du code métier, et d’y ajouter quelques assertions PHPUnit.
Exemple simple :
use Behat\Behat\Context\Context;
use PHPUnit\Framework\Assert;
class BasketContext implements Context
{
const COST_SHIPPING = 4;
private $products = [];
private $basket = [];
public function __construct()
{
}
/**
* @Given there is a :arg1, which costs :arg2€
*/
public function thereIsAWhichCostsEur($arg1, $arg2)
{
$product = new \stdClass();
$product->name = $arg1;
$product->price = $arg2;
$this->products[$product->name] = $product;
}
/**
* @When I add the :arg1 to the basket
*/
public function iAddTheToTheBasket($arg1)
{
if (\array_key_exists($arg1, $this->products)) {
$this->basket[] = $this->products[$arg1];
}
}
/**
* @Then I should have :arg1 product in the basket
*/
public function iShouldHaveProductInTheBasket($arg1)
{
Assert::assertGreaterThan(0, \count($this->basket));
}
/**
* @Then the overall basket price should be :arg1€
*/
public function theOverallBasketPriceShouldBeEur($arg1)
{
$amount = self::COST_SHIPPING;
foreach ($this->basket as $product) {
$amount += $product->price;
}
Assert::assertEquals($arg1, $amount);
}
}
Fichier Bootstrap
Le Bootstrap de Behat peut être surchargé.
Par défaut il est créé ici : features/bootstrap/FeatureContext.php
Il est possible d’ajouter des instructions à des moments précis du déroulement des tests : BeforeScenario, BeforeFeature, Then, When etc.
Exemple :
/** @BeforeFeature */
public static function prepareForTheFeature()
{
// clean database or do other preparation stuff
}
/**
* @BeforeScenario @backoffice
*/
public function createSchema()
{
echo 'je passe ici';
}
/** @Given we have some context */
public function prepareContext()
{
// do something
}
/** @When event occurs */
public function doSomeAction()
{
// do something
}
/** @Then something should be done */
public function checkOutcomes()
{
// do something
}
A noter que la fonction createSchema
ne s’exécutera que pour les features contenant le tag « backoffice »
Affichage des résultats
Pour sortir le résumé sur la sortie standard (console) et le résultat XML Junit dans un fichier /behat-output/default.xml
vendor/bin/behat -f progress -o std -f junit -o behat-output --strict
Tests navigateurs
Afin de réaliser des tests Selenium depuis Behat, il faut installer plusieurs outils, voici un combo possible : JAR Selenium standalone + chromedriver + Mink
Mink est un outil open source, browser emulator écrit en PHP, l’installation se faire via composer (Cf. next step).
Il faut ensuite récupérer le JAR Selenium sur le site officiel, puis le driver chrome (lien à modifier en fonction de la version en cours).
Quelques lignes de commande afin d’utiliser chromedriver :
unzip ~/Downloads/chromedriver_linux64.zip -d ~/Downloads
chmod +x ~/Downloads/chromedriver
sudo mv -f ~/Downloads/chromedriver /usr/local/share/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/bin/chromedriver
Si tout s’est bien passé il est maintenant possible de lancer Selenium de cette façon :
java -Dwebdriver.chromedriver=/usr/bin/chromedriver -jar ~/Downloads/selenium-server-standalone-3.12.0.jar
Voici un exemple de feature qui va réaliser les tests navigateurs, et qui fonctionne avec les directives natives Behat/Mink et donc ne nécessite pas de classe PHP !
@selenium
Feature: User profile
I can see my name on my profile
Scenario: Dashboard page shows my name
Given I am on "/sign-in"
Then I should see "Sign in"
When I fill in "email" with "foo@bar.com"
And I fill in "password" with "123456"
And I press "submit"
Then I should see "Name: Foo"
Intégration à Symfony
Installer les packages nécessaires :
composer require --dev behat/symfony2-extension
composer require --dev behat/mink
composer require --dev behat/mink-extension
composer require --dev behat/mink-browserkit-driver
composer require --dev behat/mink-selenium2-driver
Modifier le fichier de conf behat.yml
généré à la racine du projet :
default:
suites:
default:
type: symfony_bundle
bundle: AppBundle
contexts:
- FeatureContext:
mySymfonyParam: "%%db_host%%"
doctrine: "@doctrine"
- AppBundle\Features\Context\BasketContext:
basket: "@app.basket"
- AppBundle\Features\Context\UsersContext:
extensions:
Behat\Symfony2Extension:
kernel:
env: "dev"
debug: "true"
Behat\MinkExtension:
base_url: http://myapp.my.company/web/
browser_name: 'chrome'
sessions:
default:
selenium2: ~
Dans l’exemple ci-dessus on voit qu’il est possible de passer en argument des paramètres et des services Symfony aux classes Behat.
Intégration à Jenkins
Il est nécessaire d’abord de configurer le lancement de Selenium pour l’utilisateur Linux qui va lancer les jobs (utilisateur jenkins en l’occurrence).
Cela peut se faire via une CRON qui exécutera le script suivant :
#!/bin/bash
Xvfb -ac :99 -screen 0 1280x1024x16 &
export DISPLAY=:99
java -Dwebdriver.chromedriver=/usr/bin/chromedriver -jar ~/behat/selenium-server-standalone-3.12.0.jar
Et pour le crontab :
@reboot ~/behat/script-demarrage.sh
Pour vérifier que Selenium se lance bien après reboot, exécuter la commande suivante (le port 4444 doit être visible) :
netstat -tln
Ensuite il suffit d’ajouter l’instruction Behat au Pipeline Jenkins :
stage('Tests Behat') {
def path = pwd()
sh "${path}/vendor/bin/behat -f progress -o std --strict"
}
Si les tests échouent, le job retournera un fail.