BDD в php
May 10, 2015
BDD в php
Яктотакой
everzetsenior from-birth web
developer в
@
Яктотакой
senior from-birth web developer в
International speakerРазработчик Behat, MinkРазработчик jade.phpРазработчик capifonyCore-contributor Symfony2 frameworkРазработчик плагинов symfony и Symfony2
http://github.com/everzethttp://card.everzet.com
everzet@
BDD, Symfony2 эксперты
Активные контрибуторы в open-source проекты
Консультанты, аудиторы, тренеры
http://knplabs.com
Историятестирования
UnitTest
История
Автом
атизаци
я тестов
UnitTest TDD
Тесты
вперед
История
Автом
атизаци
я тестов
UnitTest TDD
Dan N
orth
BDD
История
Тесты
вперед
Автом
атизаци
я тестов
BDD ⎯ эволюция TDD
- Эволюция-хренолюция... Что не так с TDD?
Test-Driven Development
Test-Driven Development
Мы на самом деле говорим о тестах???Но каким образом тестировать то, чего еще нет?
Test-Driven Development
дизайнеНа самом деле, мы говорим о
Test-Driven DevelopmentBehavior
© 2003, Dan North
BDD был создан как наборконвенций поверхTDD
Тест-кейсы должы составлять предложения
BDD был создан как наборконвенций поверхTDD
testFindsCustomerById()testFailsForDuplicateCustomers()
testFindsCustomerById()testFailsForDuplicateCustomers()
Тест-кейсы должны начинаться со слова “should”shouldFindCustomerById()shouldFailForDuplicateCustomers()
BDD был создан как наборконвенций поверхTDD
Тест-кейсы должы составлять предложения
shouldFindCustomerById()shouldFailForDuplicateCustomers()
Класс тест-кейсов должен представлять из себя существительное для кейсов
class CustomerTableTest extends \PHPUnitTestCase{ /** * @Test */ shouldFindCustomerById() ...}
BDD был создан как наборконвенций поверхTDD
Тест-кейсы должны начинаться со слова “should”
Тест-кейсы должы составлять предложенияtestFindsCustomerById()testFailsForDuplicateCustomers()
АССЕРШЕНЫтоже TEST-ориентированы
assertEquals($expected, $actual)
assertGreaterThan($expected, $actual)
assertInstanceOf($class, $actual)
АССЕРШЕНЫтоже TEST-ориентированы
ТЕСТируем
assertEquals($expected, $actual)
assertGreaterThan($expected, $actual)
assertInstanceOf($class, $actual)
$actual should be Equals to $expected
$actual should be GreaterThan $expected
$actual should be InstanceOf $class
Описываем
АССЕРШЕНЫтоже TEST-ориентированы
ТЕСТируем
SpecификационныеBDD Фрэймворки
*Spec
RSpec by Dave Astels
*Spec
RSpec by Dave Astels
JSpec by TJ Holowaychuk
*Spec
RSpec by Dave Astels
JSpec by TJ Holowaychuk
Fabulous by Alex Rudakov
RSpec
# bowling_spec.rbrequire 'bowling'
describe Bowling, "#score" do it "returns 0 for all gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should == 0 endend
RSpec
# bowling_spec.rbrequire 'bowling'
describe Bowling, "#score" do it "returns 0 for all gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should == 0 endend
Пишем СПЕЦИФИКАЦИЮ, а не UnitTEST
UnitTest TDD
Spec BDDСначала дизайн
Dan N
orth
BDD
История
Тесты
вперед
Автом
атизаци
я тестов
BDDСЦЕНАРНЫЙ
photo by dsearls
photo by dsearlsphoto by Horia Varlan
СЛОВАРЬ
photo by dsearlsphoto by Horia Varlan
для те
стеро
в
СЛОВАРЬ
photo by dsearlsphoto by Horia Varlan
для аналитиков
СЛОВАРЬ
для те
стеро
в
photo by dsearlsphoto by Horia Varlan
для де
вело
перо
в СЛОВАРЬ для аналитиков
для те
стеро
в
photo by dsearlsphoto by Horia Varlan
для заказчиков
СЛОВАРЬ
для де
вело
перо
в для аналитиков
для те
стеро
в
photo by dsearlsphoto by Horia Varlan
1СЛОВАРЬ
для заказчиков
для де
вело
перо
в для аналитиков
для те
стеро
в
photo by dsearlsphoto by Horia Varlan
тестер
ы
аналитики
девелоперызаказчики
ИСКОРЕНИТ множество проблемДИЗАЙНА и КОММУНИКАЦИЙ
1СЛОВАРЬ
КОММУНИКАЦИИ
photo by joshfassbind.com
In order to [A]As a [B]I need [C]
Наратив:
Чтобы [A]В качестве [B]Мне нужно [C]
Наратив:
A ⎯ добавочное знач. (профит) функционала B ⎯ профитирующая персона (роль)C ⎯ функционал
Чтобы [A]В качестве [B]Мне нужно [C]
Наратив:
⎯ Сила данной конструкции в том, что она требует определения профита от функционала еще до его реализации
© Dan North
A ⎯ добавочное знач. (профит) функционала B ⎯ профитирующая персона (роль)C ⎯ функционал
Чтобы [A]В качестве [B]Мне нужно [C]
Наратив:
Поведение story ⎯ это ее приемочный критерий!⎯ если система удовлетворяет все приемочные критерии, то она работает верно; если не выполняет - неверно.
In order to ...As a ...I need ...
Story:
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
In order to ...As a ...I need ...
Story:
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
Story:In order to ...As a ...I need ...
Scenario 1:
Scenario 2:
Story:
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
In order to ...As a ...I need ...
UnitTest TDD
Spec BDD
Scenario BDD
Сначал
а анализ
Dan N
orth
BDD
История
Сначала дизайн
Тесты
вперед
Автом
атизаци
я тестов
Сначал
а анализ
Сначала дизайн
UnitTest TDD
Spec BDD
Scenario BDD
Dan N
orth
BDD
История
Тесты
вперед
Автом
атизаци
я тестов
+
GHERKINDSL
photo by isobel.gordon
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
In order to ...As a ...I need ...
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
Scenario 1:
Scenario 2:
Story:
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
In order to ...As a ...I need ...
Given some initial context (the givens),When an event occurs,Then ensure some outcomes.
Scenario: 1st scenario title
Scenario: 2nd scenario title
Feature: Feature description
1. feature2.
sce
nario
3. step......
2. s
cena
rio
3. step......
Given some initial context (the givens)When an event occursThen ensure some outcomes
In order to ...As a ...I need ...
Given some initial context (the givens)When an event occursThen ensure some outcomes
Scenario: 1st scenario title
Scenario: 2nd scenario title
Feature: Feature descriptionfeature tree
Given some initial context (the givens)When an event occursThen ensure some outcomes
In order to ...As a ...I need ...
Given some initial context (the givens)When an event occursThen ensure some outcomes
Scenario: 1st scenario title
Scenario: 2nd scenario title
Feature: Feature description
Etant donné some initial context (the givens)Lorsque an event occursAlors ensure some outcomes
In order to ...As a ...I need ...
Etant donné some initial context (the givens)Lorsque an event occursAlors ensure some outcomes
Scénario: 1st scenario title
Scénario: 2nd scenario title
Fonctionnalité: Feature description# language: fr
ならば some initial context (the givens)しかし an event occurs前提 ensure some outcomes
In order to ...As a ...I need ...
ならば some initial context (the givens)しかし an event occurs前提 ensure some outcomes
シナリオ: 1st scenario title
シナリオ: 2nd scenario title
フィーチャ: Feature description# language: ja
Допустим some initial context (the givens)Когда an event occursТо ensure some outcomes
In order to ...As a ...I need ...
Допустим some initial context (the givens)Когда an event occursТо ensure some outcomes
Сценарий: 1st scenario title
Сценарий: 2nd scenario title
Функционал: Feature description# language: ru
Let go and haul some initial context (the givens)Blimey! an event occursAye ensure some outcomes
In order to ...As a ...I need ...
Let go and haul some initial context (the givens)Blimey! an event occursAye ensure some outcomes
Heave to: 1st scenario title
Heave to: 2nd scenario title
Ahoy matey!: Feature description# language: en-pirate
Let go and haul some initial context (the givens)Blimey! an event occursAye ensure some outcomes
Let go and haul some initial context (the givens)Blimey! an event occursAye ensure some outcomes
Heave to:
Heave to:
Ahoy matey!:# language: en-pirate
Приемочные критериидолжны быть исполняемы!
Установка
1. Добавляем pear-channel:$ pear channel-discover pear.behat.org
2. Ставим:$ pear install behat/behat
3. Инициализируем:$ cd path/to/project && behat --init
Установка
1. Добавляем pear-channel:$ pear channel-discover pear.behat.org
2. Ставим:$ pear install behat/behat
3. Инициализируем:$ cd path/to/project && behat --init
+d features - place your *.feature files here
+d features/steps - place step definition files here
+f features/steps/steps.php - place some step definitions in this file
+d features/support - place support scripts and static files here
+f features/support/bootstrap.php - place bootstrap scripts in this file
+f features/support/env.php - place environment initialization scripts in this file
# language: ruФункционал: Утилита lsЧтобы узнать содержимое директорииКак пользователь UNIXЯ должен иметь утилиту листинга директорий
Сценарий: 2 файла в директории
Чтобы узнать содержимое директорииКак пользователь UNIXЯ должен иметь утилиту листинга директорий
# language: ruФункционал: Утилита ls
Допустим я нахожусь в директории “test1”Если я исполню “ls”То я должен увидеть:
Сценарий: 2 файла в директории
Чтобы узнать содержимое директорииКак пользователь UNIXЯ должен иметь утилиту листинга директорий
# language: ruФункционал: Утилита ls
""" file_one.txt file_foo.txt """
1. feature2.
sce
nario
Допустим я нахожусь в директории “test1”Если я исполню “ls”То я должен увидеть:
Сценарий: 2 файла в директории
Чтобы узнать содержимое директорииКак пользователь UNIXЯ должен иметь утилиту листинга директорий
# language: ruФункционал: Утилита ls
""" file_one.txt file_foo.txt """
Допустим я нахожусь в директории “test1”Если я исполню “ls”То я должен увидеть:
Сценарий: 2 файла в директории
Чтобы узнать содержимое директорииКак пользователь UNIXЯ должен иметь утилиту листинга директорий
# language: ruФункционал: Утилита ls
""" file_one.txt file_foo.txt """
Допустим я нахожусь в директории “test1”
ОПРЕДЕЛЕНИЯШАГОВ
<?php
Допустим('/^я нахожусь в директории “(.*)”$/');
ОПРЕДЕЛЕНИЯШАГОВ
Допустим я нахожусь в директории “test1”
<?php
Допустим('/^я нахожусь в директории “(.*)”$/', function() { throw new \Behat\Behat\Exception\Pending(); });
ОПРЕДЕЛЕНИЯШАГОВ
Допустим я нахожусь в директории “test1”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function() { throw new \Behat\Behat\Exception\Pending(); });
ОПРЕДЕЛЕНИЯШАГОВ
Допустим я нахожусь в директории “test1”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function() { throw new \Behat\Behat\Exception\Pending(); });
???
ОПРЕДЕЛЕНИЯШАГОВ
Допустим я нахожусь в директории “test1”
ТИПЫРЕЗУЛЬТАТОВШАГОВ
ТИПЫРЕЗУЛЬТАТОВШАГОВ
1. Pending шаг ⎯ который throw new \Behat\Behat\Exception\Pending();
1. Pending шаг ⎯ который throw new \Behat\Behat\Exception\Pending();
2. Undefined шаг ⎯ у которого нет (не найдено) определений
ТИПЫРЕЗУЛЬТАТОВШАГОВ
1. Pending шаг ⎯ который throw new \Behat\Behat\Exception\Pending();
2. Undefined шаг ⎯ у которого нет (не найдено) определений
3. Ambiguous шаг ⎯ который подпадает под несколько определений
ТИПЫРЕЗУЛЬТАТОВШАГОВ
1. Pending шаг ⎯ который throw new \Behat\Behat\Exception\Pending();
2. Undefined шаг ⎯ у которого нет (не найдено) определений
3. Ambiguous шаг ⎯ который подпадает под несколько определений
4. Failed шаг ⎯ который throw \Exception();
ТИПЫРЕЗУЛЬТАТОВШАГОВ
1. Pending шаг ⎯ который throw new \Behat\Behat\Exception\Pending();
2. Undefined шаг ⎯ у которого нет (не найдено) определений
3. Ambiguous шаг ⎯ который подпадает под несколько определений
4. Failed шаг ⎯ который throw \Exception();
5. Skipped шаг ⎯ который идет следом за pending/undefined/failed в сценарии
ТИПЫРЕЗУЛЬТАТОВШАГОВ
1. Pending шаг ⎯ который throw new \Behat\Behat\Exception\Pending();
2. Undefined шаг ⎯ у которого нет (не найдено) определений
3. Ambiguous шаг ⎯ который подпадает под несколько определений
4. Failed шаг ⎯ который throw \Exception();
5. Skipped шаг ⎯ который идет следом за pending/undefined/failed в сценарии
6. Passed шаг ⎯ который не кидает эксепшенов
ТИПЫРЕЗУЛЬТАТОВШАГОВ
ОПРЕДЕЛЕНИЯШАГОВ
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function() { throw new \Behat\Behat\Exception\Pending(); });
Допустим я нахожусь в директории “test1”
ОПРЕДЕЛЕНИЯШАГОВ
Если я исполню “ls”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function() { throw new \Behat\Behat\Exception\Pending(); });
Допустим я нахожусь в директории “test1”
<?php
$steps->Если('/^я исполню “(.*)”$/', function($dollars) { throw new \Behat\Behat\Exception\Pending(); });
ОПРЕДЕЛЕНИЯШАГОВ
Если я исполню “ls”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function() { throw new \Behat\Behat\Exception\Pending(); });
Допустим я нахожусь в директории “test1”
<?php
$steps->Если('/^я исполню “(.*)”$/', function($command) { // $command === “ls” });
ОПРЕДЕЛЕНИЯШАГОВ
Если я исполню “ls”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function($dir) { // $dir === “test1” });
Допустим я нахожусь в директории “test1”
<?php
$steps->Если('/^я исполню “(.*)”$/', function($command) { exec($command, $output); $output = trim(implode(“\n”, $output)); });
ОПРЕДЕЛЕНИЯШАГОВ
Если я исполню “ls”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function($dir) { chdir('fixtures/' . $dir); });
Допустим я нахожусь в директории “test1”
<?php
$steps->Если('/^я исполню “(.*)”$/', function($world, $command) { exec($command, $output); $world->output = trim(implode(“\n”, $output)); });
ОПРЕДЕЛЕНИЯШАГОВ
Если я исполню “ls”
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function($world, $dir) { chdir('fixtures/' . $dir); });
Допустим я нахожусь в директории “test1”
ПРОВЕРЯЕМРЕЗУЛЬТАТЫ
То я должен увидеть:
<?php
$steps->То('/^я должен увидеть:$/', function($world, $string) { if ($world->output !== (string) $string) { throw new \Exception('Неверный вывод'); } });
ПРОВЕРЯЕМРЕЗУЛЬТАТЫ
То я должен увидеть:
<?php
$steps->То('/^я должен увидеть:$/', function($world, $string) { assertEquals((string) $string, $world->output); });
usingPHPUnit
<?php
$steps->То('/^я должен увидеть:$/', function($world, $string) { if ($world->output !== (string) $string) { throw new \Exception('Неверный вывод'); } });
ПРОВЕРЯЕМРЕЗУЛЬТАТЫ
То я должен увидеть:
То я должен увидеть: ( )
<?php
$steps->Допустим('/^я нахожусь в директории “(.*)”$/', function($world, $dir) { chdir('fixtures/' . $dir); });
$steps->Если('/^я исполню “(.*)”$/', function($world, $command) { exec($command, $output); $world->output = trim(implode(“\n”, $output)); });
$steps->То('/^я должен увидеть:$/', function($world, $string) { assertEquals((string) $string, $world->output); });
ОПРЕДЕЛЕНИЯШАГОВ
<?php
$steps->
Допустим('/^я нахожусь в директории “(.*)”$/', function($world, $dir) { chdir('fixtures/' . $dir); } )->
Если('/^я исполню “(.*)”$/', function($world, $command) { exec($command, $output); $world->output = trim(implode(“\n”, $output)); } )->
То('/^я должен увидеть:$/', function($world, $string) { assertEquals((string) $string, $world->output); } );
ОПРЕДЕЛЕНИЯШАГОВ
1. Описываем поведение
Workflow
$ behat features/
1. Описываем поведение2. Проверяем поведение ( )
Workflow
$ behat features/
1. Описываем поведение2. Проверяем поведение ( )3. Реализуем поведение
Workflow
$ behat features/
1. Описываем поведение2. Проверяем поведение ( )
4. Проверяем поведение3. Реализуем поведение
Workflow
$ behat features/
1. Описываем поведение2. Проверяем поведение ( )
4. Проверяем поведение3. Реализуем поведение
Workflow
3.1. Пишем спеки
$ behat features/
1. Описываем поведение2. Проверяем поведение ( )
3.4. Прогоняем спеки3.3. Пишем код3.2. Прогоняем спеки
4. Проверяем поведение
3. Реализуем поведение:
Workflow
3.1. Пишем спеки
$ behat features/
1. Описываем поведение
3.4. Прогоняем спеки3.3. Пишем код3.2. Прогоняем спеки
3. Реализуем поведение:2. Проверяем поведение ( )
4. Проверяем поведение
Workflow
Описание web-приложений
M!"#
Установка
1. Добавляем pear-channel:$ pear channel-discover pear.behat.org
2. Ставим:$ pear install behat/mink-beta
<?php
use Behat\Mink\Mink, Behat\Mink\Session, Behat\Mink\Driver\GoutteDriver, Behat\Mink\Driver\SahiDriver;
// инициализируем Mink и сессии$mink = new Mink();$mink->registerSession('goutte', new Session(new GoutteDriver($startUrl)));$mink->registerSession('javascript',, new Session(new SahiDriver($startUrl, 'firefox')));
// выполняем действия в стандартном драйвере$mink->getSession('goutte')->getPage()->clickLink('Downloads');echo $mink->getSession('goutte')->getPage()->getContent();
// выполняем действия в javascript (Sahi) сессии$mink->getSession('javascript')->getPage()->clickLink('Downloads');echo $mink->getSession('javascript')->getPage()->getContent();
Новый проект
1. Создаем каркас проекта:$ cd path/to/project && zf ...
Новый проект
1. Создаем каркас проекта:$ cd path/to/project && zf ...
Getting Started with Zend Framework
By Rob Allen, www.akrabat.comDocument Revision 1.7.6Copyright © 2006, 2010
Новый проект
1. Создаем каркас проекта:$ cd path/to/project && zf ...
2. Инициализируем B$%&':$ behat --init
Новый проект
3. Знакомим B$%&' с M!"#:$ vim behat.yml
# behat.yml
default:
environment:
parameters:
start_url: http://tutorial.zf.dev/
imports:
- mink/behat.yml
$ vim features/support/bootstrap.php
<?php
// features/support/bootstrap.php
require_once 'PHPUnit/Autoload.php';
require_once 'PHPUnit/Framework/Assert/Functions.php';
require_once 'mink/autoload.php';
$ behat --steps --lang ru
Чтобы иметь представление об исполнителях Как каталогизатор Я должен уметь управлять коллекцией альбомов
Сценарий: Добавление альбомаДопустим я на странице /index/addЕсли я ввожу "Pendulum" в поле "Artist"И я ввожу "In Silico" в поле "Title"И нажимаю "Add"То я должен видеть "In Cilico"И я должен видеть "Edit"
# language: ruФункционал: Альбомы
Чтобы иметь представление об исполнителях Как каталогизатор Я должен уметь управлять коллекцией альбомов
Сценарий: Добавление альбомаДопустим я на странице /index/addЕсли я ввожу "Pendulum" в поле "Artist"И я ввожу "In Silico" в поле "Title"И нажимаю "Add"То я должен видеть "In Cilico"И я должен видеть "Edit"
# language: ruФункционал: Альбомы
Чтобы иметь представление об исполнителях Как каталогизатор Я должен уметь управлять коллекцией альбомов
Сценарий: Добавление альбомаДопустим в базе нет альбомовИ я на странице /index/addЕсли я ввожу "Pendulum" в поле "Artist"И я ввожу "In Silico" в поле "Title"И нажимаю "Add"То я должен видеть "In Silico"И я должен видеть "Edit"
# language: ruФункционал: Альбомы
<?php# features/support/bootstrap.php
// Конфигурация и инициализация тестовой среды ZF
<?php# features/steps/steps.php$steps->Допустим('/^в базе нет альбомов$/', function($world) { $albums = new Application_Model_DbTable_Albums(); $albums->delete(1); });
Чтобы иметь представление об исполнителях Как каталогизатор Я должен уметь управлять коллекцией альбомов
Сценарий: Добавление альбомаДопустим в базе нет альбомовИ я на странице /index/addЕсли я ввожу "Pendulum" в поле "Artist"И я ввожу "In Silico" в поле "Title"И нажимаю "Add"То я должен видеть "In Silico"И я должен видеть "Edit"
# language: ruФункционал: Альбомы
Чтобы иметь представление об исполнителях Как каталогизатор Я должен уметь управлять коллекцией альбомов
@javascriptСценарий: Добавление альбомаДопустим в базе нет альбомовИ я на странице /index/addЕсли я ввожу "Pendulum" в поле "Artist"И я ввожу "In Silico" в поле "Title"И нажимаю "Add"То я должен видеть "In Silico"И я должен видеть "Edit"
# language: ruФункционал: Альбомы
http://knplabs.com/trainings
http://github.com/behathttp://groups.google.com/behat
Вопросы?
http://github.com/behat
http://knplabs.com/trainingshttp://groups.google.com/behat