SlideShare a Scribd company logo
1 of 64
Download to read offline
Building a Test
Pyramid: Symfony
testing strategies
with Ciaran McNulty
Before we start:
You must test your
applications!
What kind of tests to use?
» Manual testing
» Acceptance testing
» Unit testing
» Integration testing
» End-to-end testing
» Black box / white box
What tools to use?
» PHPUnit
» PhpSpec
» Behat
» Codeception
» BrowserKit / Webcrawler
Question:
Why can't someone
tell me which one to
use?
Answer:
Because there is no
best answer that fits
all cases
You have to find the
Testing different layers
Introducing the pyramid
» Defined by Mike Cohn in Succeeding with Agile
» For understanding different layers of testing
UI layer tests
» Test the whole application end-to-end
» Sensitive to UI changes
» Aligned with acceptance criteria
» Does not require good code
» Probably slow
e.g. Open a browser, fill in the form and submit it
Service layer tests
» Test the application logic by making service calls
» Faster than UI testing
» Aligned with acceptance criteria
» Mostly written in the target language
» Requires high-level services to exist
e.g. Instantiate the Calculator service and get it to add two
numbers
Unit level tests
» Test individual classes
» Much faster than service level testing
» Very fine level of detail
» Requires good design
Why a pyramid?
» Each layer builds on the one below it
» Lower layers are faster to run
» Higher levels are slower and more brittle
» Have more tests at the bottom than at the top
Why do you want
tests?
The answer will affect the type of
tests you write
If you want existing
features from
breaking
... write Regression Tests
Regression tests
» Check that behaviour hasn't changed
» Easiest to apply at the UI level
» ALL tests become regression tests eventually
'Legacy' code
class BasketController extends Controller
{
public function addAction(Request $request)
{
$productId = $request->attributes->get('product_id');
$basket = $request->getSession()->get('basket_context')->getCurrent();
$products = $basket->getProducts();
$products[] = $productId;
$basket->setProducts($products);
return $this->render('::basket.html.twig', ['basket' => $basket]);
}
}
Regression testing with PHPUnit
+ BrowserKit
class PostControllerTest extends WebTestCase
{
public function testShowPost()
{
$client = static::createClient();
$crawler = $client->request('GET', '/products/1234');
$form = $crawler->selectButton('Add to basket')->form();
$client->submit($form, ['id'=>1234]);
$product = $crawler->filter('html:contains("Product: 1234")');
$this->assertCount(1, $product);
}
}
Regression testing with
Codeception
$I = new AcceptanceTester($scenario);
$I->amOnPage('/products/1234');
$I->click('Add to basket');
$I->see('Product: 1234');
Regression testing with Behat +
MinkExtension
Scenario: Adding a product to the basket
Given I am on "/product/1234"
When I click "Add to Basket"
Then I should see "Product: 1234"
Regression testing with Ghost
Inspector
When regression testing
» Use a tool that gets you coverage quickly and easily
» Plan to phase out regression tests later
» Lean towards testing end-to-end
» Recognise they will be hard to maintain
If you want to match
customer
requirements better
... write Acceptance Tests
Acceptance Tests
» Check the system does what the customer wants
» Are aligned with customer language and intention
» Write them in English (or another language) first
» Can be tested at the UI or Service level
Start with an
example-led
conversation
... before you start working on it
... but not too long before
» "What should the system do when X happens?"
» "Does Y always happen when X?"
» "What assumptions Z are causing Y to be the outcome?"
» "Given Z when X then Y"
» "What other things aside from Y might happen?"
» "What if...?"
Write the examples
out in business-
readable tests
Try and make the code look like
the natural conversation you had
Easiest to test through
the User Interface
UI Acceptance testing with
PHPUnit + BrowserKit
class PostControllerTest extends WebTestCase
{
public function testAddingProductToTheBasket()
{
$this->addProductToBasket(1234);
$this->productShouldBeShownInBasket(1234);
}
private function addProductToBasket($productId)
{
//... browser automation code
}
private function productShouldBeShownInBasket($productId)
{
//... browser automation code
}
}
UI Acceptance testing with
Codeception
$I = new AcceptanceTester($scenario);
$I->amGoingTo('Add a product to the basket');
$I->amOnPage('/products/1234');
$I->click('Add to basket');
$I->expectTo('see the product in the basket');
$I->see('Product: 1234');
UI Acceptance testing with Behat
+ MinkExtension
Scenario: Adding a product to the basket
When I add product 1234 to the basket
Then I should see product 1234 in the basket
UI Acceptance testing with Behat
+ MinkExtension
/**
* @When I add product :productId to the basket
*/
public function iAddProduct($productId)
{
$this->visitUrl('/product/' . $productId);
$this->getSession()->clickButton('Add to Basket');
}
/**
* @Then I should see product :productId in the basket
*/
public function iShouldSeeProduct($productId)
{
$this->assertSession()->elementContains('css', '#basket', 'Product: ' . $productId);
}
Acceptance testing
through the UI is slow
and brittle
To test at the service layer, we
need to introduce services
'Legacy' code
class BasketController extends Controller
{
public function addAction(Request $request)
{
$productId = $request->attributes->get('product_id');
$basket = $request->getSession()->get('basket_context')->getCurrent();
$products = $basket->getProducts();
$products[] = $productId;
$basket->setProducts($products);
return $this->render('::basket.html.twig', ['basket' => $basket]);
}
}
'Service-oriented' code
class BasketController extends Controller
{
public function addAction(Request $request)
{
$basket = $this->get('basket_context')->getCurrent();
$productId = $request->attributes->get('product_id');
$basket->addProduct($productId);
return $this->render('::basket.html.twig', ['basket' => $basket]);
}
}
A very small change
but now the business logic is out of
the controller
Service layer Acceptance testing
with PHPUnit
class PostControllerTest extends PHPUnit_Framework_TestCase
{
public function testAddingProductToTheBasket()
{
$basket = new Basket(new BasketArrayStorage());
$basket->addProduct(1234);
$this->assertContains(1234, $basket->getProducts());
}
}
Service layer acceptance testing
with Behat + MinkExtension
Scenario: Adding a product to the basket
When I add product 1234 to the basket
Then I should see product 1234 in the basket
Service layer acceptance testing
with Behat
/**
* @When I add product :productId to the basket
*/
public function iAddProduct($productId)
{
$this->basket = new Basket(new BasketArrayStorage());
$this->basket->addProduct($productId);
}
/**
* @Then I should see product :productId in the basket
*/
public function iShouldSeeProduct($productId)
{
assert(in_array($productId, $this->basket->getProducts());
}
When all of the
acceptance tests are
running against the
Service layer
... how many also need to be run
through the UI?
Symfony is a controller for your
app
If you test everything
through services
... you only need
enough UI tests to be
sure the UI is
Multiple Behat suites
Scenario: Adding a product to the basket
When I add product 1234 to the basket
Then I should see product 1234 in the basket
Scenario: Adding a product that is already there
Given I have already added product 1234 to the basket
When I add product 1234 to the basket
Then I should see 2 instances of product 1234 in the basket
@ui
Scenario: Adding two products to my basket
Given I have already added product 4567 to the basket
When I add product 1234 to the basket
Then I should see product 4567 in the basket
And I should also see product 1234 in the basket
Multiple Behat suites
default:
suites:
ui:
contexts: [ UiContext ]
filters: { tags: @ui }
service:
contexts: [ ServiceContext ]
If you want the design
of your code to be
better
... write Unit Tests
Unit Tests
» Check that a class does what you expect
» Use a tool that makes it easy to test classes in isolation
» Move towards writing them first
» Unit tests force you to have good design
» Probably too small to reflect acceptance criteria
Unit tests are too granular
Customer: "The engine needs to produce 500bhp"
Engineer: "What should the diameter of the main drive shaft
be?"
Unit testing in PHPUnit
class BasketTest extends PHPUnit_Framework_Testcase
{
public function testGetsProductsFromStorage()
{
$storage = $this->getMock('BasketStorage');
$storage->expect($this->once())
->method('persistProducts')
->with([1234]);
$basket = new Basket($storage);
$basket->addProduct(1234);
}
}
Unit testing in PhpSpec
class BasketSpec extends ObjectBehavior
{
function it_gets_products_from_storage(BasketStorage $storage)
{
$this->beConstructedWith($storage);
$this->addProduct(1234);
$storage->persistProducts([1234])->shouldHaveBeenCalled([1234]);
}
}
Unit test
... code that is responsible for
business logic
... not code that interacts with
infrastructure including Symfony
You can unit test
interactions with
Symfony (e.g.
controllers)
You shouldn't need to if you have
acceptance tests
Coupled architecture
Unit testing third party
dependencies
class FileHandlerSpec extends ObjectBehaviour
{
public function it_uploads_data_to_the_cloud_when_valid(
CloudApi $client, FileValidator $validator, File $file
)
{
$this->beConstructedWith($client, $validator);
$validator->validate($file)->willReturn(true);
$client->startUpload()->shouldBeCalled();
$client->uploadData(Argument::any())->shouldBeCalled();
$client->uploadSuccessful()->willReturn(true);
$this->process($file)->shouldReturn(true);
}
}
Coupled architecture
Layered architecture
Testing layered architecture
class FileHandlerSpec extends ObjectBehaviour
{
public function it_uploads_data_to_the_cloud_when_valid(
FileStore $filestore, FileValidator $validator, File $file
)
{
$this->beConstructedWith($filestore, $validator);
$validator->validate($file)->willReturn(true);
$this->process($file);
$filestore->store($file)->shouldHaveBeenCalled();
}
}
Testing layered architecture
class CloudFilestoreTest extends PHPUnit_Framework_TestCase
{
function testItStoresFiles()
{
$testCredentials = …
$file = new File(…);
$apiClient = new CloudApi($testCredentials);
$filestore = new CloudFileStore($apiClient);
$filestore->store($file);
$this->assertTrue($apiClient->fileExists(…));
}
}
Testing layered architecture
To build your
pyramid...
Have isolated unit-
tested objects
representing your core
business logic
10,000s of tests running in <10ms
each
Have acceptance tests
at the service level
1,000s of tests running in <100ms
each
Have the bare
minimum of
acceptance tests at the
UI level
10s of tests running in <10s each
Thank You!
Any questions?
https://joind.in/talk/view/14972
@ciaranmcnulty
ciaran@sessiondigital.co.uk

More Related Content

What's hot

What's hot (20)

Solucionando a Teoria do Caos com Cypress.io
Solucionando a Teoria do Caos com Cypress.ioSolucionando a Teoria do Caos com Cypress.io
Solucionando a Teoria do Caos com Cypress.io
 
炎炎夏日學 Android 課程 - Part1: Kotlin 語法介紹
炎炎夏日學 Android 課程 -  Part1: Kotlin 語法介紹炎炎夏日學 Android 課程 -  Part1: Kotlin 語法介紹
炎炎夏日學 Android 課程 - Part1: Kotlin 語法介紹
 
Django Celery
Django Celery Django Celery
Django Celery
 
Advanced JavaScript
Advanced JavaScriptAdvanced JavaScript
Advanced JavaScript
 
Coroutines in Kotlin. In-depth review
Coroutines in Kotlin. In-depth reviewCoroutines in Kotlin. In-depth review
Coroutines in Kotlin. In-depth review
 
Spring Framework - AOP
Spring Framework - AOPSpring Framework - AOP
Spring Framework - AOP
 
Java 8 Lambda Built-in Functional Interfaces
Java 8 Lambda Built-in Functional InterfacesJava 8 Lambda Built-in Functional Interfaces
Java 8 Lambda Built-in Functional Interfaces
 
Javascript Basics
Javascript BasicsJavascript Basics
Javascript Basics
 
Pipeline oriented programming
Pipeline oriented programmingPipeline oriented programming
Pipeline oriented programming
 
Url Connection
Url ConnectionUrl Connection
Url Connection
 
ASP.NET Core MVC with EF Core code first
ASP.NET Core MVC with EF Core code firstASP.NET Core MVC with EF Core code first
ASP.NET Core MVC with EF Core code first
 
Advanced javascript
Advanced javascriptAdvanced javascript
Advanced javascript
 
Fundamental of Node.JS - Internship Presentation - Week7
Fundamental of Node.JS - Internship Presentation - Week7Fundamental of Node.JS - Internship Presentation - Week7
Fundamental of Node.JS - Internship Presentation - Week7
 
Functional solid
Functional solidFunctional solid
Functional solid
 
The Power of Composition
The Power of CompositionThe Power of Composition
The Power of Composition
 
Automação e virtualização de serviços
Automação e virtualização de serviçosAutomação e virtualização de serviços
Automação e virtualização de serviços
 
Mongoose and MongoDB 101
Mongoose and MongoDB 101Mongoose and MongoDB 101
Mongoose and MongoDB 101
 
[GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式
[GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式[GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式
[GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式
 
Django Seminar
Django SeminarDjango Seminar
Django Seminar
 
Software Design Patterns in Laravel by Phill Sparks
Software Design Patterns in Laravel by Phill SparksSoftware Design Patterns in Laravel by Phill Sparks
Software Design Patterns in Laravel by Phill Sparks
 

Similar to Building a Pyramid: Symfony Testing Strategies

Agile methodologies based on BDD and CI by Nikolai Shevchenko
Agile methodologies based on BDD and CI by Nikolai ShevchenkoAgile methodologies based on BDD and CI by Nikolai Shevchenko
Agile methodologies based on BDD and CI by Nikolai Shevchenko
Moldova ICT Summit
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
Mobile Developer Day
 

Similar to Building a Pyramid: Symfony Testing Strategies (20)

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
 
Agile methodologies based on BDD and CI by Nikolai Shevchenko
Agile methodologies based on BDD and CI by Nikolai ShevchenkoAgile methodologies based on BDD and CI by Nikolai Shevchenko
Agile methodologies based on BDD and CI by Nikolai Shevchenko
 
A test framework out of the box - Geb for Web and mobile
A test framework out of the box - Geb for Web and mobileA test framework out of the box - Geb for Web and mobile
A test framework out of the box - Geb for Web and mobile
 
Testing C# and ASP.net using Ruby
Testing C# and ASP.net using RubyTesting C# and ASP.net using Ruby
Testing C# and ASP.net using Ruby
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
 
Serverless Angular, Material, Firebase and Google Cloud applications
Serverless Angular, Material, Firebase and Google Cloud applicationsServerless Angular, Material, Firebase and Google Cloud applications
Serverless Angular, Material, Firebase and Google Cloud applications
 
Clean tests good tests
Clean tests   good testsClean tests   good tests
Clean tests good tests
 
Better Testing With PHP Unit
Better Testing With PHP UnitBetter Testing With PHP Unit
Better Testing With PHP Unit
 
How to optimize background processes - when Sylius meets Blackfire
How to optimize background processes - when Sylius meets BlackfireHow to optimize background processes - when Sylius meets Blackfire
How to optimize background processes - when Sylius meets Blackfire
 
Altitude San Francisco 2018: Testing with Fastly Workshop
Altitude San Francisco 2018: Testing with Fastly WorkshopAltitude San Francisco 2018: Testing with Fastly Workshop
Altitude San Francisco 2018: Testing with Fastly Workshop
 
Selenium my sql and junit user guide
Selenium my sql and junit user guideSelenium my sql and junit user guide
Selenium my sql and junit user guide
 
Evolve your coding with some BDD
Evolve your coding with some BDDEvolve your coding with some BDD
Evolve your coding with some BDD
 
3 Ways to test your ColdFusion API - 2017 Adobe CF Summit
3 Ways to test your ColdFusion API - 2017 Adobe CF Summit3 Ways to test your ColdFusion API - 2017 Adobe CF Summit
3 Ways to test your ColdFusion API - 2017 Adobe CF Summit
 
From framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytvFrom framework coupled code to #microservices through #DDD /by @codelytv
From framework coupled code to #microservices through #DDD /by @codelytv
 
Security Testing
Security TestingSecurity Testing
Security Testing
 
Code your Own: Authentication Provider for Blackboard Learn
Code your Own: Authentication Provider for Blackboard LearnCode your Own: Authentication Provider for Blackboard Learn
Code your Own: Authentication Provider for Blackboard Learn
 
Asp netmvc e03
Asp netmvc e03Asp netmvc e03
Asp netmvc e03
 
Workshop: Building Vaadin add-ons
Workshop: Building Vaadin add-onsWorkshop: Building Vaadin add-ons
Workshop: Building Vaadin add-ons
 
Bdd with Cucumber and Mocha
Bdd with Cucumber and MochaBdd with Cucumber and Mocha
Bdd with Cucumber and Mocha
 
BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
BDD, ATDD, Page Objects: The Road to Sustainable Web TestingBDD, ATDD, Page Objects: The Road to Sustainable Web Testing
BDD, ATDD, Page Objects: The Road to Sustainable Web Testing
 

More from CiaranMcNulty

More from CiaranMcNulty (19)

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
Behat Best PracticesBehat Best Practices
Behat Best Practices
 
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
 
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
 
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

“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
Muhammad Subhan
 
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
panagenda
 
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
FIDO Alliance
 

Recently uploaded (20)

Generative AI Use Cases and Applications.pdf
Generative AI Use Cases and Applications.pdfGenerative AI Use Cases and Applications.pdf
Generative AI Use Cases and Applications.pdf
 
Long journey of Ruby Standard library at RubyKaigi 2024
Long journey of Ruby Standard library at RubyKaigi 2024Long journey of Ruby Standard library at RubyKaigi 2024
Long journey of Ruby Standard library at RubyKaigi 2024
 
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
 
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
 
Working together SRE & Platform Engineering
Working together SRE & Platform EngineeringWorking together SRE & Platform Engineering
Working together SRE & Platform Engineering
 
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
Event-Driven Architecture Masterclass: Integrating Distributed Data Stores Ac...
 
ADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptx
 
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
 
How we scaled to 80K users by doing nothing!.pdf
How we scaled to 80K users by doing nothing!.pdfHow we scaled to 80K users by doing nothing!.pdf
How we scaled to 80K users by doing nothing!.pdf
 
Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptx
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?
 
2024 May Patch Tuesday
2024 May Patch Tuesday2024 May Patch Tuesday
2024 May Patch Tuesday
 
The Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and InsightThe Zero-ETL Approach: Enhancing Data Agility and Insight
The Zero-ETL Approach: Enhancing Data Agility and Insight
 
Microsoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireMicrosoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - Questionnaire
 
Syngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdfSyngulon - Selection technology May 2024.pdf
Syngulon - Selection technology May 2024.pdf
 
(Explainable) Data-Centric AI: what are you explaininhg, and to whom?
(Explainable) Data-Centric AI: what are you explaininhg, and to whom?(Explainable) Data-Centric AI: what are you explaininhg, and to whom?
(Explainable) Data-Centric AI: what are you explaininhg, and to whom?
 
Design and Development of a Provenance Capture Platform for Data Science
Design and Development of a Provenance Capture Platform for Data ScienceDesign and Development of a Provenance Capture Platform for Data Science
Design and Development of a Provenance Capture Platform for Data Science
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
 
Extensible Python: Robustness through Addition - PyCon 2024
Extensible Python: Robustness through Addition - PyCon 2024Extensible Python: Robustness through Addition - PyCon 2024
Extensible Python: Robustness through Addition - PyCon 2024
 
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
 

Building a Pyramid: Symfony Testing Strategies

  • 1. Building a Test Pyramid: Symfony testing strategies with Ciaran McNulty
  • 2. Before we start: You must test your applications!
  • 3. What kind of tests to use? » Manual testing » Acceptance testing » Unit testing » Integration testing » End-to-end testing » Black box / white box
  • 4. What tools to use? » PHPUnit » PhpSpec » Behat » Codeception » BrowserKit / Webcrawler
  • 5. Question: Why can't someone tell me which one to use?
  • 6. Answer: Because there is no best answer that fits all cases You have to find the
  • 7. Testing different layers Introducing the pyramid » Defined by Mike Cohn in Succeeding with Agile » For understanding different layers of testing
  • 8.
  • 9. UI layer tests » Test the whole application end-to-end » Sensitive to UI changes » Aligned with acceptance criteria » Does not require good code » Probably slow e.g. Open a browser, fill in the form and submit it
  • 10. Service layer tests » Test the application logic by making service calls » Faster than UI testing » Aligned with acceptance criteria » Mostly written in the target language » Requires high-level services to exist e.g. Instantiate the Calculator service and get it to add two numbers
  • 11. Unit level tests » Test individual classes » Much faster than service level testing » Very fine level of detail » Requires good design
  • 12. Why a pyramid? » Each layer builds on the one below it » Lower layers are faster to run » Higher levels are slower and more brittle » Have more tests at the bottom than at the top
  • 13. Why do you want tests? The answer will affect the type of tests you write
  • 14. If you want existing features from breaking ... write Regression Tests
  • 15. Regression tests » Check that behaviour hasn't changed » Easiest to apply at the UI level » ALL tests become regression tests eventually
  • 16. 'Legacy' code class BasketController extends Controller { public function addAction(Request $request) { $productId = $request->attributes->get('product_id'); $basket = $request->getSession()->get('basket_context')->getCurrent(); $products = $basket->getProducts(); $products[] = $productId; $basket->setProducts($products); return $this->render('::basket.html.twig', ['basket' => $basket]); } }
  • 17. Regression testing with PHPUnit + BrowserKit class PostControllerTest extends WebTestCase { public function testShowPost() { $client = static::createClient(); $crawler = $client->request('GET', '/products/1234'); $form = $crawler->selectButton('Add to basket')->form(); $client->submit($form, ['id'=>1234]); $product = $crawler->filter('html:contains("Product: 1234")'); $this->assertCount(1, $product); } }
  • 18. Regression testing with Codeception $I = new AcceptanceTester($scenario); $I->amOnPage('/products/1234'); $I->click('Add to basket'); $I->see('Product: 1234');
  • 19. Regression testing with Behat + MinkExtension Scenario: Adding a product to the basket Given I am on "/product/1234" When I click "Add to Basket" Then I should see "Product: 1234"
  • 20. Regression testing with Ghost Inspector
  • 21. When regression testing » Use a tool that gets you coverage quickly and easily » Plan to phase out regression tests later » Lean towards testing end-to-end » Recognise they will be hard to maintain
  • 22. If you want to match customer requirements better ... write Acceptance Tests
  • 23. Acceptance Tests » Check the system does what the customer wants » Are aligned with customer language and intention » Write them in English (or another language) first » Can be tested at the UI or Service level
  • 24. Start with an example-led conversation ... before you start working on it ... but not too long before
  • 25. » "What should the system do when X happens?" » "Does Y always happen when X?" » "What assumptions Z are causing Y to be the outcome?" » "Given Z when X then Y" » "What other things aside from Y might happen?" » "What if...?"
  • 26. Write the examples out in business- readable tests Try and make the code look like the natural conversation you had
  • 27. Easiest to test through the User Interface
  • 28. UI Acceptance testing with PHPUnit + BrowserKit class PostControllerTest extends WebTestCase { public function testAddingProductToTheBasket() { $this->addProductToBasket(1234); $this->productShouldBeShownInBasket(1234); } private function addProductToBasket($productId) { //... browser automation code } private function productShouldBeShownInBasket($productId) { //... browser automation code } }
  • 29. UI Acceptance testing with Codeception $I = new AcceptanceTester($scenario); $I->amGoingTo('Add a product to the basket'); $I->amOnPage('/products/1234'); $I->click('Add to basket'); $I->expectTo('see the product in the basket'); $I->see('Product: 1234');
  • 30. UI Acceptance testing with Behat + MinkExtension Scenario: Adding a product to the basket When I add product 1234 to the basket Then I should see product 1234 in the basket
  • 31. UI Acceptance testing with Behat + MinkExtension /** * @When I add product :productId to the basket */ public function iAddProduct($productId) { $this->visitUrl('/product/' . $productId); $this->getSession()->clickButton('Add to Basket'); } /** * @Then I should see product :productId in the basket */ public function iShouldSeeProduct($productId) { $this->assertSession()->elementContains('css', '#basket', 'Product: ' . $productId); }
  • 32. Acceptance testing through the UI is slow and brittle To test at the service layer, we need to introduce services
  • 33. 'Legacy' code class BasketController extends Controller { public function addAction(Request $request) { $productId = $request->attributes->get('product_id'); $basket = $request->getSession()->get('basket_context')->getCurrent(); $products = $basket->getProducts(); $products[] = $productId; $basket->setProducts($products); return $this->render('::basket.html.twig', ['basket' => $basket]); } }
  • 34. 'Service-oriented' code class BasketController extends Controller { public function addAction(Request $request) { $basket = $this->get('basket_context')->getCurrent(); $productId = $request->attributes->get('product_id'); $basket->addProduct($productId); return $this->render('::basket.html.twig', ['basket' => $basket]); } }
  • 35. A very small change but now the business logic is out of the controller
  • 36. Service layer Acceptance testing with PHPUnit class PostControllerTest extends PHPUnit_Framework_TestCase { public function testAddingProductToTheBasket() { $basket = new Basket(new BasketArrayStorage()); $basket->addProduct(1234); $this->assertContains(1234, $basket->getProducts()); } }
  • 37. Service layer acceptance testing with Behat + MinkExtension Scenario: Adding a product to the basket When I add product 1234 to the basket Then I should see product 1234 in the basket
  • 38. Service layer acceptance testing with Behat /** * @When I add product :productId to the basket */ public function iAddProduct($productId) { $this->basket = new Basket(new BasketArrayStorage()); $this->basket->addProduct($productId); } /** * @Then I should see product :productId in the basket */ public function iShouldSeeProduct($productId) { assert(in_array($productId, $this->basket->getProducts()); }
  • 39. When all of the acceptance tests are running against the Service layer ... how many also need to be run through the UI?
  • 40. Symfony is a controller for your app
  • 41. If you test everything through services ... you only need enough UI tests to be sure the UI is
  • 42. Multiple Behat suites Scenario: Adding a product to the basket When I add product 1234 to the basket Then I should see product 1234 in the basket Scenario: Adding a product that is already there Given I have already added product 1234 to the basket When I add product 1234 to the basket Then I should see 2 instances of product 1234 in the basket @ui Scenario: Adding two products to my basket Given I have already added product 4567 to the basket When I add product 1234 to the basket Then I should see product 4567 in the basket And I should also see product 1234 in the basket
  • 43. Multiple Behat suites default: suites: ui: contexts: [ UiContext ] filters: { tags: @ui } service: contexts: [ ServiceContext ]
  • 44. If you want the design of your code to be better ... write Unit Tests
  • 45. Unit Tests » Check that a class does what you expect » Use a tool that makes it easy to test classes in isolation » Move towards writing them first » Unit tests force you to have good design » Probably too small to reflect acceptance criteria
  • 46. Unit tests are too granular Customer: "The engine needs to produce 500bhp" Engineer: "What should the diameter of the main drive shaft be?"
  • 47. Unit testing in PHPUnit class BasketTest extends PHPUnit_Framework_Testcase { public function testGetsProductsFromStorage() { $storage = $this->getMock('BasketStorage'); $storage->expect($this->once()) ->method('persistProducts') ->with([1234]); $basket = new Basket($storage); $basket->addProduct(1234); } }
  • 48. Unit testing in PhpSpec class BasketSpec extends ObjectBehavior { function it_gets_products_from_storage(BasketStorage $storage) { $this->beConstructedWith($storage); $this->addProduct(1234); $storage->persistProducts([1234])->shouldHaveBeenCalled([1234]); } }
  • 49. Unit test ... code that is responsible for business logic ... not code that interacts with infrastructure including Symfony
  • 50. You can unit test interactions with Symfony (e.g. controllers) You shouldn't need to if you have acceptance tests
  • 52. Unit testing third party dependencies class FileHandlerSpec extends ObjectBehaviour { public function it_uploads_data_to_the_cloud_when_valid( CloudApi $client, FileValidator $validator, File $file ) { $this->beConstructedWith($client, $validator); $validator->validate($file)->willReturn(true); $client->startUpload()->shouldBeCalled(); $client->uploadData(Argument::any())->shouldBeCalled(); $client->uploadSuccessful()->willReturn(true); $this->process($file)->shouldReturn(true); } }
  • 56. class FileHandlerSpec extends ObjectBehaviour { public function it_uploads_data_to_the_cloud_when_valid( FileStore $filestore, FileValidator $validator, File $file ) { $this->beConstructedWith($filestore, $validator); $validator->validate($file)->willReturn(true); $this->process($file); $filestore->store($file)->shouldHaveBeenCalled(); } }
  • 58. class CloudFilestoreTest extends PHPUnit_Framework_TestCase { function testItStoresFiles() { $testCredentials = … $file = new File(…); $apiClient = new CloudApi($testCredentials); $filestore = new CloudFileStore($apiClient); $filestore->store($file); $this->assertTrue($apiClient->fileExists(…)); } }
  • 61. Have isolated unit- tested objects representing your core business logic 10,000s of tests running in <10ms each
  • 62. Have acceptance tests at the service level 1,000s of tests running in <100ms each
  • 63. Have the bare minimum of acceptance tests at the UI level 10s of tests running in <10s each