SlideShare a Scribd company logo
1 of 99
Download to read offline
Behat Best
Practices
Ciaran McNulty
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
Behaviour Driven
Development
@ciaranmcnulty | #scotphp17
BDD is the art of using examples in
conversations to illustrate behaviour
— Liz Keogh
@ciaranmcnulty | #scotphp17
BDD is the art of using
examples in
conversations to
illustrate behaviour
@ciaranmcnulty | #scotphp17
BDD is the art of using
examples in
conversations to
illustrate behaviour
@ciaranmcnulty | #scotphp17
BDD is the art of using
examples in
conversations to
illustrate behaviour
@ciaranmcnulty | #scotphp17
Example
2 - Something that serves to illustrate or explain a rule
— Wiktionary
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
Rule:
We charge our customers sales tax at a rate of 20%
@ciaranmcnulty | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
Capturing Examples
Input -> Rules -> Output
@ciaranmcnulty | #scotphp17
Capturing Examples
Input: When an action is taken
Output: Then an outcome should occur
@ciaranmcnulty | #scotphp17
Capturing Examples
When I buy a pair of Levi 501s
Then I am charged £32.99
@ciaranmcnulty | #scotphp17
Capturing Examples
Context: Given some situation
Input: When an action is taken
Output: Then an outcome should occur
@ciaranmcnulty | #scotphp17
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 | #scotphp17
Context Questioning
“Is there any other context which, when this event happens, will
produce a different outcome?” - Liz Keogh
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
Outcome Questioning
“Given this context, when this event happens, is there another outcome
that’s important? Something we missed, perhaps?” - Liz Keogh
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
BDD is not about
testing
@ciaranmcnulty | #scotphp17
BDD is also not about
requirement capture
@ciaranmcnulty | #scotphp17
Validating examples
(ok it is a bit about testing,
sometimes)
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
Behat
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
class Course
{
//...
public function isViable()
{
return $this->classSize->isViable($this->learners);
}
}
class ClassSize
{
//...
public function isViable(int $size)
{
return $size >= $this->min;
}
}
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
Driving the Service layer
» Configure services in test environment
» Inject services into Behat context (using an extension)
» Interact with domain model via the services
» Aligns service layer with business use cases
@ciaranmcnulty | #scotphp17
Suites
# behat.yml
default:
suites:
domain:
contexts: [ DomainContext ]
services:
contexts: [ ServiceContext ]
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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->courseId = $course;
$this->courseEnrolments->propose($course, $min, $max);
}
}
@ciaranmcnulty | #scotphp17
Symfony extension
# behat.yml
default:
suites:
domain:
contexts:
- DomainContext
services:
contexts:
- ServiceContext:
courseEnrolments: "@cjm.training.enrolment.course_enrolments"
extensions:
BehatSymfony2Extension: ~
@ciaranmcnulty | #scotphp17
PSR-11 support
» 'Native' support for instantiatable containers
» Extension point to plug in containers using other methods
» e.g. Roave/behat-psr11extension for Zend Expressive
@ciaranmcnulty | #scotphp17
Autowiring
Type hint services and get them automatically injected in:
» Constructors
» Step definitions
» Transformations
@ciaranmcnulty | #scotphp17
class CourseEnrolments
{
public function propose(string $title, int $minimum, int $maximum)
{
$this->courses->add(
Course::propose(
$title,
ClassSize::between($minimum, $maximum)
)
);
}
}
@ciaranmcnulty | #scotphp17
Infrastructure
» Using real infrastructure is slow
» Using fake infrastructure can lower confidence
» Use fake infrastructure but sync via contract tests
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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->courseId);
}
}
@ciaranmcnulty | #scotphp17
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->courseId) == false);
}
}
@ciaranmcnulty | #scotphp17
Domain vs Service layer
» Start by driving domain layer
» Refactor to services when confidence grows
» Drop back to domain layer when remodelling
@ciaranmcnulty | #scotphp17
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
Mink
» Browser driver abstraction
» Supports Selenium, Goutte, Browserkit
@ciaranmcnulty | #scotphp17
# behat.yml
deafult:
suites:
domain:
contexts:
- DomainContext
services:
contexts:
- ServiceContext:
courseEnrolments: "@cjm.training.enrolment.course_enrolments"
endtoend:
filters:
tags: "@endtoend"
contexts:
- EndToEndContext:
courseEnrolments: "@cjm.training.enrolment.course_enrolments"
@ciaranmcnulty | #scotphp17
Mink with Symfony
# behat.yml
extensions:
BehatSymfony2Extension: ~
BehatMinkExtension:
sessions:
symfony:
symfony2: ~
@ciaranmcnulty | #scotphp17
Mink with PSR-7
# behat.yml
extensions:
CjmBehatPsr7Extension:
app: %paths.base%/path/to/file.php
BehatMinkExtension:
sessions:
psr:
psr-7: ~
@ciaranmcnulty | #scotphp17
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 | #scotphp17
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 | #scotphp17
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 | #scotphp17
Automating a real browser
» Avoid or minimise
» Orders of magnitude slower
» Required for end-to-end with JS
» Can often be replaced with JS cucumber stack
@ciaranmcnulty | #scotphp17
# behat.yml
extensions:
BehatMinkExtension:
sessions:
symfony:
symfony2: ~
selenium2:
browser: chrome
capabilities:
chrome:
switches:
- "--headless"
- "--disable-gpu"
@ciaranmcnulty | #scotphp17
Things I'm trying out
» dmore/behat-chrome-extension
» aligning end-to-end tests to service APIs
» property-based testing
@ciaranmcnulty | #scotphp17
Summary
» Drive domain objects directly to explore model
» Refactor to services when model is stable
» Add minimal UI coverage
@ciaranmcnulty | #scotphp17
Thanks
» @ciaranmcnulty
» @Inviqa
» @PhpSpec
» @BDDLondon
» @SymfonyUK
github.com/ciaranmcnulty/behat-symfony-demo
joind.in/event/scotlandphp-2017/behat-best-practices
@ciaranmcnulty | #scotphp17

More Related Content

What's hot

Asynchronous javascript
 Asynchronous javascript Asynchronous javascript
Asynchronous javascriptEman Mohamed
 
Test your microservices with REST-Assured
Test your microservices with REST-AssuredTest your microservices with REST-Assured
Test your microservices with REST-AssuredMichel Schudel
 
Saving Time By Testing With Jest
Saving Time By Testing With JestSaving Time By Testing With Jest
Saving Time By Testing With JestBen McCormick
 
React web development
React web developmentReact web development
React web developmentRully Ramanda
 
Envoy 를 이용한 코드 배포 자동화
Envoy 를 이용한 코드 배포 자동화Envoy 를 이용한 코드 배포 자동화
Envoy 를 이용한 코드 배포 자동화Juwon Kim
 
A Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on AndroidA Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on AndroidOutware Mobile
 
Coding principles
Coding principles Coding principles
Coding principles DevAdnani
 
Painless JavaScript Testing with Jest
Painless JavaScript Testing with JestPainless JavaScript Testing with Jest
Painless JavaScript Testing with JestMichał Pierzchała
 
REST API testing with SpecFlow
REST API testing with SpecFlowREST API testing with SpecFlow
REST API testing with SpecFlowAiste Stikliute
 
Introducing GitLab (September 2018)
Introducing GitLab (September 2018)Introducing GitLab (September 2018)
Introducing GitLab (September 2018)Noa Harel
 
Contract Testing of WebSockets: Functional Programming Is Taking the Stage
Contract Testing of WebSockets: Functional Programming Is Taking the StageContract Testing of WebSockets: Functional Programming Is Taking the Stage
Contract Testing of WebSockets: Functional Programming Is Taking the StageNordic APIs
 
신입 웹 개발자 포트폴리오 / 댓글 게시판
신입 웹 개발자 포트폴리오 / 댓글 게시판신입 웹 개발자 포트폴리오 / 댓글 게시판
신입 웹 개발자 포트폴리오 / 댓글 게시판hyeonjae Cheon
 
Microservices Docker Kubernetes Istio Kanban DevOps SRE
Microservices Docker Kubernetes Istio Kanban DevOps SREMicroservices Docker Kubernetes Istio Kanban DevOps SRE
Microservices Docker Kubernetes Istio Kanban DevOps SREAraf Karsh Hamid
 
Clean Architecture Applications in Python
Clean Architecture Applications in PythonClean Architecture Applications in Python
Clean Architecture Applications in PythonSubhash Bhushan
 
Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門
Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門
Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門tamtam180
 
Introducing Clean Architecture
Introducing Clean ArchitectureIntroducing Clean Architecture
Introducing Clean ArchitectureRoc Boronat
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기SeungYong Oh
 
CSS Grid Layout for Topconf, Linz
CSS Grid Layout for Topconf, LinzCSS Grid Layout for Topconf, Linz
CSS Grid Layout for Topconf, LinzRachel Andrew
 

What's hot (20)

Asynchronous javascript
 Asynchronous javascript Asynchronous javascript
Asynchronous javascript
 
Test your microservices with REST-Assured
Test your microservices with REST-AssuredTest your microservices with REST-Assured
Test your microservices with REST-Assured
 
Saving Time By Testing With Jest
Saving Time By Testing With JestSaving Time By Testing With Jest
Saving Time By Testing With Jest
 
React web development
React web developmentReact web development
React web development
 
Envoy 를 이용한 코드 배포 자동화
Envoy 를 이용한 코드 배포 자동화Envoy 를 이용한 코드 배포 자동화
Envoy 를 이용한 코드 배포 자동화
 
A Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on AndroidA Separation of Concerns: Clean Architecture on Android
A Separation of Concerns: Clean Architecture on Android
 
Solid principles
Solid principlesSolid principles
Solid principles
 
Coding principles
Coding principles Coding principles
Coding principles
 
Painless JavaScript Testing with Jest
Painless JavaScript Testing with JestPainless JavaScript Testing with Jest
Painless JavaScript Testing with Jest
 
Jenkins-CI
Jenkins-CIJenkins-CI
Jenkins-CI
 
REST API testing with SpecFlow
REST API testing with SpecFlowREST API testing with SpecFlow
REST API testing with SpecFlow
 
Introducing GitLab (September 2018)
Introducing GitLab (September 2018)Introducing GitLab (September 2018)
Introducing GitLab (September 2018)
 
Contract Testing of WebSockets: Functional Programming Is Taking the Stage
Contract Testing of WebSockets: Functional Programming Is Taking the StageContract Testing of WebSockets: Functional Programming Is Taking the Stage
Contract Testing of WebSockets: Functional Programming Is Taking the Stage
 
신입 웹 개발자 포트폴리오 / 댓글 게시판
신입 웹 개발자 포트폴리오 / 댓글 게시판신입 웹 개발자 포트폴리오 / 댓글 게시판
신입 웹 개발자 포트폴리오 / 댓글 게시판
 
Microservices Docker Kubernetes Istio Kanban DevOps SRE
Microservices Docker Kubernetes Istio Kanban DevOps SREMicroservices Docker Kubernetes Istio Kanban DevOps SRE
Microservices Docker Kubernetes Istio Kanban DevOps SRE
 
Clean Architecture Applications in Python
Clean Architecture Applications in PythonClean Architecture Applications in Python
Clean Architecture Applications in Python
 
Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門
Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門
Introduction httpClient on Java11 / Java11時代のHTTPアクセス再入門
 
Introducing Clean Architecture
Introducing Clean ArchitectureIntroducing Clean Architecture
Introducing Clean Architecture
 
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기[NDC17] Kubernetes로 개발서버 간단히 찍어내기
[NDC17] Kubernetes로 개발서버 간단히 찍어내기
 
CSS Grid Layout for Topconf, Linz
CSS Grid Layout for Topconf, LinzCSS Grid Layout for Topconf, Linz
CSS Grid Layout for Topconf, Linz
 

More from CiaranMcNulty

Greener web development at PHP London
Greener web development at PHP LondonGreener web development at PHP London
Greener web development at PHP LondonCiaranMcNulty
 
Doodle Driven Development
Doodle Driven DevelopmentDoodle Driven Development
Doodle Driven DevelopmentCiaranMcNulty
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with SymfonyCiaranMcNulty
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with SymfonyCiaranMcNulty
 
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 (20)

Greener web development at PHP London
Greener web development at PHP LondonGreener web development at PHP London
Greener web development at PHP London
 
Doodle Driven Development
Doodle Driven DevelopmentDoodle Driven Development
Doodle Driven Development
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with Symfony
 
Behat Best Practices with Symfony
Behat Best Practices with SymfonyBehat Best Practices with Symfony
Behat Best Practices with Symfony
 
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

Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
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
 
Engage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyEngage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyFrank van der Linden
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningVitsRangannavar
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfPower Karaoke
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsMehedi Hasan Shohan
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 

Recently uploaded (20)

Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
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)
 
Engage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyEngage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The Ugly
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learning
 
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdf
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software Solutions
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 

Behat Best Practices

  • 6. BDD is the art of using examples in conversations to illustrate behaviour — Liz Keogh @ciaranmcnulty | #scotphp17
  • 7. BDD is the art of using examples in conversations to illustrate behaviour @ciaranmcnulty | #scotphp17
  • 8. BDD is the art of using examples in conversations to illustrate behaviour @ciaranmcnulty | #scotphp17
  • 9. BDD is the art of using examples in conversations to illustrate behaviour @ciaranmcnulty | #scotphp17
  • 10. Example 2 - Something that serves to illustrate or explain a rule — Wiktionary @ciaranmcnulty | #scotphp17
  • 12. Rule: We charge our customers sales tax at a rate of 20% @ciaranmcnulty | #scotphp17
  • 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 | #scotphp17
  • 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 | #scotphp17
  • 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 | #scotphp17
  • 21. Capturing Examples Input -> Rules -> Output @ciaranmcnulty | #scotphp17
  • 22. Capturing Examples Input: When an action is taken Output: Then an outcome should occur @ciaranmcnulty | #scotphp17
  • 23. Capturing Examples When I buy a pair of Levi 501s Then I am charged £32.99 @ciaranmcnulty | #scotphp17
  • 24. Capturing Examples Context: Given some situation Input: When an action is taken Output: Then an outcome should occur @ciaranmcnulty | #scotphp17
  • 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 | #scotphp17
  • 26. Context Questioning “Is there any other context which, when this event happens, will produce a different outcome?” - Liz Keogh @ciaranmcnulty | #scotphp17
  • 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 | #scotphp17
  • 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 | #scotphp17
  • 29. Outcome Questioning “Given this context, when this event happens, is there another outcome that’s important? Something we missed, perhaps?” - Liz Keogh @ciaranmcnulty | #scotphp17
  • 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 | #scotphp17
  • 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 | #scotphp17
  • 32. BDD is not about testing @ciaranmcnulty | #scotphp17
  • 33. BDD is also not about requirement capture @ciaranmcnulty | #scotphp17
  • 34. Validating examples (ok it is a bit about testing, sometimes) @ciaranmcnulty | #scotphp17
  • 38. 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 | #scotphp17
  • 39. 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 | #scotphp17
  • 40. 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 | #scotphp17
  • 41. 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 | #scotphp17
  • 42. 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 | #scotphp17
  • 43. 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 | #scotphp17
  • 45. 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 | #scotphp17
  • 48. 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 | #scotphp17
  • 50. 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 | #scotphp17
  • 53. 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 | #scotphp17
  • 54. 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 | #scotphp17
  • 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 { /** * @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 | #scotphp17
  • 57. 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 | #scotphp17
  • 58. 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 | #scotphp17
  • 60. 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 | #scotphp17
  • 61. class Course { //... public function isViable() { return $this->classSize->isViable($this->learners); } } class ClassSize { //... public function isViable(int $size) { return $size >= $this->min; } } @ciaranmcnulty | #scotphp17
  • 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 { /** * @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 | #scotphp17
  • 64. 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 | #scotphp17
  • 65. 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 | #scotphp17
  • 67. 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 | #scotphp17
  • 70. Driving the Service layer » Configure services in test environment » Inject services into Behat context (using an extension) » Interact with domain model via the services » Aligns service layer with business use cases @ciaranmcnulty | #scotphp17
  • 71. Suites # behat.yml default: suites: domain: contexts: [ DomainContext ] services: contexts: [ ServiceContext ] @ciaranmcnulty | #scotphp17
  • 72. 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 | #scotphp17
  • 73. 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->courseId = $course; $this->courseEnrolments->propose($course, $min, $max); } } @ciaranmcnulty | #scotphp17
  • 74. Symfony extension # behat.yml default: suites: domain: contexts: - DomainContext services: contexts: - ServiceContext: courseEnrolments: "@cjm.training.enrolment.course_enrolments" extensions: BehatSymfony2Extension: ~ @ciaranmcnulty | #scotphp17
  • 75. PSR-11 support » 'Native' support for instantiatable containers » Extension point to plug in containers using other methods » e.g. Roave/behat-psr11extension for Zend Expressive @ciaranmcnulty | #scotphp17
  • 76. Autowiring Type hint services and get them automatically injected in: » Constructors » Step definitions » Transformations @ciaranmcnulty | #scotphp17
  • 77. class CourseEnrolments { public function propose(string $title, int $minimum, int $maximum) { $this->courses->add( Course::propose( $title, ClassSize::between($minimum, $maximum) ) ); } } @ciaranmcnulty | #scotphp17
  • 78. Infrastructure » Using real infrastructure is slow » Using fake infrastructure can lower confidence » Use fake infrastructure but sync via contract tests @ciaranmcnulty | #scotphp17
  • 81. 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 | #scotphp17
  • 82. 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->courseId); } } @ciaranmcnulty | #scotphp17
  • 83. 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->courseId) == false); } } @ciaranmcnulty | #scotphp17
  • 84. Domain vs Service layer » Start by driving domain layer » Refactor to services when confidence grows » Drop back to domain layer when remodelling @ciaranmcnulty | #scotphp17
  • 86. 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 | #scotphp17
  • 87. 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 | #scotphp17
  • 88. Mink » Browser driver abstraction » Supports Selenium, Goutte, Browserkit @ciaranmcnulty | #scotphp17
  • 89. # behat.yml deafult: suites: domain: contexts: - DomainContext services: contexts: - ServiceContext: courseEnrolments: "@cjm.training.enrolment.course_enrolments" endtoend: filters: tags: "@endtoend" contexts: - EndToEndContext: courseEnrolments: "@cjm.training.enrolment.course_enrolments" @ciaranmcnulty | #scotphp17
  • 90. Mink with Symfony # behat.yml extensions: BehatSymfony2Extension: ~ BehatMinkExtension: sessions: symfony: symfony2: ~ @ciaranmcnulty | #scotphp17
  • 91. Mink with PSR-7 # behat.yml extensions: CjmBehatPsr7Extension: app: %paths.base%/path/to/file.php BehatMinkExtension: sessions: psr: psr-7: ~ @ciaranmcnulty | #scotphp17
  • 92. 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 | #scotphp17
  • 93. 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 | #scotphp17
  • 94. 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 | #scotphp17
  • 95. Automating a real browser » Avoid or minimise » Orders of magnitude slower » Required for end-to-end with JS » Can often be replaced with JS cucumber stack @ciaranmcnulty | #scotphp17
  • 96. # behat.yml extensions: BehatMinkExtension: sessions: symfony: symfony2: ~ selenium2: browser: chrome capabilities: chrome: switches: - "--headless" - "--disable-gpu" @ciaranmcnulty | #scotphp17
  • 97. Things I'm trying out » dmore/behat-chrome-extension » aligning end-to-end tests to service APIs » property-based testing @ciaranmcnulty | #scotphp17
  • 98. Summary » Drive domain objects directly to explore model » Refactor to services when model is stable » Add minimal UI coverage @ciaranmcnulty | #scotphp17
  • 99. Thanks » @ciaranmcnulty » @Inviqa » @PhpSpec » @BDDLondon » @SymfonyUK github.com/ciaranmcnulty/behat-symfony-demo joind.in/event/scotlandphp-2017/behat-best-practices @ciaranmcnulty | #scotphp17