Share
Embed
BDD в PHP вместе с Behat и Mink
Презентация с ZFConf11 про Behavior Driven Development в PHP проектах с использованием библиотек Behat и Mink
Konstantin Kudryashov
September 26, 2011
Transcript
120 slides
-
Я кто такой senior from-birth web developer в International speaker
Разработчик Behat, Mink Разработчик jade.php Разработчик capifony Core-contributor Symfony2 framework Разработчик плагинов symfony и Symfony2 [email protected] http://github.com/everzet http://card.everzet.com everzet @ -
BDD, Symfony2 эксперты Активные контрибуторы в open-source проекты Консультанты, аудиторы,
тренеры http://knplabs.com -
Test-Driven Development Мы на самом деле говорим о тестах??? Но
каким образом тестировать то, чего еще нет? -
Тест-кейсы должы составлять предложения BDDбыл создан как набор конвенций поверх
TDD testFindsCustomerById() testFailsForDuplicateCustomers() -
testFindsCustomerById() testFailsForDuplicateCustomers() Тест-кейсы должны начинаться со слова “should” shouldFindCustomerById() shouldFailForDuplicateCustomers()
BDDбыл создан как набор конвенций поверх TDD Тест-кейсы должы составлять предложения -
shouldFindCustomerById() shouldFailForDuplicateCustomers() Класс тест-кейсов должен представлять из себя существительное для
кейсов class CustomerTableTest extends \PHPUnitTestCase { /** * @Test */ shouldFindCustomerById() ... } BDDбыл создан как набор конвенций поверх TDD Тест-кейсы должны начинаться со слова “should” Тест-кейсы должы составлять предложения testFindsCustomerById() testFailsForDuplicateCustomers() -
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 RSpec by Dave Astels JSpec by TJ Holowaychuk Fabulous
by Alex Rudakov -
RSpec # bowling_spec.rb require '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 end end -
RSpec # bowling_spec.rb require '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 end end Пишем СПЕЦИФИКАЦИЮ, а не UnitTEST -
UnitTest TDD Spec BDD Сначала дизайн Dan North BDD История
Тесты вперед Автоматизация тестов -
photo by dsearls photo by Horia Varlan для девелоперов СЛОВАРЬ
для аналитиков для тестеров -
photo by dsearls photo by Horia Varlan для заказчиков СЛОВАРЬ
для девелоперов для аналитиков для тестеров -
photo by dsearls photo by Horia Varlan 1 СЛОВАРЬ для
заказчиков для девелоперов для аналитиков для тестеров -
photo by dsearls photo by Horia Varlan тестеры аналитики девелоперы
заказчики ИСКОРЕНИТ множество проблем ДИЗАЙНА и КОММУНИКАЦИЙ 1 СЛОВАРЬ -
In order to [A] As a [B] I need [C]
Наратив: -
A ⎯ добавочное знач. (профит) функционала B ⎯ профитирующая персона
(роль) C ⎯ функционал Чтобы [A] В качестве [B] Мне нужно [C] Наратив: -
⎯ Сила данной конструкции в том, что она требует определения
профита от функционала еще до его реализации © Dan North A ⎯ добавочное знач. (профит) функционала B ⎯ профитирующая персона (роль) C ⎯ функционал Чтобы [A] В качестве [B] Мне нужно [C] Наратив: -
Поведение 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 North
BDD История Сначала дизайн Тесты вперед Автоматизация тестов -
Сначала анализ Сначала дизайн UnitTest TDD Spec BDD Scenario BDD
Dan North BDD История Тесты вперед Автоматизация тестов + -
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. feature 2. scenario 3. step ... ... 2. scenario
3. step ... ... 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 feature tree -
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 -
Etant donné some initial context (the givens) Lorsque an event
occurs Alors ensure some outcomes In order to ... As a ... I need ... Etant donné some initial context (the givens) Lorsque an event occurs Alors 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 occurs Aye ensure some outcomes In order to ... As a ... I need ... Let go and haul some initial context (the givens) Blimey! an event occurs Aye 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 occurs Aye ensure some outcomes Let go and haul some initial context (the givens) Blimey! an event occurs Aye 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. feature 2. scenario Допустим я нахожусь в директории “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 """ -
<?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(); 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);
} ); using PHPUnit <?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); } ) ; ОПРЕДЕЛЕНИЯШАГОВ -
$ 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 -
Установка 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 ... Getting Started with Zend Framework By Rob Allen, www.akrabat.com Document Revision 1.7.6 Copyright © 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 Функционал: Альбомы