BDD i Behat

BDD

Bug Driven Development

Behaviour Driven Development

Problem

Kako je klijent opisao proizvod.

Kako je vođa projekta razumeo.

Kako ga je programer implementirao.

Šta su beta testeri dobili.

Šta je klijentu zaista bilo potrebno.

Rešenje

Hajde da svi koristimo istu terminologiju

  • Menadžeri
  • Programeri
  • Biznis analitičari
  • Vlasnici projekta
  • Testeri
  • ...

...za planiranje, implementaciju i testiranje funkcionalnosti

  • Menadžeri
  • Programeri
  • Biznis analitičari
  • Vlasnici projekta
  • Testeri
  • ...

...sa fokusom na ponašanje

  • Menadžeri
  • Programeri
  • Biznis analitičari
  • Vlasnici projekta
  • Testeri
  • ...

StoryBDD nam pomaže da razvojni tim razume biznis logiku na istom nivou kao klijent.

I čak podiže nivo znanja klijenta o sopstvenom biznisu.

Ali kako?

Primoravajući vas da odgovorite na tri jednostavna pitanja.

  • Ko?

  • Šta?

  • Kako?

  • Ko ima koristi? 

  • Upotrebna vrednost.

  • Opis funkcionalnosti.

Gherkin

Gherkin

Struktuirani jezik kojim se opisuje funkcionalnost.

Feature: News admin panel
    In order to maintain a list of news
    As a site administrator
    I need to be able to edit news

Feature: News admin panel
    In order to maintain a list of news
    As a site administrator
    I need to be able to edit news

Feature: (naziv funkcionalnosti)
    In order to (upotrebna vrednost)
    As a (osoba koja koristi)
    I need (opis funkcionalnosti)

Feature: News admin panel
    In order to maintain a list of news
    As a site administrator
    I need to be able to edit news

 

    Scenario: Add new article
         Given I am on the "/admin/news" page
         When I click "New Article"
            And I fill in "Title" with "Learn BDD"
            And I press "Save"
          Then I should see "A new article was added"

Feature: News admin panel
    In order to maintain a list of news
    As a site administrator
    I need to be able to edit news

 

    Scenario: Add new article
         Given I am on the "/admin/news" page
         When I click "New Article"
            And I fill in "Title" with "Learn BDD"
            And I press "Save"

          Then I should see "A new article was added"

Feature: News admin panel
    In order to maintain a list of news
    As a site administrator
    I need to be able to edit news

 

    Scenario: (naziv scenarija)
         Given (podesi stanje sistema)
         When (akcija osobe/uloge)
            And (se može dodati za više akcija...)
            And (...Given/When/Then linije)
          Then (proveri stanje sistema)

Gherkin

Konzinstentan način da se opišu funkcionalnosti i scenariji korišćenja istih.

I to je nešto.

Behat

Zamislite da možemo da izvršavamo Gherkin rečenice kao funkcionalne testove.

Behat

Mapira svaki korak (Gherkin rečenicu) u PHP poziv.

Instalacija Behat-a

{
    "require": {
        "behat/behat": "~3.0"
    }
}

Konzolna aplikacija

$ ./bin/behat --help
Usage:
 behat [-s|--suite="..."] [-f|--format="..."] [-o|--out="..."] [--format-settings="..."] [--init] [--lang="..."] [--name="..."] [--tags="..."] [--role="..."] [--story-syntax] [-d|--definitions="..."] [--append-snippets] [--no-snippets] [--strict] [--rerun] [--stop-on-failure] [--dry-run] [paths]

Arguments:
 paths                 Optional path(s) to execute. Could be:
                       - a Symfony2 bundle path (@BundleName/)
                       - a dir (features/)
                       - a feature (*.feature)
                       - a scenario at specific line (*.feature:10).
                       - all scenarios at or after a specific line (*.feature:10-*).
                       - all scenarios at a line within a specific range (*.feature:10-20).
                       - a scenarios list file (*.scenarios).

Options:
 --suite (-s)          Only execute a specific suite.
 --format (-f)         How to format tests output. pretty is default.
                       Available formats are:
                       - progress: Prints one character per step.
                       - pretty: Prints the feature as is.
                       You can use multiple formats at the same time. (multiple values allowed)
 --out (-o)            Write format output to a file/directory instead of
                       STDOUT (output_path). You can also provide different
                       outputs to multiple formats. (multiple values allowed)
 --format-settings     Set formatters parameters using json object.
                       Keys are parameter names, values are values. (multiple values allowed)
 --init                Initialize all registered test suites.
 --lang                Print output in particular language.
 --name                Only executeCall the feature elements which match part
                       of the given name or regex. (multiple values allowed)
 --tags                Only executeCall the features or scenarios with tags
                       matching tag filter expression. (multiple values allowed)
 --role                Only executeCall the features with actor role matching
                       a wildcard.
 --story-syntax        Print *.feature example.
                       Use --lang to see specific language.
 --definitions (-d)    Print all available step definitions:
                       - use --definitions l to just list definition expressions.
                       - use --definitions i to show definitions with extended info.
                       - use --definitions 'needle' to find specific definitions.
                       Use --lang to see definitions in specific language.
 --append-snippets     Appends snippets for undefined steps into main context.
 --no-snippets         Do not print snippets for undefined steps after stats.
 --strict              Passes only if all tests are explicitly passing.
 --rerun               Re-run scenarios that failed during last execution.
 --stop-on-failure     Stop processing on first failed scenario.
 --dry-run             Invokes formatters without executing the tests and hooks.
 --profile (-p)        Specify config profile to use.
 --config (-c)         Specify config file to use.
 --verbose (-v)        Increase verbosity of exceptions.
 --help (-h)           Display this help message.
 --config-reference    Display the configuration reference.
 --version (-V)        Display this behat version.
 --no-interaction (-n) Do not ask any interactive question.
 --colors              Force ANSI color in the output. By default color support is
                       guessed based on your platform and the output if not specified.
 --no-colors           Force no ANSI color in the output.

Inicijalizacija

$ php bin/behat --init
+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

Behatu je potrebno:

  • *.feature fajlovi za izvšenje.
  • FeatureContext.php koji sadrži metode za izvršenje.
  • Opcioni behat.yml konfiguracioni fajl.

Linus Torvalds nije razvijao Linux koristeći BDD

Ali mi možemo!

Feature: ls
    In order to see the directory structure
    As a UNIX user
    I need to be able to list directory's contents

features/ls.feature

Scenario

Ako imamo dva fajla u direktorijumu, i pokrenemo ls komandu - trebalo bi da ih vidimo u listi.

Scenario: List 2 files in a directory
    Given I have a file named "foo"
       And I have a file named "bar"
    When I run "ls"
     Then I should see "foo" in the output
       And I should see "bar" in the output

features/ls.feature

$ php bin/behat

$ php bin/behat 
Feature: ls
  In order to see the directory structure
  As a UNIX user
  I need to be able to list directory's contents

  Scenario: List 2 files in a directory   # features/ls.feature:6
    Given I have a file named "foo"
    And I have a file named "bar"
    When I run "ls"
    Then I should see "foo" in the output
    And I should see "bar" in the output

1 scenario (1 undefined)
5 steps (5 undefined)
0m0.01s (10.10Mb)

--- FeatureContext has missing steps. Define them with these snippets:

    /**
     * @Given I have a file named :arg1
     */
    public function iHaveAFileNamed($arg1)
    {
        throw new PendingException();
    }

    /**
     * @When I run :arg1
     */
    public function iRun($arg1)
    {
        throw new PendingException();
    }

    /**
     * @Then I should see :arg1 in the output
     */
    public function iShouldSeeInTheOutput($arg1)
    {
        throw new PendingException();
    }

Implementiraj generisane metode

/**
 * @Given I have a file named :file
 */
public function iHaveAFileNamed($file)
{
    touch($file);
}

/**
 * @When I run :command
 */
public function iRun($command)
{
    $this->output = shell_exec($command);
}

$ php bin/behat

$ php bin/behat
Feature: ls
  In order to see the directory structure
  As a UNIX user
  I need to be able to list directory's contents

  Scenario: List 2 files in a directory   # features/ls.feature:6
    Given I have a file named "foo"       # FeatureContext::iHaveAFileNamed()
    And I have a file named "bar"         # FeatureContext::iHaveAFileNamed()
    When I run "ls"                       # FeatureContext::iRun()
    Then I should see "foo" in the output # FeatureContext::iShouldSeeInTheOutput()
    And I should see "bar" in the output  # FeatureContext::iShouldSeeInTheOutput()

1 scenario (1 passed)
5 steps (5 passed)
0m0.02s (10.22Mb)

Možemo da mapiramo svaki korak u PHP poziv sa Behatom.

Nije loše

Mink

Mink

Mink je open sors pregledač 

kontroler/emulator za web aplikacije.

Zajednički API

  • Goutte
  • Selenium
  • Zombie.js
  • ...

Korišćenje Mink-a

use Behat\Mink\Session;
use Behat\Mink\Driver\GoutteDriver;

$session = new Session(new GoutteDriver());

$session->visit($startUrl);
$session->getPage()->findLink('Downloads')->click();

echo 'Status: '.$session->getPage()->getStatusCode();
echo 'Content: '.$session->getPage()->getContent();

Mink unutar FeatureContext

Mink Extension

Jednostavno korišćenje Mink-a unutar FeatureContext klase.

Ažuriranje zavisnosti

{
    "require": {
        "behat/behat": "~3.0",
        "behat/mink-extension": "~2.0@dev",
        "behat/mink-goutte-driver": "~1.0",
        "behat/mink-selenium2-driver": "~1.1"
    }
}

Mink unutar FeatureContext klase

namespace Hypebeast\Bundle\WebBundle\Behat;

use Behat\MinkExtension\Context\RawMinkContext;

class FeatureContext extends RawMinkContext
{
    public function doSomething()
    {
        $this->getSession()->visit('https://github.com/');
    }
}

FeatureContext extends MinkContext

namespace Hypebeast\Bundle\WebBundle\Behat;

use Behat\MinkExtension\Context\RawMinkContext;

class FeatureContext extends MinkContext
{
    public function doSomething()
    {
        $this->visit('/foo');
        $this->selectOption('Country', 'Poland');
        $this->pressButton('Save');

        $this->assertPageContainsText('Country changed.');
    }
}

Nakon dodavanja MinkContext

$ php bin/behat -dl
default | Given /^(?:|I )am on (?:|the )homepage$/
default |  When /^(?:|I )go to (?:|the )homepage$/
default | Given /^(?:|I )am on "(?P<page>[^"]+)"$/
default |  When /^(?:|I )go to "(?P<page>[^"]+)"$/
default |  When /^(?:|I )reload the page$/
default |  When /^(?:|I )move backward one page$/
default |  When /^(?:|I )move forward one page$/
default |  When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with:$/
default |  When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )fill in the following:$/
default |  When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/
default |  When /^(?:|I )attach the file "(?P<path>[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should be on "(?P<page>[^"]+)"$/
default |  Then /^(?:|I )should be on (?:|the )homepage$/
default |  Then /^the (?i)url(?-i) should match (?P<pattern>"(?:[^"]|\\")*")$/
default |  Then /^the response status code should be (?P<code>\d+)$/
default |  Then /^the response status code should not be (?P<code>\d+)$/
default |  Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/
default |  Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/
default |  Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
default |  Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
default |  Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^the "(?P<element>[^"]*)" element should not contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/
default |  Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/
default |  Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/
default |  Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" (?:is|should be) checked$/
default |  Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" should (?:be unchecked|not be checked)$/
default |  Then /^the checkbox "(?P<checkbox>(?:[^"]|\\")*)" is (?:unchecked|not checked)$/
default |  Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
default |  Then /^print current URL$/
default |  Then /^print last response$/
default |  Then /^show last response$/

Što znači

Možemo pisati funkcionalne testove bez potrebe za pisanjem i jedne linije PHP koda!

Feature: Search
    In order to find articles
    As a website visitor
    I need to be able to search for articles

    Scenario: Searching for an article that exists
        Given I am on the homepage
         When I fill in "search" with "BDD"
          And I press "go"
          And I follow "Behavior-driven development"
         Then I should see "History"
          And I should see "Principles of BDD"
          And I should see "Story versus specification"

BDD i Behat

By Saša Stamenković

BDD i Behat

  • 2,144