SlideShare a Scribd company logo
1 of 91
Download to read offline
Behat Best Practices with
Symfony
Ciaran McNulty
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Behaviour Driven
Development
@ciaranmcnulty | #symfony_live
BDD is the art of using examples in
conversations to illustrate behaviour
— Liz Keogh
@ciaranmcnulty | #symfony_live
BDD is the art of using
examples in
conversations to
illustrate behaviour
@ciaranmcnulty | #symfony_live
BDD is the art of using
examples in
conversations to
illustrate behaviour
@ciaranmcnulty | #symfony_live
BDD is the art of using
examples in
conversations to
illustrate behaviour
@ciaranmcnulty | #symfony_live
Example
2 - Something that serves to illustrate or explain a rule
— Wiktionary
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Rule:
We charge our customers sales tax at a rate of 20%
@ciaranmcnulty | #symfony_live
Rule:
We charge our customers sales tax at a rate of 20%
Example:
So, if an item is priced at $10, we charge $10
+ $2 tax for a total of $12
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Rule:
We charge our customers sales tax at a rate of 20%
Example:
So, if an item is priced at $10, we charge $10
+ $2 tax for a total of $12
@ciaranmcnulty | #symfony_live
Rule:
We charge our customers sales tax at a rate of 20%
Example:
No! If an item is priced at £10, we charge £10
and allocate £1.67 of that as sales tax
@ciaranmcnulty | #symfony_live
Capturing Examples
Input -> Rules -> Output
@ciaranmcnulty | #symfony_live
Capturing Examples
Input: When an action is taken
Output: Then an outcome should occur
@ciaranmcnulty | #symfony_live
Capturing Examples
When I buy a pair of Levi 501s
Then I am charged £32.99
@ciaranmcnulty | #symfony_live
Capturing Examples
Context: Given some situation
Input: When an action is taken
Output: Then an outcome should occur
@ciaranmcnulty | #symfony_live
Capturing Examples
Given Levi 501s are listed at £32.99
When I buy a pair of Levi 501s
Then I am charged £32.99
@ciaranmcnulty | #symfony_live
Context Questioning
“Is there any other context which, when this event happens, will
produce a different outcome?” - Liz Keogh
@ciaranmcnulty | #symfony_live
Context Questioning
Given Levi 501s are listed at £32.99
When I buy a pair of Levi 501s
Then I am charged £32.99
“Is there any situation where I could buy these jeans and pay a different
amount?”
@ciaranmcnulty | #symfony_live
Context Questioning
Given Levi 501s are listed at £32.99
When I buy a pair of Levi 501s
Then I am charged £32.99
“Is there any situation where I could buy these jeans and pay a different
amount?”
» When they are on sale
» When I get a staff discount
» When they are ex-display
@ciaranmcnulty | #symfony_live
Outcome Questioning
“Given this context, when this event happens, is there another outcome
that’s important? Something we missed, perhaps?” - Liz Keogh
@ciaranmcnulty | #symfony_live
Outcome Questioning
Given Levi 501s are listed at £32.99
When I buy a pair of Levi 501s
Then I am charged £32.99
"Aside from me being charged for the jeans, does something else
happen that we need to care about?"
@ciaranmcnulty | #symfony_live
Outcome Questioning
Given Levi 501s are listed at £32.99
When I buy a pair of Levi 501s
Then I am charged £32.99
"Aside from me being charged for the jeans, does something else
happen that we need to care about?"
» I get given a pair of jeans!
» Stock control has to be told we sold them
@ciaranmcnulty | #symfony_live
Validating examples
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Behat
@ciaranmcnulty | #symfony_live
Feature: Scheduling a training course
As a trainer
In order to be able to cancel courses or schedule new ones
I should be able to specify a maximum and minimum class size
Rules:
- Course is proposed with size limits
- When enough enrolments happen, course is considered viable
- When maximum class size is reached, further enrolments are not allowed
Background:
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Scenario: Course does not get enough enrolments to be viable
When only Alice enrols on this course
Then this course will not be viable
Scenario: Course gets enough enrolments to be viable
Given Alice has already enrolled on this course
When Bob enrols on this course
Then this course will be viable
Scenario: Enrolments are stopped when class size is reached
Given Alice, Bob and Charlie have already enrolled on this course
When Derek tries to enrol on this course
Then he should not be able to enrol
@ciaranmcnulty | #symfony_live
Feature: Scheduling a training course
As a trainer
In order to be able to cancel courses or schedule new ones
I should be able to specify a maximum and minimum class size
Rules:
- Course is proposed with size limits
- When enough enrolments happen, course is considered viable
- When maximum class size is reached, further enrolments are not allowed
@ciaranmcnulty | #symfony_live
Background:
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Scenario: Course does not get enough enrolments to be viable
When only Alice enrols on this course
Then this course will not be viable
@ciaranmcnulty | #symfony_live
Background:
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Scenario: Course does not get enough enrolments to be viable
When only Alice enrols on this course
Then this course will not be viable
@ciaranmcnulty | #symfony_live
Background:
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Scenario: Course gets enough enrolments to be viable
Given Alice has already enrolled on this course
When Bob enrols on this course
Then this course will be viable
@ciaranmcnulty | #symfony_live
Background:
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Scenario: Enrolments are stopped when class size is reached
Given Alice, Bob and Charlie have already enrolled on this course
When Derek tries to enrol on this course
Then he should not be able to enrol
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Gherkin:
Given a thing happens to Ciaran
PHP:
/**
* @Given a thing happens to :person
*/
public function doAThing(string $person)
{
// you have to write this
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Driving the Domain layer
» Drive PHP objects directly from scenario
» Proves domain supports business actions
» Aligns domain model with business language
» Executes quickly with few dependencies
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class FeatureContext implements Context
{
/**
* @Given :courseTitle was proposed with a class size of :min to :max people
*/
public function courseWasProposedWithAClassSizeOfToPeople(string $courseTitle, int $min, int $max)
{
$this->course = Course::propose(
$courseTitle,
ClassSize::between($min, $max)
);
}
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class FeatureContext implements Context
{
/** @Transform */
public function transformLearner(string $name) : Learner
{
return Learner::called($name);
}
/**
* @When only :learner enrols on this course
*/
public function learnerEnrolsOnCourse(Learner $learner)
{
$this->course->enrol($learner);
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class FeatureContext implements Context
{
/**
* @Then this course will not be viable
*/
public function thisCourseWillNotBeViable()
{
assert($this->course->isViable() == false);
}
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Given Alice has already enrolled on this course
When Bob enrols on this course
Then this course will be viable
class FeatureContext implements Context
{
/**
* @When only :learner enrols on this course
* @Given :learner has already enrolled on this course
*/
public function learnerEnrolsOnCourse(Learner $learner)
{
$this->course->enrol($learner);
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Given Alice has already enrolled on this course
When Bob enrols on this course
Then this course will be viable
class FeatureContext implements Context
{
/**
* @When (only) :learner enrols on this course
* @Given :learner has already enrolled on this course
*/
public function learnerEnrolsOnCourse(Learner $learner)
{
$this->course->enrol($learner);
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
Given Alice has already enrolled on this course
When Bob enrols on this course
Then this course will be viable
class FeatureContext implements Context
{
/**
* @Then this course will be viable
*/
public function thisCourseWillBeViable()
{
assert($this->course->isViable() == true);
}
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
class Course
{
//...
public function isViable(): bool
{
return $this->classSize->isViable($this->learners);
}
}
class ClassSize
{
//...
public function isViable(int $size) : bool
{
return $size >= $this->min;
}
}
@ciaranmcnulty | #symfony_live
class Course
{
//...
public function isViable()
{
return $this->classSize->isViable($this->learners);
}
}
class ClassSize
{
//...
public function isViable(int $size)
{
return $size >= $this->min;
}
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Given Alice, Bob and Charlie have already enrolled on this course
When Derek tries to enrol on this course
Then he should not be able to enrol
class FeatureContext implements Context
{
/**
* @Given :learner1, :learner2 and :learner3 have already enrolled on this course
*/
public function learnersHaveAlreadyEnrolledOnThisCourse(
Learner $learner1, Learner $learner2, Learner $learner3
)
{
$this->course->enrol($learner1);
$this->course->enrol($learner2);
$this->course->enrol($learner3);
}
}
@ciaranmcnulty | #symfony_live
Given Alice, Bob and Charlie have already enrolled on this course
When Derek tries to enrol on this course
Then he should not be able to enrol
class FeatureContext implements Context
{
/**
* @When :learner tries to enrol on this course
*/
public function learnerTriesToEnrolOnCourse(Learner $learner)
{
try {
$this->course->enrol($learner);
}
catch (Exception $e)
{
$this->enrolmentProblem = $e;
}
}
}
@ciaranmcnulty | #symfony_live
Given Alice, Bob and Charlie have already enrolled on this course
When Derek tries to enrol on this course
Then he should not be able to enrol
class FeatureContext implements Context
{
/**
* @Then (s)he should not be able to enrol
*/
public function learnerShouldNotBeAbleToEnrol()
{
assert($this->enrolmentProblem instanceof CjmTrainingEnrolmentProblem);
}
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
final class Course
{
public function enrol(Learner $learner)
{
if (!$this->classSize->hasMoreCapacity($this->learners)) {
throw new EnrolmentProblem('Class is already at capacity');
}
$this->learners++;
}
}
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Driving the Service layer
» Configure services in test SF environment
» Inject services into Behat with behat/symfony2extension
» Interact with domain model via the services
» Aligns service layer with business use cases
@ciaranmcnulty | #symfony_live
# behat.yml
default:
suites:
domain:
contexts: [ DomainContext ]
services:
contexts: [ ServiceContext ]
extensions:
BehatSymfony2Extension: ~
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class ServiceContext implements Context
{
public function __construct(CourseEnrolments $courseEnrolments)
{
$this->courseEnrolments = $courseEnrolments;
}
/**
* @Given :course was proposed with a class size of :min to :max people
*/
public function wasProposedWithAClassSizeOfToPeople(string $course, int $min, int $max)
{
$this->course = $course;
$this->courseEnrolments->propose($course, $min, $max);
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class ServiceContext implements Context
{
public function __construct(CourseEnrolments $courseEnrolments)
{
$this->courseEnrolments = $courseEnrolments;
}
/**
* @Given :course was proposed with a class size of :min to :max people
*/
public function wasProposedWithAClassSizeOfToPeople(string $course, int $min, int $max)
{
$this->course = $course;
$this->courseEnrolments->propose($course, $min, $max);
}
}
@ciaranmcnulty | #symfony_live
# behat.yml
default:
suites:
domain:
contexts:
- DomainContext
services:
contexts:
- ServiceContext:
courseEnrolments: "@cjm.training.enrolment.course_enrolments"
extensions:
BehatSymfony2Extension: ~
@ciaranmcnulty | #symfony_live
class CourseEnrolments
{
public function propose(string $title, int $minimum, int $maximum)
{
$this->courses->add(
Course::propose(
$title,
ClassSize::between($minimum, $maximum)
)
);
}
}
@ciaranmcnulty | #symfony_live
Repositories
» Using real infrastructure is slow
» Using fake infrastructure can lower confidence
» Use fake infrastructure but sync via contract tests
@ciaranmcnulty | #symfony_live
final class Courses implements CjmTrainingEnrolmentModelCourses
{
public function add(Course $course) : void
{
$this->courses[] = $course;
}
public function findByTitle(string $title): Course
{
return $this->courses[0];
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class ServiceContext implements Context
{
/**
* @When (only) :learner enrols on this course
*/
public function learnerEnrolsOnThisCourse(string $learner)
{
$this->courseEnrolments->enrol($learner, $this->course);
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class ServiceContext implements Context
{
/**
* @Then this course will not be viable
*/
public function thisCourseWillNotBeViable()
{
assert($this->courseEnrolments->isCourseViable($this->course) == false);
}
}
@ciaranmcnulty | #symfony_live
Domain vs Service layer
» Start by driving domain layer
» Refactor to services when confidence grows
» Drop back to domain layer when remodelling
@ciaranmcnulty | #symfony_live
@ciaranmcnulty | #symfony_live
Driving the UI layer
» Simulate a browser with behat/minkextension
» Interact with domain model via the UI
» Ensures UI supports business actions
» Slow, brittle, flakey...
» Does not constrain API
@ciaranmcnulty | #symfony_live
Don't do this
Scenario: Buying a pair of jeans
Given I am on "/products/levi-501"
When I click on "#add-form input[type=submit]"
Then "#basket ul" should contain "jeans"
@ciaranmcnulty | #symfony_live
Mink
» Browser driver abstraction
» Supports Selenium, Goutte, Browserkit
@ciaranmcnulty | #symfony_live
# behat.yml
endtoend:
filters:
tags: "@endtoend"
suites:
domain: false
services: false
endtoend:
contexts:
- EndToEndContext:
courseEnrolments: "@cjm.training.enrolment.course_enrolments"
@ciaranmcnulty | #symfony_live
# behat.yml
extensions:
BehatSymfony2Extension:
env:
test_e2e
BehatMinkExtension:
sessions:
symfony:
symfony2: ~
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class EndToEndContext implements RawMinkContext
{
/**
* @Given :course was proposed with a class size of :min to :max people
*/
public function wasProposedWithAClassSizeOfToPeople(string $course, int $min, int $max)
{
$this->course = $course;
$this->courseEnrolments->propose($course, $min, $max);
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class EndToEndContext implements RawMinkContext
{
/**
* @When only :learner enrols on this course
*/
public function learnerEnrolsOnCourse(string $learner)
{
$this->visitPath('/courses/' . $this->course);
$page = $this->getSession()->getPage();
$page->fillField('Your name', $learner);
$page->pressButton('Enrol');
}
}
@ciaranmcnulty | #symfony_live
Given "BDD for Beginners" was proposed with a class size of 2 to 3 people
When only Alice enrols on this course
Then this course will not be viable
class EndToEndContext implements RawMinkContext
{
/**
* @Then this course will not be viable
*/
public function thisCourseWillNotBeViable()
{
$this->visitPath('/courses/'.$this->course);
$this->assertSession()->elementExists('css', '#not-viable-warning');
}
}
@ciaranmcnulty | #symfony_live
Automating a real browser
» Avoid or minimise
» Orders of magnitude slower
» Required for end-to-end with JS
» Replace with JS cucumber stack
@ciaranmcnulty | #symfony_live
# behat.yml
extensions:
BehatMinkExtension:
sessions:
symfony:
symfony2: ~
selenium2:
browser: chrome
capabilities:
chrome:
switches:
- "--headless"
- "--disable-gpu"
@ciaranmcnulty | #symfony_live
Summary
» Drive domain objects directly to explore model
» Refactor to services when model is stable
» Add minimal UI coverage
@ciaranmcnulty | #symfony_live
Thanks
» @ciaranmcnulty
» @Inviqa
» @PhpSpec
» @BDDLondon
github.com/ciaranmcnulty/behat-symfony-demo
@ciaranmcnulty | #symfony_live

More Related Content

More from CiaranMcNulty

Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through ExamplesCiaranMcNulty
 
Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016CiaranMcNulty
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through ExamplesCiaranMcNulty
 
Finding the Right Testing Tool for the Job
Finding the Right Testing Tool for the JobFinding the Right Testing Tool for the Job
Finding the Right Testing Tool for the JobCiaranMcNulty
 
Conscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHPConscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHPCiaranMcNulty
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016CiaranMcNulty
 
Fly In Style (without splashing out)
Fly In Style (without splashing out)Fly In Style (without splashing out)
Fly In Style (without splashing out)CiaranMcNulty
 
Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015CiaranMcNulty
 
Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015CiaranMcNulty
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesCiaranMcNulty
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through ExamplesCiaranMcNulty
 
Why Your Test Suite Sucks
Why Your Test Suite SucksWhy Your Test Suite Sucks
Why Your Test Suite SucksCiaranMcNulty
 
Driving Design with PhpSpec
Driving Design with PhpSpecDriving Design with PhpSpec
Driving Design with PhpSpecCiaranMcNulty
 
Using HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless IntegrationUsing HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless IntegrationCiaranMcNulty
 

More from CiaranMcNulty (16)

Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through Examples
 
Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016Modelling by Example Workshop - PHPNW 2016
Modelling by Example Workshop - PHPNW 2016
 
Conscious Coupling
Conscious CouplingConscious Coupling
Conscious Coupling
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through Examples
 
Finding the Right Testing Tool for the Job
Finding the Right Testing Tool for the JobFinding the Right Testing Tool for the Job
Finding the Right Testing Tool for the Job
 
Conscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHPConscious Decoupling - Lone Star PHP
Conscious Decoupling - Lone Star PHP
 
TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016TDD with PhpSpec - Lone Star PHP 2016
TDD with PhpSpec - Lone Star PHP 2016
 
Fly In Style (without splashing out)
Fly In Style (without splashing out)Fly In Style (without splashing out)
Fly In Style (without splashing out)
 
Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015Why Your Test Suite Sucks - PHPCon PL 2015
Why Your Test Suite Sucks - PHPCon PL 2015
 
Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015Driving Design through Examples - PhpCon PL 2015
Driving Design through Examples - PhpCon PL 2015
 
Building a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing StrategiesBuilding a Pyramid: Symfony Testing Strategies
Building a Pyramid: Symfony Testing Strategies
 
TDD with PhpSpec
TDD with PhpSpecTDD with PhpSpec
TDD with PhpSpec
 
Driving Design through Examples
Driving Design through ExamplesDriving Design through Examples
Driving Design through Examples
 
Why Your Test Suite Sucks
Why Your Test Suite SucksWhy Your Test Suite Sucks
Why Your Test Suite Sucks
 
Driving Design with PhpSpec
Driving Design with PhpSpecDriving Design with PhpSpec
Driving Design with PhpSpec
 
Using HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless IntegrationUsing HttpKernelInterface for Painless Integration
Using HttpKernelInterface for Painless Integration
 

Recently uploaded

MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based projectAnoyGreter
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样umasea
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Andreas Granig
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfStefano Stabellini
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanyChristoph Pohl
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Hr365.us smith
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 

Recently uploaded (20)

MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based project
 
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
办理学位证(UQ文凭证书)昆士兰大学毕业证成绩单原版一模一样
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdf
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)Recruitment Management Software Benefits (Infographic)
Recruitment Management Software Benefits (Infographic)
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 

Behat Best Practices with Symfony

  • 1. Behat Best Practices with Symfony Ciaran McNulty @ciaranmcnulty | #symfony_live
  • 6. BDD is the art of using examples in conversations to illustrate behaviour — Liz Keogh @ciaranmcnulty | #symfony_live
  • 7. BDD is the art of using examples in conversations to illustrate behaviour @ciaranmcnulty | #symfony_live
  • 8. BDD is the art of using examples in conversations to illustrate behaviour @ciaranmcnulty | #symfony_live
  • 9. BDD is the art of using examples in conversations to illustrate behaviour @ciaranmcnulty | #symfony_live
  • 10. Example 2 - Something that serves to illustrate or explain a rule — Wiktionary @ciaranmcnulty | #symfony_live
  • 12. Rule: We charge our customers sales tax at a rate of 20% @ciaranmcnulty | #symfony_live
  • 13. Rule: We charge our customers sales tax at a rate of 20% Example: So, if an item is priced at $10, we charge $10 + $2 tax for a total of $12 @ciaranmcnulty | #symfony_live
  • 19. Rule: We charge our customers sales tax at a rate of 20% Example: So, if an item is priced at $10, we charge $10 + $2 tax for a total of $12 @ciaranmcnulty | #symfony_live
  • 20. Rule: We charge our customers sales tax at a rate of 20% Example: No! If an item is priced at £10, we charge £10 and allocate £1.67 of that as sales tax @ciaranmcnulty | #symfony_live
  • 21. Capturing Examples Input -> Rules -> Output @ciaranmcnulty | #symfony_live
  • 22. Capturing Examples Input: When an action is taken Output: Then an outcome should occur @ciaranmcnulty | #symfony_live
  • 23. Capturing Examples When I buy a pair of Levi 501s Then I am charged £32.99 @ciaranmcnulty | #symfony_live
  • 24. Capturing Examples Context: Given some situation Input: When an action is taken Output: Then an outcome should occur @ciaranmcnulty | #symfony_live
  • 25. Capturing Examples Given Levi 501s are listed at £32.99 When I buy a pair of Levi 501s Then I am charged £32.99 @ciaranmcnulty | #symfony_live
  • 26. Context Questioning “Is there any other context which, when this event happens, will produce a different outcome?” - Liz Keogh @ciaranmcnulty | #symfony_live
  • 27. Context Questioning Given Levi 501s are listed at £32.99 When I buy a pair of Levi 501s Then I am charged £32.99 “Is there any situation where I could buy these jeans and pay a different amount?” @ciaranmcnulty | #symfony_live
  • 28. Context Questioning Given Levi 501s are listed at £32.99 When I buy a pair of Levi 501s Then I am charged £32.99 “Is there any situation where I could buy these jeans and pay a different amount?” » When they are on sale » When I get a staff discount » When they are ex-display @ciaranmcnulty | #symfony_live
  • 29. Outcome Questioning “Given this context, when this event happens, is there another outcome that’s important? Something we missed, perhaps?” - Liz Keogh @ciaranmcnulty | #symfony_live
  • 30. Outcome Questioning Given Levi 501s are listed at £32.99 When I buy a pair of Levi 501s Then I am charged £32.99 "Aside from me being charged for the jeans, does something else happen that we need to care about?" @ciaranmcnulty | #symfony_live
  • 31. Outcome Questioning Given Levi 501s are listed at £32.99 When I buy a pair of Levi 501s Then I am charged £32.99 "Aside from me being charged for the jeans, does something else happen that we need to care about?" » I get given a pair of jeans! » Stock control has to be told we sold them @ciaranmcnulty | #symfony_live
  • 36. Feature: Scheduling a training course As a trainer In order to be able to cancel courses or schedule new ones I should be able to specify a maximum and minimum class size Rules: - Course is proposed with size limits - When enough enrolments happen, course is considered viable - When maximum class size is reached, further enrolments are not allowed Background: Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Scenario: Course does not get enough enrolments to be viable When only Alice enrols on this course Then this course will not be viable Scenario: Course gets enough enrolments to be viable Given Alice has already enrolled on this course When Bob enrols on this course Then this course will be viable Scenario: Enrolments are stopped when class size is reached Given Alice, Bob and Charlie have already enrolled on this course When Derek tries to enrol on this course Then he should not be able to enrol @ciaranmcnulty | #symfony_live
  • 37. Feature: Scheduling a training course As a trainer In order to be able to cancel courses or schedule new ones I should be able to specify a maximum and minimum class size Rules: - Course is proposed with size limits - When enough enrolments happen, course is considered viable - When maximum class size is reached, further enrolments are not allowed @ciaranmcnulty | #symfony_live
  • 38. Background: Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Scenario: Course does not get enough enrolments to be viable When only Alice enrols on this course Then this course will not be viable @ciaranmcnulty | #symfony_live
  • 39. Background: Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Scenario: Course does not get enough enrolments to be viable When only Alice enrols on this course Then this course will not be viable @ciaranmcnulty | #symfony_live
  • 40. Background: Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Scenario: Course gets enough enrolments to be viable Given Alice has already enrolled on this course When Bob enrols on this course Then this course will be viable @ciaranmcnulty | #symfony_live
  • 41. Background: Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Scenario: Enrolments are stopped when class size is reached Given Alice, Bob and Charlie have already enrolled on this course When Derek tries to enrol on this course Then he should not be able to enrol @ciaranmcnulty | #symfony_live
  • 43. Gherkin: Given a thing happens to Ciaran PHP: /** * @Given a thing happens to :person */ public function doAThing(string $person) { // you have to write this } @ciaranmcnulty | #symfony_live
  • 46. Driving the Domain layer » Drive PHP objects directly from scenario » Proves domain supports business actions » Aligns domain model with business language » Executes quickly with few dependencies @ciaranmcnulty | #symfony_live
  • 48. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class FeatureContext implements Context { /** * @Given :courseTitle was proposed with a class size of :min to :max people */ public function courseWasProposedWithAClassSizeOfToPeople(string $courseTitle, int $min, int $max) { $this->course = Course::propose( $courseTitle, ClassSize::between($min, $max) ); } } @ciaranmcnulty | #symfony_live
  • 51. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class FeatureContext implements Context { /** @Transform */ public function transformLearner(string $name) : Learner { return Learner::called($name); } /** * @When only :learner enrols on this course */ public function learnerEnrolsOnCourse(Learner $learner) { $this->course->enrol($learner); } } @ciaranmcnulty | #symfony_live
  • 52. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class FeatureContext implements Context { /** * @Then this course will not be viable */ public function thisCourseWillNotBeViable() { assert($this->course->isViable() == false); } } @ciaranmcnulty | #symfony_live
  • 54. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Given Alice has already enrolled on this course When Bob enrols on this course Then this course will be viable class FeatureContext implements Context { /** * @When only :learner enrols on this course * @Given :learner has already enrolled on this course */ public function learnerEnrolsOnCourse(Learner $learner) { $this->course->enrol($learner); } } @ciaranmcnulty | #symfony_live
  • 55. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Given Alice has already enrolled on this course When Bob enrols on this course Then this course will be viable class FeatureContext implements Context { /** * @When (only) :learner enrols on this course * @Given :learner has already enrolled on this course */ public function learnerEnrolsOnCourse(Learner $learner) { $this->course->enrol($learner); } } @ciaranmcnulty | #symfony_live
  • 56. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people Given Alice has already enrolled on this course When Bob enrols on this course Then this course will be viable class FeatureContext implements Context { /** * @Then this course will be viable */ public function thisCourseWillBeViable() { assert($this->course->isViable() == true); } } @ciaranmcnulty | #symfony_live
  • 58. class Course { //... public function isViable(): bool { return $this->classSize->isViable($this->learners); } } class ClassSize { //... public function isViable(int $size) : bool { return $size >= $this->min; } } @ciaranmcnulty | #symfony_live
  • 59. class Course { //... public function isViable() { return $this->classSize->isViable($this->learners); } } class ClassSize { //... public function isViable(int $size) { return $size >= $this->min; } } @ciaranmcnulty | #symfony_live
  • 61. Given Alice, Bob and Charlie have already enrolled on this course When Derek tries to enrol on this course Then he should not be able to enrol class FeatureContext implements Context { /** * @Given :learner1, :learner2 and :learner3 have already enrolled on this course */ public function learnersHaveAlreadyEnrolledOnThisCourse( Learner $learner1, Learner $learner2, Learner $learner3 ) { $this->course->enrol($learner1); $this->course->enrol($learner2); $this->course->enrol($learner3); } } @ciaranmcnulty | #symfony_live
  • 62. Given Alice, Bob and Charlie have already enrolled on this course When Derek tries to enrol on this course Then he should not be able to enrol class FeatureContext implements Context { /** * @When :learner tries to enrol on this course */ public function learnerTriesToEnrolOnCourse(Learner $learner) { try { $this->course->enrol($learner); } catch (Exception $e) { $this->enrolmentProblem = $e; } } } @ciaranmcnulty | #symfony_live
  • 63. Given Alice, Bob and Charlie have already enrolled on this course When Derek tries to enrol on this course Then he should not be able to enrol class FeatureContext implements Context { /** * @Then (s)he should not be able to enrol */ public function learnerShouldNotBeAbleToEnrol() { assert($this->enrolmentProblem instanceof CjmTrainingEnrolmentProblem); } } @ciaranmcnulty | #symfony_live
  • 65. final class Course { public function enrol(Learner $learner) { if (!$this->classSize->hasMoreCapacity($this->learners)) { throw new EnrolmentProblem('Class is already at capacity'); } $this->learners++; } } @ciaranmcnulty | #symfony_live
  • 68. Driving the Service layer » Configure services in test SF environment » Inject services into Behat with behat/symfony2extension » Interact with domain model via the services » Aligns service layer with business use cases @ciaranmcnulty | #symfony_live
  • 69. # behat.yml default: suites: domain: contexts: [ DomainContext ] services: contexts: [ ServiceContext ] extensions: BehatSymfony2Extension: ~ @ciaranmcnulty | #symfony_live
  • 70. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class ServiceContext implements Context { public function __construct(CourseEnrolments $courseEnrolments) { $this->courseEnrolments = $courseEnrolments; } /** * @Given :course was proposed with a class size of :min to :max people */ public function wasProposedWithAClassSizeOfToPeople(string $course, int $min, int $max) { $this->course = $course; $this->courseEnrolments->propose($course, $min, $max); } } @ciaranmcnulty | #symfony_live
  • 71. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class ServiceContext implements Context { public function __construct(CourseEnrolments $courseEnrolments) { $this->courseEnrolments = $courseEnrolments; } /** * @Given :course was proposed with a class size of :min to :max people */ public function wasProposedWithAClassSizeOfToPeople(string $course, int $min, int $max) { $this->course = $course; $this->courseEnrolments->propose($course, $min, $max); } } @ciaranmcnulty | #symfony_live
  • 72. # behat.yml default: suites: domain: contexts: - DomainContext services: contexts: - ServiceContext: courseEnrolments: "@cjm.training.enrolment.course_enrolments" extensions: BehatSymfony2Extension: ~ @ciaranmcnulty | #symfony_live
  • 73. class CourseEnrolments { public function propose(string $title, int $minimum, int $maximum) { $this->courses->add( Course::propose( $title, ClassSize::between($minimum, $maximum) ) ); } } @ciaranmcnulty | #symfony_live
  • 74. Repositories » Using real infrastructure is slow » Using fake infrastructure can lower confidence » Use fake infrastructure but sync via contract tests @ciaranmcnulty | #symfony_live
  • 75. final class Courses implements CjmTrainingEnrolmentModelCourses { public function add(Course $course) : void { $this->courses[] = $course; } public function findByTitle(string $title): Course { return $this->courses[0]; } } @ciaranmcnulty | #symfony_live
  • 76. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class ServiceContext implements Context { /** * @When (only) :learner enrols on this course */ public function learnerEnrolsOnThisCourse(string $learner) { $this->courseEnrolments->enrol($learner, $this->course); } } @ciaranmcnulty | #symfony_live
  • 77. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class ServiceContext implements Context { /** * @Then this course will not be viable */ public function thisCourseWillNotBeViable() { assert($this->courseEnrolments->isCourseViable($this->course) == false); } } @ciaranmcnulty | #symfony_live
  • 78. Domain vs Service layer » Start by driving domain layer » Refactor to services when confidence grows » Drop back to domain layer when remodelling @ciaranmcnulty | #symfony_live
  • 80. Driving the UI layer » Simulate a browser with behat/minkextension » Interact with domain model via the UI » Ensures UI supports business actions » Slow, brittle, flakey... » Does not constrain API @ciaranmcnulty | #symfony_live
  • 81. Don't do this Scenario: Buying a pair of jeans Given I am on "/products/levi-501" When I click on "#add-form input[type=submit]" Then "#basket ul" should contain "jeans" @ciaranmcnulty | #symfony_live
  • 82. Mink » Browser driver abstraction » Supports Selenium, Goutte, Browserkit @ciaranmcnulty | #symfony_live
  • 83. # behat.yml endtoend: filters: tags: "@endtoend" suites: domain: false services: false endtoend: contexts: - EndToEndContext: courseEnrolments: "@cjm.training.enrolment.course_enrolments" @ciaranmcnulty | #symfony_live
  • 85. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class EndToEndContext implements RawMinkContext { /** * @Given :course was proposed with a class size of :min to :max people */ public function wasProposedWithAClassSizeOfToPeople(string $course, int $min, int $max) { $this->course = $course; $this->courseEnrolments->propose($course, $min, $max); } } @ciaranmcnulty | #symfony_live
  • 86. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class EndToEndContext implements RawMinkContext { /** * @When only :learner enrols on this course */ public function learnerEnrolsOnCourse(string $learner) { $this->visitPath('/courses/' . $this->course); $page = $this->getSession()->getPage(); $page->fillField('Your name', $learner); $page->pressButton('Enrol'); } } @ciaranmcnulty | #symfony_live
  • 87. Given "BDD for Beginners" was proposed with a class size of 2 to 3 people When only Alice enrols on this course Then this course will not be viable class EndToEndContext implements RawMinkContext { /** * @Then this course will not be viable */ public function thisCourseWillNotBeViable() { $this->visitPath('/courses/'.$this->course); $this->assertSession()->elementExists('css', '#not-viable-warning'); } } @ciaranmcnulty | #symfony_live
  • 88. Automating a real browser » Avoid or minimise » Orders of magnitude slower » Required for end-to-end with JS » Replace with JS cucumber stack @ciaranmcnulty | #symfony_live
  • 89. # behat.yml extensions: BehatMinkExtension: sessions: symfony: symfony2: ~ selenium2: browser: chrome capabilities: chrome: switches: - "--headless" - "--disable-gpu" @ciaranmcnulty | #symfony_live
  • 90. Summary » Drive domain objects directly to explore model » Refactor to services when model is stable » Add minimal UI coverage @ciaranmcnulty | #symfony_live
  • 91. Thanks » @ciaranmcnulty » @Inviqa » @PhpSpec » @BDDLondon github.com/ciaranmcnulty/behat-symfony-demo @ciaranmcnulty | #symfony_live