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.

Driving Design with PhpSpec

1,462 views

Published on

What is BDD? Is it the same as TDD, or something quite different? This talk will answer these questions, and show how PhpSpec can be integrated into your development workflow to drive your Object Oriented design. Plus: a sneak peak at some of the new features in the forthcoming 2.1 release.

Published in: Technology
  • Be the first to comment

Driving Design with PhpSpec

  1. 1. Driving Development with PhpSpec with Ciaran McNulty PHPLondon November 2014
  2. 2. My experiences 4 Unit testing since 2004 4 Test Driven Development since 2005(ish) 4 Behaviour Driven Development since 2012
  3. 3. TDD vs BDD (or are they the same?)
  4. 4. BDD is a second-generation, outside-in, pull-based, multiple-stakeholder… 1 Dan North
  5. 5. …multiple-scale, high-automation, agile methodology. 1 Dan North
  6. 6. BDD is the art of using examples in conversation to illustrate behaviour 1 Liz Keogh
  7. 7. Test Driven Development 4 Before you write your code, write a test that validates how it should behave 4 After you have written the code, see if it passes the test
  8. 8. Behaviour Driven Development 4 Before you write your code, describe how it should behave using examples 4 Then, Implement the behaviour you you described
  9. 9. SpecBDD with PhpSpec Describing individual classes
  10. 10. History 1.0 - Inspired by RSpec 4 Pádraic Brady and Travis Swicegood
  11. 11. History 2.0beta - Inspired by 1.0 4 Marcello Duarte and Konstantin Kudryashov (Everzet) 4 Ground-up rewrite 4 No BC in specs
  12. 12. History 2.0 stable - The boring bits 4 Me 4 Christophe Coevoet 4 Jakub Zalas 4 Richard Miller 4 Gildas Quéméner
  13. 13. Installation via Composer { "require-dev": { "phpspec/phpspec": "~2.1-RC1" }, "config": { "bin-dir": "bin" }, "autoload": {"psr-0": {"": "src"}} }
  14. 14. A requirement: We need something that says hello to people
  15. 15. Describing object behaviour 4 We describe an object using a Specification 4 A specification is made up of Examples illustrating different scenarios Usage: phpspec describe [Class]
  16. 16. /spec/PhpLondon/HelloWorld/GreeterSpec.php namespace specPhpLondonHelloWorld; use PhpSpecObjectBehavior; use ProphecyArgument; class GreeterSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('PhpLondonHelloWorldGreeter'); } }
  17. 17. Verifying object behaviour 4 Compare the real objects' behaviours with the examples Usage: phpspec run
  18. 18. /src/PhpLondon/HelloWorld/Greeter.php namespace PhpLondonHelloWorld; class Greeter { }
  19. 19. An example for Greeter: When this greets, it should return "Hello"
  20. 20. /spec/PhpLondon/HelloWorld/GreeterSpec.php class GreeterSpec extends ObjectBehavior { function it_greets_by_saying_hello() { $this->greet()->shouldReturn('Hello'); } }
  21. 21. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { public function greet() { // TODO: write logic here } }
  22. 22. So now I write some code?
  23. 23. Fake it till you make it 4 Do the simplest thing that works 4 Only add complexity later when more examples drive it phpspec run --fake
  24. 24. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { public function greet() { return 'Hello'; } }
  25. 25. Describing values Matchers
  26. 26. Describing values - Equality $this->greet()->shouldReturn('Hello'); $this->sum(3,3)->shouldEqual(6); $user = $this->findById(1234); $user->shouldBe($expectedUser); $this->numberList() ->shouldBeLike(new ArrayObject([1,2,3]));
  27. 27. Describing values - Type $this->address()->shouldHaveType('EmailAddress'); $this->getTime()->shouldReturnAnInstanceOf('DateTime'); $user = $this->findById(1234); $user->shouldBeAnInstanceOf('User'); $this->shouldImplement('Countable');
  28. 28. Describing values - Strings $this->getStory()->shouldStartWith('A long time ago'); $this->getStory()->shouldEndWith('happily ever after'); $this->getSlug()->shouldMatch('/^[0-9a-z]+$/');
  29. 29. Describing values - Arrays $this->getNames()->shouldContain('Tom'); $this->getNames()->shouldHaveKey(0); $this->getNames()->shouldHaveCount(1);
  30. 30. Describing values - object state // calls isAdmin() $this->getUser()->shouldBeAdmin(); // calls hasLoggedInUser() $this->shouldHaveLoggedInUser();
  31. 31. Describing custom values function it_gets_json_with_user_details() { $this->getResponseData()->shouldHaveJsonKey('username'); } public function getMatchers() { return [ 'haveJsonKey' => function ($subject, $key) { return array_key_exists($key, json_decode($subject)); } ]; }
  32. 32. Another example for Greeter: When this greets Bob, it should return "Hello, Bob"
  33. 33. Wait, what is Bob? Bob is a Person What is a Person?
  34. 34. An example for a Person: When you ask a person named "Alice" for their name, they return "Alice"
  35. 35. /spec/PhpLondon/HelloWorld/PersonSpec.php class PersonSpec extends ObjectBehavior { function it_returns_the_name_it_is_created_with() { $this->beConstructedWith('Alice'); $this->getName()->shouldReturn('Alice'); } }
  36. 36. /src/PhpLondon/HelloWorld/Person.php class Person { public function __construct($argument1) { // TODO: write logic here } public function getName() { // TODO: write logic here } }
  37. 37. So now I write some code!
  38. 38. /src/PhpLondon/HelloWorld/Person.php class Person { private $name; public function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } }
  39. 39. Another example for a Person: When a person named "Alice" changes their name to "Bob", when you ask their name they return "Bob"
  40. 40. /spec/PhpLondon/HelloWorld/PersonSpec.php class PersonSpec extends ObjectBehavior { function it_returns_the_name_it_is_created_with() { $this->beConstructedWith('Alice'); $this->getName()->shouldReturn('Alice'); } }
  41. 41. /spec/PhpLondon/HelloWorld/PersonSpec.php class PersonSpec extends ObjectBehavior { function let() { $this->beConstructedWith('Alice'); } function it_returns_the_name_it_is_created_with() { $this->getName()->shouldReturn('Alice'); } }
  42. 42. /spec/PhpLondon/HelloWorld/PersonSpec.php class PersonSpec extends ObjectBehavior { function let() { $this->beConstructedWith('Alice'); } // … function it_returns_its_new_name_when_the_name_has_been_changed() { $this->changeNameTo('Bob'); $this->getName()->shouldReturn('Bob'); } }
  43. 43. /src/PhpLondon/HelloWorld/Person.php class Person { private $name; // … public function changeNameTo($argument1) { // TODO: write logic here } }
  44. 44. /src/PhpLondon/HelloWorld/Person.php class Person { private $name; // … public function changeNameTo($name) { $this->name = $name; } }
  45. 45. Another example for Greeter: When this greets Bob, it should return "Hello, Bob"
  46. 46. Describing collaboration - Stubs Stubs are used to describe how we interact with objects we query 4 Maybe it is hard to get the real collaborator to return the value we want 4 Maybe using the real collaborator is expensive
  47. 47. /spec/PhpLondon/HelloWorld/GreeterSpec.php class GreeterSpec extends ObjectBehavior { //… function it_greets_people_by_name(Person $bob) { $bob->getName()->willReturn('Bob'); $this->greet($bob)->shouldReturn('Hello, Bob'); } }
  48. 48. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { public function greet() { return 'Hello'; } }
  49. 49. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { public function greet() { $greeting = 'Hello'; return $greeting; } }
  50. 50. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { public function greet(Person $person = null) { $greeting = 'Hello'; if ($person) { $greeting .= ', ' . $person->getName(); } return $greeting; } }
  51. 51. Final example for Greeter: When it greets Bob, the message "Hello Bob" should be logged
  52. 52. What's a log? Let's not worry yet
  53. 53. /src/PhpLondon/HelloWorld/Logger.php interface Logger { public function log($message); }
  54. 54. Describing collaboration - Mocks and Spies Mocks or Spies are used to describe how we interact with objects we command 4 Maybe the real command is has side effects 4 Maybe using the real collaborator is expensive
  55. 55. /spec/PhpLondon/HelloWorld/GreeterSpec.php class GreeterSpec extends ObjectBehavior { //… function it_greets_people_by_name(Person $bob) { $bob->getName()->willReturn('Bob'); $this->greet($bob)->shouldReturn('Hello, Bob'); } }
  56. 56. /spec/PhpLondon/HelloWorld/GreeterSpec.php class GreeterSpec extends ObjectBehavior { function let(Person $bob) { $bob->getName()->willReturn('Bob'); } //… function it_greets_people_by_name(Person $bob) { $this->greet($bob)->shouldReturn('Hello, Bob'); } }
  57. 57. /spec/PhpLondon/HelloWorld/GreeterSpec.php class GreeterSpec extends ObjectBehavior { function let(Person $bob, Logger $logger) { $this->beConstructedWith($logger); $bob->getName()->willReturn('Bob'); } //… function it_logs_the_greetings(Person $bob, Logger $logger) { $this->greet($bob); $logger->log('Hello, Bob')->shouldHaveBeenCalled(); } }
  58. 58. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { public function __construct($argument1) { // TODO: write logic here } public function greet(Person $person = null) { $greeting = 'Hello'; if ($person) { $greeting .= ', ' . $person->getName(); } return $greeting; } }
  59. 59. /src/PhpLondon/HelloWorld/Greeter.php class Greeter { private $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function greet(Person $person = null) { $greeting = 'Hello'; if ($person) { $greeting .= ', ' . $person->getName(); } $this->logger->log($greeting); return $greeting; } }
  60. 60. What have we built?
  61. 61. The domain model
  62. 62. Specs as documentation
  63. 63. PhpSpec 4 Focuses on being descriptive 4 Makes common dev activities easier or automated 4 Drives your design
  64. 64. 2.1 release - soon! 4 Rerun after failure 4 --fake option 4 Named constructors: User::named('Bob') 4 PSR-4 support (+ other autoloaders) 4 + lots of small improvements
  65. 65. Me 4 Senior Trainer at Inviqa / Sensio Labs UK / Session Digital 4 Contributor to PhpSpec 4 @ciaranmcnulty 4 https://github.com/ciaranmcnulty/phplondon-phpspec- talk
  66. 66. Questions?

×