behat в php с использованием behat и mink
DESCRIPTION
TRANSCRIPT
BDD в PHP с использованием
Behat и Mink
About me
• Symfony developer at KnpLabs
• twitter: @tyomo4ka
• GitHub: tyomo4ka
Happy Awesome Developers
Agenda
• BDD
• Gherkin DSL
• Behat
• Mink
• BDD workflow
Зачем тестировать?
• Безопасный рефакторинг• Отсутствие регрессий• Более качеcтвенная архитектура• Уменьшение числа багов• Степень зрелости разработчика?
Behavior Driven Development
TDD
• TDD не очень удачое название
• Если мы пишем тесты перед кодом, мы все равно думаем об архитектуре
• Design Driven Development?
TDD и BDD
• Также пишем тесты перед кодом
• При TDD мы фиксируем в тестах архитектуру приложения или его частей
• При BDD мы фиксируем в тестах поведение приложения или его частей
• Описательная часть: спецификация или пользовательские сценарии
Spec BDD
• Добавляем описательную часть к тестам
• Получаем не тесты, а спецификации объектов
• Тестирование системы изнутри
• Используем вместо юнит тестов и интеграционых тестов
Story (Scenario) BDD
• Вместо тестов описываем шаги, которые нужно выполнить для достижения определнного результата
• Шаги должны легко читаться, в идеале это должны быть простые предложения
• Тестирование системы снаружи
• Замена функциональным тестам
Gherkin DSL
Feature: Customer login In order to view protected data As a customer I need to be able to login
Background: Given customers are registered: | username | password | blocked | | [email protected] | password | no | | [email protected] | password | yes |
Scenario: Successful login Given I am on page "Login" When I fill in "Username" with "[email protected]" And I fill in "Password" with "password" And I press "Submit" Then I should be on page "Personal profile" And I should see "Successful login"
Feature: Название функционала In order to ... Ценность функционала As a ... Выгодополучатель I need ... Краткое описание функционала
Background: Given ... Начальное состояние системы
Scenario: Название сценария Given ... Начальное состояние And ... Начальное состояние When ... Выполняем шаг And ... Выполняем шаг Then ... Проверяем результат And ... Проверяем результат
Behat
Зачем?
• A php framework for testing your business expectations
Установка
• PHP 5.3
• Composer
• PHAR
• Git
Инициализация
• behat --init
• features/ directory
• features/bootstrap/ directory
• features/bootstrap/*Context.php
• behat.yml
features/*.feature
• Gherkin DSL
• behat --story-syntax --lang=LANG
[Feature|Business Need|Ability]: Internal operations In order to stay secret As a secret organization We need to be able to erase past agents' memory
Background: Given there is agent A And there is agent B
Scenario: Erasing agent memory Given there is agent J And there is agent K When I erase agent K's memory Then there should be agent J But there should not be agent K
[Scenario Outline|Scenario Template]: Erasing other agents' memory Given there is agent <agent1> And there is agent <agent2> When I erase agent <agent2>'s memory Then there should be agent <agent1> But there should not be agent <agent2>
[Examples|Scenarios]: | agent1 | agent2 | | D | M |
Feature: Listing developers As a Visitor I want to browse through developers list
Background: Given the site has following users: | name | | knplabs | | fos | Given the site has following bundles: | username | name | description | lastCommitAt | score | trend1 | | knplabs | TestBundle | test desc |-‐1 day | 10 | 15 | | fos | UserBundle | user desc |-‐2 days | 20 | 5 |
Scenario: Listing developers When I go to "/" And I follow "Developers" Then I should see "2 developers using Symfony2" And I should see "knplabs" developer And I should see "fos" developer
Context
• POPO
• Описание шагов• Хуки• Subcontexts
• Closures для описания шагов и хуков
public function __construct($kernel) { $this-‐>useContext('symfony_doctrine', new SymfonyDoctrineContext()); $this-‐>useContext('solr', new SolrContext()); $this-‐>useContext('mink', new MinkContext()); $this-‐>useContext('api', new ApiContext()); }
Steps definition
• @Given, @When, @Then
• Если шаг не выбросил исключение, значит он завершился успешно
• Нет своих асершенов, но легко можно использовать асершены из PHPUnit
/** * @Given /^the bundles have following keywords:$/ */ public function theBundlesHaveFollowingKeywords(TableNode $table) { $entityManager = $this-‐>getEntityManager();
foreach ($table-‐>getHash() as $row) { if (isset($this-‐>bundles[$row['bundle']])) { $bundle = $this-‐>bundles[$row['bundle']]; $keyword = $entityManager
-‐>getRepository('Knp\Bundle\KnpBundlesBundle\Entity\Keyword')-‐>findOrCreateOne($row['keyword']);
$bundle-‐>addKeyword($keyword); $entityManager-‐>persist($bundle); } }
$entityManager-‐>flush(); }
Hooks
• BeforeStep/AfterStep
• BeforeScenario/AfterScenario
• BeforeFeature/AfterFeature
• BeforeSuite/AfterSuite
• Hooks can be tagged
/** * @BeforeScenario * * @return null */ public function buildSchema($event) { $metadata = $this-‐>getMetadata();
if (!empty($metadata)) { $tool = new SchemaTool($this-‐>getEntityManager()); $tool-‐>dropSchema($metadata); $tool-‐>createSchema($metadata); } }
TableNode
• getRows
• getHash
• getRowsHash
• getRowLines
• getRowAsString
• getNumeratedRows
Scenario: Given the following people exist: | name | email | phone | | Aslak | [email protected] | 123 | | Joe | [email protected] | 234 | | Bryan | [email protected] | 456 |
/** * @Given /the following people exist:/ */public function thePeopleExist(TableNode $table){ $hash = $table->getHash(); foreach ($hash as $row) { // $row['name'], $row['email'], $row['phone'] }}
PyStringNode
• Опеределение длинного теста в несколько строчек
Scenario: Given a blog post named "Random" with: """ Some Title, Eh? =============== Here is the first paragraph of my blog post. Lorem ipsum dolor sit amet, consectetur adipiscing elit. """
/** * @Given /a blog post named "([^"]+)" with:/ */public function blogPost($title, PyStringNode $markdown){ $this->createPost($title, $markdown->getRaw());}
Backgrounds
• Общие шаги для всех сценариев• Позволяется избавиться от дублирования шагов в каждом сценарии
Background: Given the site has following users: | name | | knplabs | | fos | Given the site has following bundles: | username | name | description | lastCommitAt | score | trend1 | | knplabs | TestBundle | test desc |-‐1 day | 10 | 15 | | fos | UserBundle | user desc |-‐2 days | 20 | 5 |
MetaSteps
• Объединяем несколько шагов в один
• Помогает избавиться от дублирования шагов
• Тесты запускаются по цепочке
• Возвращаем массив состоящий из шагов, которые необходимо выполнить
/** * @Given /I entered "([^"]*)" and expect "([^"]*)"/ */public function complexStep($number, $result){ return array( new Step\Given("I have entered \"$number\""), new Step\When("I press +"), new Step\Then("I should see \"$result\" on the screen") );}
ScenarioOutlines
• Помогает избавиться от дублирования сценариев
• Предоставляет удобный интерфейс для описания набора тестов и ожидаемых результатов
Scenario Outline: Given I have entered <number1> And I have entered <number2> When I add Then The result should be <result> Examples: | number1 | number2 | result | | 10 | 12 | 22 | | 5 | 3 | 8 | | 5 | 5 | 10 |
Tags
• Тэги в сценариях• Тэги в хуках• behat --tags "@orm,@database"
• behat --tags "@orm&&@database"
• beaht --tags "-@database"
Запуск сценариев
• behat features/
• behat features/single.feature
• behat features/single.feature:10-20
• behat --name=”Feature name”
• behat --tags @tag1,@tag2
• behat --profile test
Profiles
• Настройки форматтеров• Настройка контекстов• Настройки тэгов• Настройки экстеншенов• Настройка путей к файлам
# behat.ymldefault: context: class: Your\Custom\Contextwip: filters: tags: "@wip" formatter: name: progressci: formatter: name: junit parameters: output_path: /var/tmp/junit
Система экстеншенов
• Mink Extension
• Symfony2 Extension
• Behatch Extension
• Doctrine DataFixtures Extension
• Gearman Extension
• Write your own
Mink
Зачем?
• Слой абстракции для использования различных эмуляторов браузера
• Приемочное тестирование web-приложений
Установка
• PHP 5.3
• Composer
• PHAR
• Git
Drivers
• Goutte
• Zombie
• Selenium
• Selenium2
• Sahi
Session
• Через сессию можно получить доступ к остальным объектам: страница, статус код, куки, заголовки и т. д.
• Несколько сессий запущенных одновременно
Selectors
• Named
• CSS
• XPath
• find*
• Traversing
NodeElement
• Можем манипулировать элементом найденным по одному из селекторов
• Посмотреть аттрибуты, текст
• Эмулировать события браузера• Манипулировать элеметами формы, ввести текст в инпут, выбрать чекбокс, прикрепить файл и т. д.
Mink + Behat
• Mink Extension for Behat
• Mink может быть использован отдельно от Behat
• Минимум конфигурации
BDD workflow
• Обсуждение функционала
• Составление User stories
• Подготовка сценариев на Gherkin DSL
• Пишем недостающие Step definitions
• Пишем функционал, юнит тесты, и т. д.
• Проверяем DoD
Спасибо за внимание!