Behat – wprowadzenie do testowania BDD

Czym jest Behat

Behat jest frameworkiem Behavior-driven development (w skrócie BDD) dla języka PHP, który pomaga w implementacji i testowaniu założeń biznesowych. Oficjalna dokumentacja Behata znajduje się na stronie https://docs.behat.org/.

Instalacja

Instalacja behata sprowadza się do zainstalowania paczki przy użyciu composera:

composer require --dev behat/behat

Inicjacja

Następnie należy zainicjować behata, poleceniem:

$ vendor/bin/behat --init

Polecenie to stworzy katalog features i wypisze informacje na temat jego zawartości:

+d features - place your *.feature files here
+d features/bootstrap - place your context classes here
+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here

Podstawymi elementami Behata są pliki .feature oraz klasy kontekstu, powyższy rezultat pokazuje gdzie należy umieścić każde z nich.

Pliki .feature

Plik .feature jest to plik w formacie zwanym Gherkin lub Cucumber, opisujący dane rozwiązanie. Format opisu scenariuszy powinien być zrozumiały dla osób nietechnicznych. Na początku zawsze znajduje się opis danego rozwiązania, np.:

Feature: In order to buy products as a customer
  I have to put products into my basket
  and have enough money to pay for them.

Mamy tutaj założenie biznesowe opisane w sposób czytelny dla człowieka i zrozumiały dla osób nietechnicznych. Teraz musimy opisać scenariusze testowe. Pierwszym przypadkiem testowym może być dodanie pojedynczego produktu do koszyka.

Scenario: Buying a single product under €10
  Given there is a "Elephant T-Shirt", which costs €5
  When I add the "Elephant T-Shirt" to the basket
  Then I should have 1 product in the basket
  And the overall basket price should be €5

Jak widać w powyższym przykładzie, scenariusz opisuje:
– założenia (Given)
– wykonywane akcje (When I add)
– spodziewane rezultaty (Then I should)

Jeśli masz doświadczenie z PHPUnit być może zauważysz analogie do przygotowywania danych testowych i tworzenia asercji.

Nasz pierwszy plik zapiszmy w ścieżce features/basket.feature, jego zawartość wygląda następująco:

Feature: In order to buy products as a customer
  I have to put products into my basket
  and have enough money to pay for them.

Scenario: Buying a single product under €10
  Given there is a "Elephant T-Shirt", which costs €5
  When I add the "Elephant T-Shirt" to the basket
  Then I should have 1 product in the basket
  And the overall basket price should be €5

Testy uruchamiamy poleceniem ./vendor/bin/behat, możemy również podać nazwę pliku np. /vendor/bin/behat features/basket.feature a nawet linię z konkretnym scenariuszem, który chcemy przetestować: /vendor/bin/behat features/basket.feature:123 – numer linii po dwukropku musi wskazywać linię zawierającą „Scenario” lub „Scenario Outline”.

Po uruchomieniu testów otrzymasz następujące informacje:

1 scenariusz (1 niezdefiniowany)
4 kroki (4 niezdefiniowane)
0m0.01s (8.06Mb)

 >> default suite has undefined steps. Please choose the context to generate snippets:

  [0] None
  [1] FeatureContext
 > 

Jak widać behat nie był w stanie przetestować czterech kroków w jednym scenariuszu, ze względu na brak definicji dla tych kroków.

Warto w tym miejscu zauważyć że rezultaty testów będą odpowiednio kolorowane w zależności od tego czy dany krok został wykonany poprawnie, niepoprawnie czy nie został zdefiniowany.

Kontekst

Odpowiedzmy na zadane przez skrypt pytanie wybierając [1], w rezultacie otrzymamy informację o kodzie jaki musimy umieścić w klasie FeatureContext:

--- FeatureContext zawiera brakujące kroki. Utwórz je korzystając z tych fragmentów kodu:

    /**
     * @Given there is a :arg1, which costs €:arg2
     */
    public function thereIsAWhichCostsEur($arg1, $arg2)
    {
        throw new PendingException();
    }

    /**
     * @When I add the :arg1 to the basket
     */
    public function iAddTheToTheBasket($arg1)
    {
        throw new PendingException();
    }

    /**
     * @Then I should have :arg1 product in the basket
     */
    public function iShouldHaveProductInTheBasket($arg1)
    {
        throw new PendingException();
    }

    /**
     * @Then the overall basket price should be €:arg1
     */
    public function theOverallBasketPriceShouldBeEur($arg1)
    {
        throw new PendingException();
    }

Jak widać kod ten zawiera odpowiednie metody do wszystkich kroków z pliku features/basket.feature, to co musimy teraz zrobić to zaimplementować zachowanie tych metod, nic nie stoi na przeszkodzie by przy okazji poprawić nazwy metod oraz ich argumentów, natomiast jeśli chcielibyśmy zmienić definicję kroku w PHPDoc (@Given there is a :arg1, which costs €:arg2), musielibyśmy odpowiednio zmienić dany krok we wszystkich testach.

Przy implementacji kontekstu możemy zastosować podejście Test-driven-development i najpierw stworzyć testy, uruchomić je z błędnym rezultatem i wówczas zaimplementować właściwy kod, ale nie jest to wymagane, jeśli wolisz najpierw pisać właściwy kod albo po prostu jest on już napisany, możesz nie stosować TDD.

Nasza klasa FeatureContext może mieć następującą postać:

class FeatureContext implements Context
{
    /** @var ProductCollection */
    private $products;
    
    /** @var Basket */
    private $basket;
    
    /**
     * Initializes context.
     */
    public function __construct()
    {
    	$this->products = new ProductCollection();
    	$this->basket = new Basket();
    }
    
    /**
     * @Given there is a :arg1, which costs €:arg2
     */
    public function thereIsAProductWhichCostsEur(string $name, float $price)
    {
        $this->products->add(
            new Product($name, $price)
        );
    }

    /**
     * @When I add the :arg1 to the basket
     */
    public function iAddProductToTheBasket(string $productName)
    {
        $this->basket->put(
            $this->products->findByName($productName)
        );
    }

    /**
     * @Then I should have :arg1 product in the basket
     */
    public function iShouldHaveProductInTheBasket(string $productName)
    {
        PHPUnit_Framework_Assert::assertConains(
            $productName,
            $this->basket->getProductNames()
        );
    }

    /**
     * @Then the overall basket price should be €:arg1
     */
    public function theOverallBasketPriceShouldBeEur(float $expectedBasketPrice)
    {
        PHPUnit_Framework_Assert::assertEquals(
            $expectedBasketPrice,
            $this->basket->getOverallPrice()
        );
    }
}

Behat sam w sobie nie dostarcza metod do sprawdzania asercji, dlatego musimy użyć np. method dostarczanych przez PHPUnit jak w powyższym przykładzie, można oczywiście napisać również własne metody sprawdzające.

W zależności od stopnia rozwoju kodu aplikacji nasz test zwróci odpowiednie błędy lub zakończy się sukcesem. Kiedy już nasze scenariusze zostaną w pełni pokryte działającym kodem, możemy napisać kolejne scenariusze wewnątrz pliku features/basket.feature lub utworzyć kolejne pliki .feature opisujące inne funkcjonalności aplikacji.

Background

Kolejną rzeczą, którą można opisać w plikach .feature jest tło (background), które definiuje stan aplikacji przed uruchomieniem każdego ze scenariuszy z danego pliku. Przykładowo, w naszym teście możemy zdefiniować stan towaru na półkach.

Background:
  Given there is a product "Elephant T-Shirt"
  And there is 12 pieces of "Elephant T-Shirt" on the shelf

Opis tła definiujemy w taki sam sposób jak scenariusze, przy użyciu kroków obsługiwanych przez kod kontekstu (np. Given there is).

Scenario outline

W przypadku, gdy ten sam scenariusz chcemy wykonać wiele razy ale z różnymi danymi testowymi, możemy zdefiniować zarys scenariusza (Scenario Outline) oraz przykłady (Examples).

Scenario Outline: Reducing number of items on the shelf
Given there are  t-shirts
When I put  t-shirts into my basket
Then I there is  t-shirts left on the shelf

Examples:
| start | buy_amount | left |
| 12    | 5          | 7    |
| 20    | 3          | 17   |
| 11    | 11         | 0    |

Po uruchomieniu powyższego przykładu, test zostanie uruchomiony 3 razy, z różnymi wartościami wejściowymi i różnymi oczekiwanymi rezultatami.

Podsumowanie

Behat jest bardzo prostym w użyciu narzędziem oferującym ogrom możliwości definiowania opisów funkcjonalności, które stanowią nie tylko scenariusze do testów automatycznych ale również dokumentację aplikacji czytelną nie tylko dla programistów.

Przedstawione w powyższym artykule informacje są punktem wyjścia do rozpoczęcia przygody z Behatem i BDD. Behat oczywiście ma dużo więcej możliwości, które można zgłębić w kolejnych etapach pracy z nim, niemniej samo zrozumienie jak należy tworzyć pliki .feature i metody kontekstowe oraz jak uruchamiać testy jest wystarczające, żeby zacząć opisywanie i testowanie aplikacji.

Udostępnij

Skomentuj