Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Finding the Right Testing Tool for the Job

441 views

Published on

Over the last decade the idea that we should test our applications has slowly made its way from a niche idea to the mainstream of PHP development. With many tools and approaches to testing now available it can be difficult to choose which ones to use.

In this talk we will explore the current landscape of PHP testing practices, look at the different tools and approaches available, and find out how we can decide which are best for our project, team, and context.

Published in: Software
  • Be the first to comment

Finding the Right Testing Tool for the Job

  1. 1. Finding the Right Testing Tool for the Job Sebastian Bergmann and Ciaran McNulty
  2. 2. Who are these guys?
  3. 3. 3 Dimensions of Testing Goal - why we are writing the test Scope - how much of the system is involved in the test Form - how we express the test
  4. 4. 3 4 Dimensions of Testing Goal - why we are writing the test Scope - how much of the system is involved in the test Form - how we express the test Time - when we write the test
  5. 5. What we will talk about — Characterisation Tests — Acceptance Tests — Integration Tests — Unit Tests
  6. 6. Characterisation Tests
  7. 7. Characterisation Tests Goals: — Capture existing behaviour — Find out when behaviour changes
  8. 8. Characterisation Tests Scopes: — Often at UI level — Sometimes at object/service level
  9. 9. Characterisation Tests Times: — Always after implementation
  10. 10. Characterisation Tests Best practice: — Treat these tests as a temporary measure — Use a tool that makes it easy to create tests — Expect pain and suffering
  11. 11. Characterisation Tests Form: Behat + MinkExtension builtin steps Scenario: Product search returns results Given I am on "/" And I fill in "search" with "Blue jeans" When I press "go" Then I should see "100 Results Found"
  12. 12. Characterisation Tests Form: PHPUnit + phpunit-mink-trait class SearchTest extends PHPUnit_Framework_TestCase { use phpunitminkTestCaseTrait; public function testProductSearchReturnsResult() { $page = $this->visit('http://example.com/'); $page->fillField('search', 'Blue Jeans'); $page->pressButton('go'); $this->assertContains('100 Results Found', $page->getText()); } }
  13. 13. Characterisation Tests Form: Selenium IDE
  14. 14. Characterisation Tests Form: Ghost Inspector
  15. 15. Characterisation Tests Form: PHPUnit + de-legacy-fy See docs at on GitHub at sebastianbergmann/de-legacy-fy
  16. 16. Acceptance Tests
  17. 17. Acceptance Tests Goals: — Match system behaviour to business requirements — Get feedback on proposed implementations — Understand business better — Document behaviour for the future
  18. 18. Acceptance Tests Scopes: — At a UI layer — At an API layer — At the service layer — Lower level may be too disconnected
  19. 19. Acceptance Tests Times: — Before implementation — Before commitment (as long as it's not expensive?) — Hard to write in retrospect
  20. 20. Acceptance Tests Best practices: — Get feedback on tests early — Make them readable, or have readable output, for the intended audience — Apply for the smallest scope first — Apply to core domain model first — Minimise end-to-end tests
  21. 21. Acceptance Tests Form: Behat at service level Scenario: Sales tax is applied to basket Given "Blue Jeans" are priced as €100 in the catalogue When I add "Blue Jeans" to my shopping basket Then the basket total should be €120
  22. 22. class BasketContext implements Context { public function __construct() { $this->catalogue = new InMemoryCatalogue(); $this->basket = new Basket($catalogue); } /** * @Given :productName is/are priced as :cost in the catalogue */ public function priceProduct(ProductName $productName, Cost $cost) { $this->catalogue->price($productName, $cost); } //... }
  23. 23. class BasketContext implements Context { //... /** * @When I add :productName to my shopping basket */ public function addProductToBasket(ProductName $productName) { $this->basket->add($productName); } /** * @Then the basket total should be :cost */ public function checkBasketTotal(Cost $cost) { assert($this->basket->total == $cost->asInt()); } }
  24. 24. Acceptance Tests Form: PHPUnit at service level class BasketTest extends PHPUnit_Framework_TestCase { public function testSalesTaxIsApplied() { $catalogue = new InMemoryCatalogue(); $basket = new Basket($catalogue); $productName = new ProductName('Blue Jeans'); $catalogue->price($productName, new Cost('100')); $basket->add($productName); $this->assertEquals(new Cost('120'), $basket->calculateTotal()); } }
  25. 25. Acceptance Tests Form: PHPUnit at service level
  26. 26. Acceptance Tests Form: Behat at UI level Scenario: Sales tax is applied to basket Given "Blue Jeans" are priced as €100 in the catalogue When I add "Blue Jeans" to my shopping basket Then the basket total should be €120
  27. 27. class BasketUiContext extends MinkContext { public function __construct() { $this->catalogue = new Catalogue(/* ... */); } /** * @Given :productName is/are priced as :cost in the catalogue */ public function priceProduct(ProductName $productName, Cost $cost) { $this->catalogue->price($productName, $cost); } //... }
  28. 28. class BasketUiContext extends MinkContext { //... /** * @When I add :productName to my shopping basket */ public function addProductToBasket(ProductName $productName) { $this->visitPath('/products/'.urlencode($productName)); $this->getSession()->getPage()->pressButton('Add to Basket'); } /** * @Then the basket total should be :cost */ public function checkBasketTotal(Cost $cost) { $this->assertElementContains('#basket .total', '€120'); } }
  29. 29. Acceptance Tests Form: PHPUnit at UI level class BasketUiTest extends PHPUnit_Framework_TestCase { use phpunitminkTestCaseTrait; public function testSalesTaxIsApplied() { $catalogue = new Catalogue(/* ... */); $catalogue->price(new ProductName('Blue Jeans'), new Cost(120)); $page = $this->visit('http://example.com/products/'.urlencode($productName)); $this->getSession()->getPage()->pressButton('Add to Basket'); $this->assertContains( '€120', $page->find('css', '#basket .total')->getText() ); } }
  30. 30. Integration Tests
  31. 31. Integration Tests Goals: — Test cross-boundary communication — Test integration with concrete infrastructure
  32. 32. Integration Tests Scopes: — Large parts of the system — Focus on the edges (not core domain) — Areas where your code interacts with third-party code
  33. 33. Integration Tests Times: — After the feature is implemented in core / contracts are established — During integration with real infrastructure — When you want to get more confidence in integration — When cases are not covered by End-to-End acceptance test
  34. 34. Integration Tests Best Practices: — Use tools with existing convenient integrations — Focus on testing through your API — Make sure your core domain has an interface
  35. 35. Integration Tests Form: PHPUnit + DbUnit class CatalogueTest extends PHPUnit_Extensions_Database_TestCase { public function getConnection() { $pdo = new PDO(/* ... */); return $this->createDefaultDBConnection($pdo, 'myDatabase'); } public function getDataSet() { return $this->createFlatXMLDataSet(dirname(__FILE__) . '/_files/catalogue-seed.xml'); } // ... }
  36. 36. class CatalogueTest extends PHPUnit_Extensions_Database_TestCase { // ... public function testProductCanBePriced() { $catalogue = new Catalogue(/* ... */); $catalogue->price( new ProductName('Blue Jeans'), new Cost('100') ); $this->assertEquals( new Cost('100'), $catalogue->lookUp(new ProductName('Blue Jeans') ); } }
  37. 37. Unit Tests
  38. 38. Unit Tests Goals: — Test components individually — Catch errors earlier — Drive internal design quality — Document units for other developers
  39. 39. Unit Tests: Scopes: — Single classes — Single classes + value objects? — Extremely small units of code
  40. 40. Unit Tests: Times: — Just before you implement
  41. 41. Unit Tests: Times: — Just before you implement OK, maybe... — Just after you implement — If you want to learn more about a class — But always before you share the code!
  42. 42. Unit Tests Best Practices — Write in a descriptive style — Describe interactions using Test Doubles — Don't get too hung up on isolation — Don't touch infrastructure — Don't test other people's code — Don't double other people's code?
  43. 43. Unit Tests Form: PHPUnit class BasketTest extends PHPUnit_Framework_TestCase { public function testSalesTaxIsApplied() { $catalogue = $this->getMock(Catalogue::class); $catalogue->method('lookUp')->with(new ProductName('Blue Jeans')) ->willReturn(new Cost('100')); $basket = new Basket($catalogue); $basket->add(new ProductName('Blue Jeans')); $this->assertSame(new Cost('120'), $basket->calculateTotal()); } }
  44. 44. Unit Tests Form: PhpSpec class BasketSpec extends ObjectBehavior { function it_applies_sales_tax(Catalogue $catalogue) { $catalogue->lookUp(new ProductName('Blue Jeans'))->willReturn(new Cost('100')); $this->beConstructedWith($catalogue); $this->add(new ProductName('Blue Jeans')); $basket->calculateTotal()->shouldBeLike(new Cost('120')); } }
  45. 45. 5th Dimension -Who? — Choose the right approaches for your context — What mix of languages can the team use? — What styles of testing will add the most value? — What formats make the most sense to the team? — How will tests fit into the development process? There is no right answer, there are many right answers!
  46. 46. Photo Credits — "tools" by velacreations (CC) - https://flic.kr/p/ 8ZSb3r — "Components" by Jeff Keyzer (CC) - https://flic.kr/p/ 4ZNZp1 — Doctor Who stolen from BBC.co.uk — Other images used under license
  47. 47. Thank You & Questions? @s_bergmann @ciaranmcnulty https://joind.in/talk/80dbd

×