BEHAVIOR
           DRIVEN
           DEVELOPMENT
           ДЛЯ PHP ПРОЕКТОВ


@everzet
КТО Я
                symfony-разработчик
                С 2007 года
                СОЗДАТЕЛЬ
                BEHAT
                ВЕДУЩИЙ РАЗРАБОТЧИК

               DEV.BY
ПРОГРАММИСТ
С РОЖДЕНИЯ http://everzet.com
РАЗРАБОТКА
INSIDE-OUT
Тесты соединений
                                        Тесты двигателя




                              Тесты колес




                                            UNIT
Тесты электрики




                                            ТЕСТЫ
TDD


             +                =      ?
                                  качественный
unit-тесты       разработка       продукт
TDD


             +                =
                                  качественный-
unit-тесты       разработка       продукт
UNIT-ТЕСТЫ
ОТДЕЛЬНЫХ МОДУЛЕЙ
НЕ ГАРАНТИРУЮТ КАЧЕСТВО
РАБОТЫ СИСТЕМЫ В ЦЕЛОМ
РАЗРАБОТКА
OUTSIDE-IN
ВХОДНЫЕ/ВЫХОДНЫЕ

ДАННЫЕ




                   выходные
 входные           данные
 данные
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
PHPSpec
<?php
require_once 'bowling.php'

class DescribeIndexController
    extends PHPSpec_Context_Zend
{
  public function itShouldDisplayHelloWorld()
  {
    $this->get(‘index’);
    $this->
      response()->
      should->
      match(‘/Hello World/’);
  }
}
Symfony Functional Test

<?php

$browser->get(‘/job/new’)->
  with(‘request’)->begin()->
    isParameter(‘module’, ‘job’)->
    isParameter(‘action’, ‘new’)->
  end()->

  click(‘Preview   your job’, array(‘job’ => array(
    ‘company’ =>   ‘Sensio Labs’,
    ‘url‘     =>   ‘http://sensio.com’,
    ‘logo‘    =>   ‘/uploads/jobs/sensio-labs.gif’,
  ));
BDD


                 unit-тесты

            +(      +         )=
                                   качественный
ПОВЕДЕНИЕ
                                   продукт
                 разработка
ТЕХНИКА
КОММУНИКАЦИЙ
ИДЕЯ
 # 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
ИДЕЯ
 # bowling_spec.rb




            NO!
 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
ИДЕЯ
 Хочу онлайн-боулинг
              с блэкджеком и...
 и он будет
              ПЛАТНЫМ
КОММУНИКАЦИИ

 Хочу кастомный
    Porsche
                    # 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




                                                              разработка




                  написание спек
   идея
ФУНКЦИОНАЛ


Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario: Add two numbers
    Given I have entered 10 into the calculator
    And I have entered 5 into the calculator
    When I press ‘plus’
    Then the result should be 15
ФУНКЦИОНАЛ


Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario: Add two numbers
    Given I have entered 10 into the calculator
    And I have entered 5 into the calculator
    When I press ‘plus’
    Then the result should be 15
СЦЕНАРИИ


Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario: Add two numbers
    Given I have entered 10 into the calculator
    And I have entered 5 into the calculator
    When I press ‘plus’
    Then the result should be 15
ШАГИ


Feature: Addition
  In order to avoid silly mistakes
  As a math idiot
  I want to be told the sum of two numbers

  Scenario: Add two numbers
    Given I have entered 10 into the calculator
    And I have entered 5 into the calculator
    When I press ‘plus’
    Then the result should be 15
БИБЛИОТЕКИ



ASSERTIONS   Test::Unit, RSpec

BROWSER      Webrat, Capybara, Selenium


RUNNER       Autotest, RStakeout, Watchr
ОПРЕДЕЛЕНИЯ

require 'calculator'

Given /I have entered (d+) into the calculator/ do
|n|
  @calc.push n.to_i
end

When /I press (w+)/ do |op|
  @result = @calc.send op
end

Then /the resoulr should be (.*)/ do |result|
  @result.should == result.to_f
end
ОПРЕДЕЛЕНИЯ

require 'calculator'

Given /I have entered (d+) into the calculator/ do
|n|
                ПИШУТСЯ на

               Ruby
  @calc.push n.to_i
end

When /I press (w+)/ do |op|
  @result = @calc.send op
end

Then /the resoulr should be (.*)/ do |result|
  @result.should == result.to_f
end
МИНУСЫ



 1. Не все PHP-разработчики знают/хотят знать Ruby
 2. Сложность при описании входных условий
 3. Невозможность использования PHP библиотек
 4. Скорость работы кросс-языкового решения
1. Написан с нуля на PHP5.3
2. Написан с применением Symfony Components
3. Старается быть Cucumber’ом с входными/выходными данными
4. Полностью нативное решение на PHP
5. Столь же быстр, что и Cucumber (проверено)
6. Поддерживает полную и17ю
7. Полностью расширяем и настраиваем
8. Поддерживает различные типы лоадеров
ФУНКЦИОНАЛ


Feature: Serve coffee
  In order to earn money
  Customers should be able to
  buy coffee at all times

  Scenario: Buy last coffee
    Given there are 1 coffees left in the machine
    And I have deposited 1$
    When I press the coffee button
    Then I should be served a coffee
ОПРЕДЕЛЕНИЯ


<?php

$steps->Then(‘/^it should (fail|pass)$/’,
   function($world, $status) {
     if (‘fail’ === $success) {
       assertNotEquals(0, $world->return);
     } else {
       assertEquals(0, $world->return);
     }
   }
);
ОПРЕДЕЛЕНИЯ


<?php
ТИП ОПРЕДЕЛЕНИЯ
$steps->Then(‘/^it should (fail|pass)$/’,
   function($world, $status) {
     if (‘fail’ === $success) {
       assertNotEquals(0, $world->return);
     } else {
       assertEquals(0, $world->return);
     }
   }
);
ОПРЕДЕЛЕНИЯ


<?php
                  РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ
$steps->Then(‘/^it should (fail|pass)$/’,
   function($world, $status) {
     if (‘fail’ === $success) {
       assertNotEquals(0, $world->return);
     } else {
       assertEquals(0, $world->return);
     }
   }
);
ОПРЕДЕЛЕНИЯ


<?php

$steps->Then(‘/^it should (fail|pass)$/’,
   function($world, $status) {
     if (‘fail’ === $success) {
       assertNotEquals(0, $world->return);
     } else {                                CALLBACK
       assertEquals(0, $world->return);
     }
   }
);
ИСПОЛНЕНИЕ
Feature: Behat Console Runner
  Scenario: Run feature from CLI
    Given I have default Behat configuration
    When I call ‘behat -f progress’
    Then it should pass


<?php

$steps->Then(‘/^it should (fail|pass)$/’,
   function($world, $status) {
     ...
   }
);
ИСПОЛНЕНИЕ
Feature: Behat Console Runner
  Scenario: Run feature from CLI
    Given I have default Behat configuration
    When I call ‘behat -f progress’
    Then it should pass


<?php

$steps->Then(‘/^it should (fail|pass)$/’,
   function($world, $status) {
     // $status == ‘pass’
   }
);
СТАТУСЫ
<?php

$steps->Then(‘/^it should pass$/’, function($world) {
  return true;
});

$steps->Then(‘/^it passes$/’, function($world) {
  return false;
});

$steps->Then(‘/^it should fail$/’, function($world) {
  throw new Exception();
});

$steps->Then(‘/^it should pend$/’, function($world) {
  throw new EverzetBehatExceptionPending();
});
СТАТУСЫ
<?php

$steps->Then(‘/^it should pass$/’, function($world) {
  ...
});

$steps->Then(‘/^it should pass$/’, function($world) {
  // Will throw exception on definitions read
});

$steps->Then(‘/^it’s good$/’, function($world) {
  ...
});

$steps->Then(‘/^it’s w+$/’, function($world) {
  // Will throw exception on ‘it should pass call’
});
ПРИМЕР
ИСПОЛЬЗОВАНИЯ
УСТАНОВКА




$> pear channel-discover pear.everzet.com
$> pear install everzet/behat-beta
HOW MUCH IS THE FISH?

<?php # ./user.php

class User
{
  public function __construct($username, $age = 1)
  {
  }

    public function getName() {}

    public function getAge() {}
}
FEATURE

# ./features/user.feature
# language: ru

Функционал: Базовый Пользователь
  Чтобы работать с пользователями
  Как разработчик сайта
  Я хочу иметь доступ к
  пользовательской модели

  Сценарий: Создание пользователя
    Допустим у нас нет пользователей
    Если мы добавим пользователя ‘everzet’
    То у нас должно быть 1 пользователей
    И имя у первого пользователя ‘everzet’
GET SNIPPETS
ОПРЕДЕЛЕНИЯ
<?php # ./features/steps/user_steps.php

require_once ‘PHPUnit/Autoload.php’;
require_once ‘PHPUnit/Framework/Assert/Functions.php’;
require_once __DIR__ . ‘/../../user.php’;

$steps->

Допустим(‘/^у нас нет пользователей$/’, function($world) {
     $world->users = array();
})->

Если(‘/^мы добавим пользователя ’([^’]+)’$/’, function($world, $username) {
     $world->users[] = new User($username);
})->

To(‘/^у нас должно быть (d+) пользователей$/’, function($world, $count) {
     assertEquals($count, count($world->users));
})->

То(‘/^имя у первого пользователя ’([^’]+)’$/’, function($world, $username) {
    assertEquals($username, $world->users[0]->getName());
});
WATCH IT FAILS
РЕАЛИЗАЦИЯ
<?php # ./user.php

class User
{
  protected $name, $age;

    public function __construct($username, $age = 1)
    {
      $this->name = $username;
      $this->age = $age;
    }

    public function getName() { return $this->name; }

    public function getAge() { return $this->age; }
}
WATCH IT PASSES
TODO




1. CommonWebSteps
2. Annotated class step definitions and hooks
3. JUnit formatter
4. PDF/HTML formatter
5. Selenium integration
ССЫЛКИ
         ОФФИЦИАЛЬНЫЙ САЙТ
         http://everzet.com/Behat




         ОФФИЦИАЛЬНЫЙ РЕПОЗИТОРИЙ
         http://github.com/everzet/Behat




         ОФФИЦИАЛЬНАЯ GOOGLE GROUP
         http://groups.google.com/group/behat
ВОПРОСЫ?
CREDITS




http://www.flickr.com/photos/jasmic/279741827
http://www.flickr.com/photos/mulmatsherm/2312688473
http://www.flickr.com/photos/joshfassbind/4584323789
http://www.flickr.com/photos/bearuk/564788081
http://www.flickr.com/photos/jasmincormier/3511034253
http://www.flickr.com/photos/chuqui/4552338602

BDD для PHP проектов

  • 1.
    BEHAVIOR DRIVEN DEVELOPMENT ДЛЯ PHP ПРОЕКТОВ @everzet
  • 2.
    КТО Я symfony-разработчик С 2007 года СОЗДАТЕЛЬ BEHAT ВЕДУЩИЙ РАЗРАБОТЧИК DEV.BY ПРОГРАММИСТ С РОЖДЕНИЯ http://everzet.com
  • 3.
  • 4.
    Тесты соединений Тесты двигателя Тесты колес UNIT Тесты электрики ТЕСТЫ
  • 5.
    TDD + = ? качественный unit-тесты разработка продукт
  • 6.
    TDD + = качественный- unit-тесты разработка продукт
  • 7.
    UNIT-ТЕСТЫ ОТДЕЛЬНЫХ МОДУЛЕЙ НЕ ГАРАНТИРУЮТКАЧЕСТВО РАБОТЫ СИСТЕМЫ В ЦЕЛОМ
  • 8.
  • 9.
    ВХОДНЫЕ/ВЫХОДНЫЕ ДАННЫЕ выходные входные данные данные
  • 10.
    RSpec # bowling_spec.rb require 'bowling' describeBowling, "#score" do it "returns 0 for all gutter game" do bowling = Bowling.new 20.times { bowling.hit(0) } bowling.score.should == 0 end end
  • 11.
    PHPSpec <?php require_once 'bowling.php' class DescribeIndexController extends PHPSpec_Context_Zend { public function itShouldDisplayHelloWorld() { $this->get(‘index’); $this-> response()-> should-> match(‘/Hello World/’); } }
  • 12.
    Symfony Functional Test <?php $browser->get(‘/job/new’)-> with(‘request’)->begin()-> isParameter(‘module’, ‘job’)-> isParameter(‘action’, ‘new’)-> end()-> click(‘Preview your job’, array(‘job’ => array( ‘company’ => ‘Sensio Labs’, ‘url‘ => ‘http://sensio.com’, ‘logo‘ => ‘/uploads/jobs/sensio-labs.gif’, ));
  • 13.
    BDD unit-тесты +( + )= качественный ПОВЕДЕНИЕ продукт разработка
  • 14.
  • 15.
    ИДЕЯ # 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
  • 16.
    ИДЕЯ # bowling_spec.rb NO! 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
  • 17.
    ИДЕЯ Хочу онлайн-боулинг с блэкджеком и... и он будет ПЛАТНЫМ
  • 18.
    КОММУНИКАЦИИ Хочу кастомный Porsche # 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 разработка написание спек идея
  • 20.
    ФУНКЦИОНАЛ Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario: Add two numbers Given I have entered 10 into the calculator And I have entered 5 into the calculator When I press ‘plus’ Then the result should be 15
  • 21.
    ФУНКЦИОНАЛ Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario: Add two numbers Given I have entered 10 into the calculator And I have entered 5 into the calculator When I press ‘plus’ Then the result should be 15
  • 22.
    СЦЕНАРИИ Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario: Add two numbers Given I have entered 10 into the calculator And I have entered 5 into the calculator When I press ‘plus’ Then the result should be 15
  • 23.
    ШАГИ Feature: Addition In order to avoid silly mistakes As a math idiot I want to be told the sum of two numbers Scenario: Add two numbers Given I have entered 10 into the calculator And I have entered 5 into the calculator When I press ‘plus’ Then the result should be 15
  • 24.
    БИБЛИОТЕКИ ASSERTIONS Test::Unit, RSpec BROWSER Webrat, Capybara, Selenium RUNNER Autotest, RStakeout, Watchr
  • 25.
    ОПРЕДЕЛЕНИЯ require 'calculator' Given /Ihave entered (d+) into the calculator/ do |n| @calc.push n.to_i end When /I press (w+)/ do |op| @result = @calc.send op end Then /the resoulr should be (.*)/ do |result| @result.should == result.to_f end
  • 26.
    ОПРЕДЕЛЕНИЯ require 'calculator' Given /Ihave entered (d+) into the calculator/ do |n| ПИШУТСЯ на Ruby @calc.push n.to_i end When /I press (w+)/ do |op| @result = @calc.send op end Then /the resoulr should be (.*)/ do |result| @result.should == result.to_f end
  • 27.
    МИНУСЫ 1. Невсе PHP-разработчики знают/хотят знать Ruby 2. Сложность при описании входных условий 3. Невозможность использования PHP библиотек 4. Скорость работы кросс-языкового решения
  • 29.
    1. Написан снуля на PHP5.3 2. Написан с применением Symfony Components 3. Старается быть Cucumber’ом с входными/выходными данными 4. Полностью нативное решение на PHP 5. Столь же быстр, что и Cucumber (проверено) 6. Поддерживает полную и17ю 7. Полностью расширяем и настраиваем 8. Поддерживает различные типы лоадеров
  • 30.
    ФУНКЦИОНАЛ Feature: Serve coffee In order to earn money Customers should be able to buy coffee at all times Scenario: Buy last coffee Given there are 1 coffees left in the machine And I have deposited 1$ When I press the coffee button Then I should be served a coffee
  • 31.
    ОПРЕДЕЛЕНИЯ <?php $steps->Then(‘/^it should (fail|pass)$/’, function($world, $status) { if (‘fail’ === $success) { assertNotEquals(0, $world->return); } else { assertEquals(0, $world->return); } } );
  • 32.
    ОПРЕДЕЛЕНИЯ <?php ТИП ОПРЕДЕЛЕНИЯ $steps->Then(‘/^it should(fail|pass)$/’, function($world, $status) { if (‘fail’ === $success) { assertNotEquals(0, $world->return); } else { assertEquals(0, $world->return); } } );
  • 33.
    ОПРЕДЕЛЕНИЯ <?php РЕГУЛЯРНОЕ ВЫРАЖЕНИЕ $steps->Then(‘/^it should (fail|pass)$/’, function($world, $status) { if (‘fail’ === $success) { assertNotEquals(0, $world->return); } else { assertEquals(0, $world->return); } } );
  • 34.
    ОПРЕДЕЛЕНИЯ <?php $steps->Then(‘/^it should (fail|pass)$/’, function($world, $status) { if (‘fail’ === $success) { assertNotEquals(0, $world->return); } else { CALLBACK assertEquals(0, $world->return); } } );
  • 35.
    ИСПОЛНЕНИЕ Feature: Behat ConsoleRunner Scenario: Run feature from CLI Given I have default Behat configuration When I call ‘behat -f progress’ Then it should pass <?php $steps->Then(‘/^it should (fail|pass)$/’, function($world, $status) { ... } );
  • 36.
    ИСПОЛНЕНИЕ Feature: Behat ConsoleRunner Scenario: Run feature from CLI Given I have default Behat configuration When I call ‘behat -f progress’ Then it should pass <?php $steps->Then(‘/^it should (fail|pass)$/’, function($world, $status) { // $status == ‘pass’ } );
  • 37.
    СТАТУСЫ <?php $steps->Then(‘/^it should pass$/’,function($world) { return true; }); $steps->Then(‘/^it passes$/’, function($world) { return false; }); $steps->Then(‘/^it should fail$/’, function($world) { throw new Exception(); }); $steps->Then(‘/^it should pend$/’, function($world) { throw new EverzetBehatExceptionPending(); });
  • 38.
    СТАТУСЫ <?php $steps->Then(‘/^it should pass$/’,function($world) { ... }); $steps->Then(‘/^it should pass$/’, function($world) { // Will throw exception on definitions read }); $steps->Then(‘/^it’s good$/’, function($world) { ... }); $steps->Then(‘/^it’s w+$/’, function($world) { // Will throw exception on ‘it should pass call’ });
  • 39.
  • 40.
    УСТАНОВКА $> pear channel-discoverpear.everzet.com $> pear install everzet/behat-beta
  • 41.
    HOW MUCH ISTHE FISH? <?php # ./user.php class User { public function __construct($username, $age = 1) { } public function getName() {} public function getAge() {} }
  • 42.
    FEATURE # ./features/user.feature # language:ru Функционал: Базовый Пользователь Чтобы работать с пользователями Как разработчик сайта Я хочу иметь доступ к пользовательской модели Сценарий: Создание пользователя Допустим у нас нет пользователей Если мы добавим пользователя ‘everzet’ То у нас должно быть 1 пользователей И имя у первого пользователя ‘everzet’
  • 43.
  • 44.
    ОПРЕДЕЛЕНИЯ <?php # ./features/steps/user_steps.php require_once‘PHPUnit/Autoload.php’; require_once ‘PHPUnit/Framework/Assert/Functions.php’; require_once __DIR__ . ‘/../../user.php’; $steps-> Допустим(‘/^у нас нет пользователей$/’, function($world) { $world->users = array(); })-> Если(‘/^мы добавим пользователя ’([^’]+)’$/’, function($world, $username) { $world->users[] = new User($username); })-> To(‘/^у нас должно быть (d+) пользователей$/’, function($world, $count) { assertEquals($count, count($world->users)); })-> То(‘/^имя у первого пользователя ’([^’]+)’$/’, function($world, $username) { assertEquals($username, $world->users[0]->getName()); });
  • 45.
  • 46.
    РЕАЛИЗАЦИЯ <?php # ./user.php classUser { protected $name, $age; public function __construct($username, $age = 1) { $this->name = $username; $this->age = $age; } public function getName() { return $this->name; } public function getAge() { return $this->age; } }
  • 47.
  • 48.
    TODO 1. CommonWebSteps 2. Annotatedclass step definitions and hooks 3. JUnit formatter 4. PDF/HTML formatter 5. Selenium integration
  • 49.
    ССЫЛКИ ОФФИЦИАЛЬНЫЙ САЙТ http://everzet.com/Behat ОФФИЦИАЛЬНЫЙ РЕПОЗИТОРИЙ http://github.com/everzet/Behat ОФФИЦИАЛЬНАЯ GOOGLE GROUP http://groups.google.com/group/behat
  • 50.
  • 51.