7. TDD
• TDD не очень удачое название
• Если мы пишем тесты перед кодом, мы
все равно думаем об архитектуре
• Design Driven Development?
8. TDD и BDD
• Также пишем тесты перед кодом
• При TDD мы фиксируем в тестах
архитектуру приложения или его частей
• При BDD мы фиксируем в тестах
поведение приложения или его частей
• Описательная часть: спецификация или
пользовательские сценарии
9. Spec BDD
• Добавляем описательную часть к тестам
• Получаем не тесты, а спецификации
объектов
• Тестирование системы изнутри
• Используем вместо юнит тестов и
интеграционых тестов
10. Story (Scenario) BDD
• Вместо тестов описываем шаги, которые
нужно выполнить для достижения
определнного результата
• Шаги должны легко читаться, в идеале
это должны быть простые предложения
• Тестирование системы снаружи
• Замена функциональным тестам
12. 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 |
| active@user.com | password | no |
| blocked@user.com | password | yes |
Scenario: Successful login
Given I am on page "Login"
When I fill in "Username" with "active@user.com"
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"
13. Feature: Название функционала
In order to ... Ценность функционала
As a ... Выгодополучатель
I need ... Краткое описание функционала
Background:
Given ... Начальное состояние системы
Scenario: Название сценария
Given ... Начальное состояние
And ... Начальное состояние
When ... Выполняем шаг
And ... Выполняем шаг
Then ... Проверяем результат
And ... Проверяем результат
19. [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 |
20. 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
22. 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());
}
23. Steps definition
• @Given, @When, @Then
• Если шаг не выбросил исключение,
значит он завершился успешно
• Нет своих асершенов, но легко можно
использовать асершены из PHPUnit
24. /**
*
@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('KnpBundleKnpBundlesBundleEntityKeyword')
-‐>findOrCreateOne($row['keyword']);
$bundle-‐>addKeyword($keyword);
$entityManager-‐>persist($bundle);
}
}
$entityManager-‐>flush();
}
30. 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());
}
31. Backgrounds
• Общие шаги для всех сценариев
• Позволяется избавиться от дублирования
шагов в каждом сценарии
32.
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
|
33. MetaSteps
• Объединяем несколько шагов в один
• Помогает избавиться от дублирования
шагов
• Тесты запускаются по цепочке
• Возвращаем массив состоящий из шагов,
которые необходимо выполнить
34. /**
* @Given /I entered "([^"]*)" and expect "([^"]*)"/
*/
public function complexStep($number, $result)
{
return array(
new StepGiven("I have entered "$number""),
new StepWhen("I press +"),
new StepThen("I should see "$result" on the screen")
);
}
36. 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 |
47. Session
• Через сессию можно получить доступ к
остальным объектам: страница, статус
код, куки, заголовки и т. д.
• Несколько сессий запущенных
одновременно
49. NodeElement
• Можем манипулировать элементом
найденным по одному из селекторов
• Посмотреть аттрибуты, текст
• Эмулировать события браузера
• Манипулировать элеметами формы,
ввести текст в инпут, выбрать чекбокс,
прикрепить файл и т. д.
50. Mink + Behat
• Mink Extension for Behat
• Mink может быть использован отдельно
от Behat
• Минимум конфигурации
52. • Обсуждение функционала
• Составление User stories
• Подготовка сценариев на Gherkin DSL
• Пишем недостающие Step definitions
• Пишем функционал, юнит тесты, и т. д.
• Проверяем DoD