W swoim poprzednim wpisie pokazałem podstawy konfiguracji zestawu narzędzi Behat+PHPUnit umożliwiający tworzenie scenariuszy przypadków uzycia. Konfiguracja ta pozwalała następnie na implementację tych scenariuszy oraz przetestowanie backendu. W dzisiejszym wpisie chciałbym pokazać, jak skonfigurować Behata do wykonywania testów funkcjonalnych, tak aby móc testować warstwę, którą widzi użytkownik.

Podobnie jak ostatnio, na początku musimy zainstalować w naszym systemie menedżer zależności Composer. Jeżeli jest on już zainstalowany, to świetnie! Stwórzmy zatem nowy projekt i dodajmy w nim plik composer.json z zawartością:

{ "require-dev": { "behat/behat": "^3.4", "behat/mink-selenium2-driver": "^1.3", "behat/mink-extension": "^2.3", "behat/mink": "^1.7" }, "autoload": { "psr-4": { "App\\": "app/" } } }

Następnie dodajemy do głównego katalogu folder app (na pliki źródłowe zawierające logikę) oraz instalujemy zależności poleceniem:

composer install

W tym odcinku wykonywamy analogiczny projekt skryptu obliczającego ilość permutacji zbioru liczb. W głównym katalogu projektu tworzymy plik behat.yaml z konfiguracją Behata:

default: suites: permutation: paths: [ features/app ] contexts: [ PermutationContext ] extensions: Behat\MinkExtension: base_url: http://localhost/index.php browser_name: 'chrome' sessions: default: selenium2: wd_host: 'http://localhost:4444/wd/hub' capabilities: {}

Jak zapewne widzisz, tym razem doszedł zestaw ustawień pod kluczem Behat\MinkExtension. Zawiera on wybraną przeglądarkę, zmienną zawierającą adres strony głównej aplikacji oraz adres, pod którym nasłuchiwać będzie serwer. Inicjujemy Behata:

.\vendor\bin\behat --init

W katalogu featues/app tworzymy plik permutation.feature i dodajemy zawartość:

# language: pl Funkcja: Zliczanie ilości permutacji zbioru liczb Szablon scenariusza: Użytkownik zlicza ilość permutacji zbioru liczb naturalnych dodatnich Zakładając użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru Oraz użytkownik dodaje do skryptu "<n>" liczb naturalnych dodatnich Wtedy użytkownik otrzymuje liczbę "<m>" Przykłady: | n | m | | 5 | 120 | | 4 | 24 |

Różni się on od tego ostatnio. W tym przypadku mamy szablon scenariusza, w którym znajdują się zmienne n oraz m. Następnie tworzymy dla przygotowanego scenariusza kontekst poleceniem:

.\vendor\bin\behat --dry-run --append-snippets

Został utworzony plik features/boostrap/PermutationContext.php z zawartoscią:

<?php use Behat\Behat\Tester\Exception\PendingException; use Behat\Behat\Context\Context; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; /** * Defines application features from the specific context. */ class PermutationContext implements Context { /** * Initializes context. * * Every scenario gets its own context instance. * You can also pass arbitrary arguments to the * context constructor through behat.yml. */ public function __construct() { } /** * @When użytkownik dodaje do skryptu :arg1 liczb naturalnych dodatnich */ public function uzytkownikDodajeDoSkryptuLiczbNaturalnychDodatnich($arg1) { throw new PendingException(); } /** * @When użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru */ public function uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() { throw new PendingException(); } /** * @Then użytkownik otrzymuje liczbę :arg1 */ public function uzytkownikOtrzymujeLiczbe($arg1) { throw new PendingException(); } }

Zostawmy go w takim stanie przez chwilę. Utwórzmy teraz implementację założonej na wstępie funkcjonalności. Tworzymy klasę Permutation.php w folderze app:

<?php namespace App; class Permutation { private $numbers = []; public function add(array $numbers): void { $this->numbers = array_merge($this->numbers, $numbers); } public function getDataSet(): array { return $this->numbers; } public function count() { $itemsCount = count($this->numbers); if ($itemsCount > 0) { $permutationCount = $itemsCount; while ($itemsCount-- && $itemsCount !== 0) $permutationCount *= $itemsCount; return $permutationCount; } else { return 1; } } }

W głównym folderze zróbmy skrypt wykonujący. Stwórzmy plik index.php:

<?php require './vendor/autoload.php'; session_start(); $template = file_get_contents('./views/index.tpl'); $action = $_POST['action']; $number = $_POST['permutation-number']; switch ($action) { case 'add': $instance = $_SESSION['permutation']['instance']; if (!empty($number)) { $instance->add([$number]); } $dataSet = implode(", ", $instance->getDataSet()); $count = 0; break; case 'get': $instance = $_SESSION['permutation']['instance']; $dataSet = implode(", ", $instance->getDataSet()); $count = $instance->count(); break; default: $_SESSION['permutation']['instance'] = new App\Permutation(); break; } $pageContent = str_replace( ['{$permutationNumber}', '{$permutationSet}', '{$permutationCount}'], [$number, $dataSet, $count], $template); echo $pageContent;

Skrypt jest ten trochę chaotyczny, ale myślę że prosty w zrozumieniu. Tworzymy sesję, w której trzymamy dane. Pobieramy z formularza dane i je przetwarzamy. Następnie wypełniamy danymi widok. Widok znajduje się pod ścieżką views/index.tpl, którą oczywiście musimy utworzyć. Wypełniamy plik index.tpl zawartością:

<style> input, textarea { display: block; margin-bottom: 10px; width: 300px; resize: none; } </style> <form action="index.php" method="post"> <label for="permutation-number">Liczba</label> <input id="permutation-number" name="permutation-number" type="number" value="{$permutationNumber}" /> <label for="permutation-set">Zbiór liczb</label> <textarea id="permutation-set" name="permutation-set" type="number" height="2" readonly>{$permutationSet}</textarea> <label for="permutation-count">Liczba permutacji</label> <input id="permutation-count" name="permutation-count" type="number" readonly value="{$permutationCount}" /> <button id="permutation-add" name="action" type="submit" value="add">Add number to set</button> <button id="permutation-get" name="action" type="submit" value="get">Get permutation count</button> </form>

Jak widać jest on bardzo prosty, nie zawiera nawet zestawu typowych znaczników. Dodatkowo style przechowywane są w tym samy pliku, co nie jest dobrą praktyką. Mamy już gotową aplikację, możemy ją przetestować w przeglądarce. Teraz pora na uzupełnienie kontekstu dla scenariusza. Plik PermutationContext.php uzupełniamy metodami:

<?php use Behat\Mink\WebAssert; use Behat\MinkExtension\Context\MinkContext; class PermutationContext extends MinkContext { /** * @When użytkownik dodaje do skryptu :arg1 liczb naturalnych dodatnich */ public function uzytkownikDodajeDoSkryptuLiczbNaturalnychDodatnich($arg1) { $input = $this->getSession()->getPage()->findById('permutation-number'); $button = $this->getSession()->getPage()->findById('permutation-add'); for ($i = 1; $i <= $arg1; $i++) { $input->setValue($i); $button->click(); } } /** * @When użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru */ public function uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() { $url = $this->getMinkParameter('base_url'); $this->getSession()->visit($url); } /** * @Then użytkownik otrzymuje liczbę :arg1 * @throws \Behat\Mink\Exception\ExpectationException */ public function uzytkownikOtrzymujeLiczbe($arg1) { $button = $this->getSession()->getPage()->findById('permutation-get'); $button->click(); (new WebAssert($this->getSession()))->fieldValueEquals('permutation-count', $arg1); } }

Główną różnicą jest wymiana implementacji interfejsu Context na rozszerzenie klasy MinkContext. Następnie zostały uzupełnione kroki. Jesteśmy już prawie gotowi do uruchomienia testów! Pobieramy teraz Selenium w wersji 2, np. za pomocą polecenia w konsoli:

php --run "copy('https://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar', './selenium-server-standalone-2.53.1.jar')"

Startujemy serwer Selenium, wymagane jest wcześniejsze zainstalowanie maszyny wirtualnej Java. Selenium uruchamiamy poleceniem:

java -jar selenium-server-standalone-2.53.1.jar

Serwer wystartuje i zablokuje terminal do czasu jego wyłączenia. Dlatego teraz uruchamiamy nową sesję terminala i wpisujemy komendę:

.\vendor\bin\behat

Uruchomi się przeglądarka Google Chrome i testy automatyczne się wykonają. Na koniec w konsoli pojawi się informacja o przebiegu testu. To wszystko na dzisiaj. Jak zawsze udostępniam pliki z gotowym projektem: pliki źródłowe. Pozdrawiam!