W dzisiejszym wpisie chciałbym pokazać, jak w kilku prostych krokach rozpocząć tworzenie oprogramowania w duchu BDD (Behavior-driven development). W tym celu zostaną wykorzystane frameworki Behat oraz PHPUnit. Nie będę tutaj opisywać idei BDD, chciałbym pokazać jak to przygotować środowisko w praktyce. Przykładem będzie prosty skrypt generujący ilość permutacji zbioru liczb.

Uwaga! Zakładam, że orientujesz się w jakimś stopniu czym jest Composer, PHPUnit oraz Behat.

Utwórzmy najpierw nowy projekt na dysku i dodajmy katalog na pliki źródłowe. Niech będzie to folder app. Następnie utwórzmy w głownym katalogu projektu plik composer.json:

{ "require-dev": { "behat/behat": "^3.4", "phpunit/phpunit": "^6.0" }, "autoload": { "psr-4": { "App\\": "app/" } } }

Następnie uruchamiamy w terminalu polecenie:

composer install

Mam nadzieję, że masz zainstalowanego w systemie Composera, jeżeli nie to wystarczy go pobrać ze strony getcomposer.org, zainstalować i uruchomić ponownie terminal :). Po tym zabiegu zostanie dodany do naszego projektu Behat oraz PHPUnit, a także autoloader. Teraz pora na konfigurację ścieżek dla Behata. W głównym katalogu projektu tworzymy plik behat.yaml:

default: suites: permutation: paths: [ features/app ] contexts: [ PermutationContext ]

W ten sposób "informujemy" framework, że opisy funkcjonalności będą znajdować się w folderze features/app, natomiast kontekst odpowiedzialny za ich implementację będzie znajdować się w klasie PermutationContext. Wykonujemy inicjalizację Behata:

.\vendor\bin\behat --init

Odpowiednie katalogi zostały utworzone. Następnie tworzymy scenariusz zachowania. W folderze features/app tworzymy plik permutation.feature:

# language: pl Funkcja: Zliczanie ilości permutacji zbioru liczb Scenariusz: Użytkownik zlicza ilość permutacji zbioru liczb naturalnych dodatnich Zakładając użytkownik dodaje do skryptu pięć liczb naturalnych dodatnich Kiedy użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru Wtedy użytkownik otrzymuje liczbę 120

Jak widać powyżej, funkcjonalności można opisywać w języku polskim – jest to świetna wiadomość dla naszego klienta, który może posługiwać się swoim językiem macierzystym! W kolejnym etapie wypełniamy automatycznie kontekst dla tej funkcji poleceniem:

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

Na pytanie o wybór kontekstu wpisujemy na klawiaturze klawisz 1 i zatwierdzamy operację klawiszem ENTER. Plik PermutationContext.php został zaktualizowany. Teraz już możemy spróbować uruchomić test:

.\vendor\bin\behat

Dostajemy informację zwrotną:

Funkcja: Zliczanie ilości permutacji zbioru liczb Scenariusz: Użytkownik zlicza ilość permutacji zbioru liczb naturalnych dodatnich # features\app\permutation.feature:4 Zakładając użytkownik dodaje do skryptu pięć liczb naturalnych dodatnich # PermutationContext::uzytkownikDodajeDoSkyptuPiecLiczbaNaturalnychDodatnich() TODO: write pending definition Kiedy użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru # PermutationContext::uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() Wtedy użytkownik otrzymuje liczbę 120 # PermutationContext::uzytkownikOtrzymujeLiczbe() 1 scenario (1 pending) 3 steps (1 pending, 2 skipped) 0m0.04s (8.28Mb)

Teraz pora na implementację założeń zawartych w opisie funkcji. W folderze app stwórzmy klasę Permutation.php, w której umieścimy klasę Permutation:

<?php namespace App; class Permutation { private $numbers = []; public function add(array $numbers): void { $this->numbers = array_merge($this->numbers, $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 kolejnym korku uzupełniamy test:

<?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 { private $object; /** * 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() { $this->object = new App\Permutation(); } /** * @Given użytkownik dodaje do skryptu pięć liczb naturalnych dodatnich */ public function uzytkownikDodajeDoSkyptuPiecLiczbaNaturalnychDodatnich() { $this->object->add( [ 8, 23, 22, 67, 11 ] ); } /** * @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) { $this->object->count(); } }

Uruchamiamy test i otrzymujemy informację:

Funkcja: Zliczanie ilości permutacji zbioru liczb Scenariusz: Użytkownik zlicza ilość permutacji zbioru liczb naturalnych dodatnich # features\app\permutation.feature:4 Zakładając użytkownik dodaje do skyptu pięć liczb naturalnych dodatnich # PermutationContext::uzytkownikDodajeDoSkyptuPiecLiczbaNaturalnychDodatnich() Kiedy użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru # PermutationContext::uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() TODO: write pending definition Wtedy użytkownik otrzymuje liczbę 120 # PermutationContext::uzytkownikOtrzymujeLiczbe() 1 scenario (1 pending) 3 steps (1 passed, 1 pending, 1 skipped) 0m0.04s (8.29Mb)

Nie uzupełniliśmy wszystkich kroków, dlatego ten nie występujący oraz kolejne są pomijane. Jesteśmy w tym momencie w stanie weryfikować, czy została zaimplementowana funkcja. Teraz pora na weryfikowanie jest poprawności za pomocą asercji. Rozszerzamy klasę PermutationContext i dodajemy odpowiednie asercje:

<?php use Behat\Behat\Tester\Exception\PendingException; use Behat\Behat\Context\Context; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use PHPUnit\Framework\TestCase; /** * Defines application features from the specific context. */ class PermutationContext extends TestCase implements Context { private $object; /** * 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() { $this->object = new App\Permutation(); } /** * @Given użytkownik dodaje do skyptu pięć liczb naturalnych dodatnich * @throws ReflectionException */ public function uzytkownikDodajeDoSkyptuPiecLiczbaNaturalnychDodatnich() { $this->object->add( [ 8, 23, 22, 67, 11 ] ); $reflection = new ReflectionClass('App\Permutation'); $property = $reflection->getProperty('numbers'); $property->setAccessible(true); $value = $property->getValue($this->object); $this->assertTrue(count($value) === 5); } /** * @When użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru */ public function uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() { } /** * @Then użytkownik otrzymuje liczbę :arg1 */ public function uzytkownikOtrzymujeLiczbe($arg1) { $value = $this->object->count(); $this->assertEquals($arg1, $value); } }

Teraz możemy testować także asercje dzięki PHPUnit. Uruchamiamy znowu polecenie:

.\vendor\bin\behat

Wynik:

Funkcja: Zliczanie ilości permutacji zbioru liczb Scenariusz: Użytkownik zlicza ilość permutacji zbioru liczb naturalnych dodatnich # features\app\permutation.feature:4 Zakładając użytkownik dodaje do skyptu pięć liczb naturalnych dodatnich # PermutationContext::uzytkownikDodajeDoSkyptuPiecLiczbaNaturalnychDodatnich() Kiedy użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru # PermutationContext::uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() Wtedy użytkownik otrzymuje liczbę 120 # PermutationContext::uzytkownikOtrzymujeLiczbe() 1 scenario (1 passed) 3 steps (3 passed) 0m0.10s (9.07Mb)

Jednak czy asercje działają poprawnie? Zmieniamy warunek asercji z 5 na 50 i uruchamiamy test. Otrzymujemy wynik:

Funkcja: Zliczanie ilości permutacji zbioru liczb Scenariusz: Użytkownik zlicza ilość permutacji zbioru liczb naturalnych dodatnich # features\app\permutation.feature:4 Zakładając użytkownik dodaje do skyptu pięć liczb naturalnych dodatnich # PermutationContext::uzytkownikDodajeDoSkyptuPiecLiczbaNaturalnychDodatnich() Failed asserting that false is true. Kiedy użytkownik chce otrzymać ilość permutacji wprowadzonego zbioru # PermutationContext::uzytkownikChceOtrzymacIloscPermutacjiWprowadzonegoZbioru() Wtedy użytkownik otrzymuje liczbę 120 # PermutationContext::uzytkownikOtrzymujeLiczbe() --- Failed scenarios: features\app\permutation.feature:4 1 scenario (1 failed) 3 steps (1 failed, 2 skipped) 0m0.10s (8.98Mb)

To już wszystko na dzisiaj! Mam nadzieję, że Tobie też udało się skonfigurować PHP do pracy z BDD. Pliki omawianego wyżej projektu możesz pobrać tutaj. Przed uruchomieniem projektu wymagane jest pobranie zależności za pomocą Composera.