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