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.

Software Testing & PHPSpec

564 views

Published on

An introduction to Software Testing & PHPSpec. Presented by @MinusDarren at @PHPBelfast on 10th December 2015.

Published in: Software
  • Be the first to comment

Software Testing & PHPSpec

  1. 1. SOFTWARE TESTING & PHPSPEC DARREN CRAIG @minusdarren
  2. 2. “Always code as if the guy who ends up maintaining your code will be a violent psychopath and knows where you live.” - John F. Woods THE OBLIGATORY QUOTE
  3. 3. TESTING. TESTING. 123.
  4. 4. LEARNING HOW TO TEST Started looking at unit testing about 2010 Very confusing So many new concepts, language & ideas Difficult to implement Documentation, help and examples were scarce
  5. 5. Since then, there are many new tools available Behat PHPSpec Codeception Documentation has improved, as have help & examples Frameworks are introducing better standards/practices BIGGER, BETTER TOOLS
  6. 6. GETTING MY HEAD AROUND IT Started reading about Domain Driven Design Started using CQRS Experimented with Datamapper tools, like Doctrine Started playing with other testing tools, like PHPSpec, Behat & Codeception And I discovered: The architecture of my code was a massive issue I was thinking in terms of the frameworks I was using “Fat Controllers, Thin Models” - No. (Anaemic Domain Model) Public attributes on models ($user->name = $blah) weren’t helping
  7. 7. THINGS THAT HELPED I started restructuring my code based on DDD, CQRS and SOLID principles Made loads of mistakes … and even more mistakes Started removing the database structure from my thinking (tough!!!) Discovered that some mistakes aren’t mistakes Spoke to a bunch of people on IRC
  8. 8. A QUICK OVERVIEW OF TESTING & TERMINOLOGY
  9. 9. SOFTWARE TESTING Been around since the late 70s Checks if a component of a system satisfies the requirements Usually separated into: Unit Testing Integration Testing Acceptance Testing
  10. 10. UNIT TESTING Tests individual parts - or a unit - of your code Eg. Does the add() method work properly? One function/method may have multiple tests PHPUnit, PHPSpec
  11. 11. INTEGRATION TESTING Tests several parts of your system are working correctly together Eg. When a user registers, are their details saved to the Database? Behat
  12. 12. ACCEPTANCE TESTING Tests the system is working correctly from a user’s perspective E.g. if I go to /register - is the correct form displayed? E.g. If I input an invalid email address, do I get an error? Behat, Codeception, Selenium
  13. 13. TEST DRIVEN DEVELOPMENT (TDD) Write tests first, then the code that’s being tested Red-Green-Refactor Red: Write a test - make it fail Green: Make the test pass Refactor: Tidy it up. It should still pass.
  14. 14. PHPSPEC
  15. 15. WHAT IS PHPSPEC? A PHP Library Similar to PHPUnit (but with a nicer API!) Available through Composer (phpspec/phpspec) Helps design your PHP Classes through specifications Describes the behaviour of the class before you write it No real difference between SpecBDD and TDD
  16. 16. INSTALLING "require-dev": {
 "phpspec/phpspec": "~2.4"
 },
  17. 17. DID IT WORK? vendor/bin/phpspec run 0 specs 0 examples 0ms
  18. 18. CONFIGURATION # phpspec.yml suites: main: namespace: Acme # composer.json "autoload": { "psr-4": { "Acme": "src/Acme" } }
  19. 19. DOING AS YOU’RE TOLD…
  20. 20. “Users should be able to Register on the system. They need a name, email and password to do so.” - The Client
  21. 21. LET’S CODE THAT… $input = Input::all(); $user = new User(); $user->name = $input['name']; $user->email = $input['email']; $user->password = Hash::make($input['password']); $userRepository->save($user); What’s going on here? Is this testable? Is it maintainable?
  22. 22. LET’S USE PHPSPEC TO HELP vendor/bin/phpspec describe Acme/User Specification for AcmeUser created in [dir]/spec/UserSpec.php. namespace specAcme; use PhpSpecObjectBehavior; use ProphecyArgument; class UserSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('AcmeUser'); } }
  23. 23. RUN THE TEST! $ vendor/bin/phpspec run Acme/User 10 - it is initializable class AcmeUser does not exist. 100% 1 1 specs 1 example (1 broken) 6ms Do you want me to create `AcmeUser` for you? [Y/n]
  24. 24. YES! Class AcmeUser created in phpspec/src/Acme/User.php. 100% 1 1 specs 1 example (1 passed) 13ms
  25. 25. - The Client WHAT THE CLIENT SAID “Users should be able to Register on the system. They need a name, email and password to do so.”
  26. 26. USE A CONSTRUCTOR class UserSpec extends ObjectBehavior { function let() { $this->beConstructedWith('Darren Craig', 'darren@minus40.co', 'abc123'); } function it_tests_a_users_can_be_registered() { $this->shouldHaveType('AcmeUser'); } }
  27. 27. RUN THE TEST $ vendor/bin/phpspec run Acme/User 15 - it tests a users can be registered method AcmeUser::__construct not found. 100% 1 1 specs 1 example (1 broken) 9ms Do you want me to create `AcmeUser::__construct()` for you? [Y/n] Y Method AcmeUser::__construct() has been created. 100% 1 1 specs 1 example (1 passed) 8ms
  28. 28. THE USER CLASS class User { public function __construct($name, $email, $password) { // TODO: write logic here } }
  29. 29. RETURNING USER DETAILS class UserSpec extends ObjectBehavior { // other tests… function it_tests_that_it_can_return_a_name() { $this->getName()->shouldReturn('Darren Craig'); } }
  30. 30. RUN THE TEST $ vendor/bin/phpspec run Acme/User 21 - it tests that it can return a name method AcmeUser::getName not found. 50% 50% 2 1 specs 2 examples (1 passed, 1 broken) 11ms Do you want me to create `AcmeUser::getName()` for you? [Y/n] Y Method AcmeUser::getName() has been created. Acme/User 21 - it tests that it can return a name expected "Darren Craig", but got null. 50% 50% 2 1 specs 2 examples (1 passed, 1 failed) 12ms
  31. 31. MAKING IT PASS class User { private $name; public function __construct($name, $email, $password) { $this->name = $name; } public function getName() { return $this->name; } }
  32. 32. RUN THE TEST $ vendor/bin/phpspec run 100% 2 1 specs 2 examples (2 passed) 7ms
  33. 33. THE OTHER USER DETAILS… function it_tests_that_it_can_return_a_name() { $this->getName()->shouldReturn('Darren Craig'); } function it_tests_that_it_can_return_the_email_address() { $this->getEmail()->shouldReturn('darren@minus40.co'); } function it_tests_that_it_can_return_the_password() { $this->getPassword()->shouldReturn('abc123'); }
  34. 34. REGISTERING A USER $input = Input::all(); $user = new User($input['name'], $input['email'], $input['password']); $userRepository->save($user); But, our code should represent the behaviour it’s carrying out… Are we creating a new User? What are we doing?
  35. 35. - The Client WHAT THE CLIENT SAID “Users should be able to Register on the system. They need a name, email and password to do so.”
  36. 36. REGISTERING USERS $input = Input::all(); $user = User::register($input['name'], $input['email'], $input['password']); $userRepository->save($user); private function __construct($name, $email, $password) {} public static function register($name, $email, $password) { return new static($name, $email, $password); } function let() { $this->beConstructedThrough(‘register', ['Darren Craig', 'darren@minus40.co', 'abc123']); }
  37. 37. NEXT… “Users should be able to add up to 3 Qualifications” - The Client
  38. 38. THE QUALIFICATION CLASS $ vendor/bin/phpspec describe Acme/Qualification Specification for AcmeQualification created in [dir]/spec/Acme/QualificationSpec.php. $ vendor/bin/phpspec run Acme/Qualification 10 - it is initializable class AcmeQualification does not exist. 80% 20% 5 2 specs 5 examples (4 passed, 1 broken) 24ms Do you want me to create `AcmeQualification` for you? [Y/n] Y Class AcmeQualification created in [dir]/src/Acme/Qualification.php. 100% 5 2 specs 5 examples (5 passed) 9ms
  39. 39. MORE USER TESTS… use AcmeQualification; class UserSpec extends ObjectBehavior { function it_adds_a_qualification(Qualification $qualification) { $this->addQualification($qualification); $this->getQualifications()->shouldHaveCount(1); } }
  40. 40. RUN AND CREATE THE METHODS $ vendor/bin/phpspec run Acme/User 36 - it adds a qualification method AcmeUser::addQualification not found. 80% 20% 5 2 specs 5 examples (4 passed, 1 broken) 24ms Do you want me to create `AcmeUser::addQualification()` for you? [Y/n] Y Method AcmeUser::addQualification() has been created. Acme/User 31 - it adds a qualification method AcmeUser::getQualifications not found. 80% 20% 5 2 specs 5 examples (4 passed, 1 broken) 15ms Do you want me to create `AcmeUser::getQualifications()` for you? [Y/n] Y Method AcmeUser::getQualifications() has been created. Acme/User 31 - it adds a qualification no haveCount([array:1]) matcher found for null. 80% 20% 5 2 specs 5 examples (4 passed, 1 broken) 20ms
  41. 41. AND MAKE IT PASS… class User { private $qualifications = []; public function addQualification(Qualification $qualification) { $this->qualifications[] = $qualification; } public function getQualifications() { return $this->qualifications; } }
  42. 42. CHECK IF IT PASSED $ vendor/bin/phpspec run 100% 5 2 specs 5 examples (5 passed) 14ms
  43. 43. GREAT, BUT… “Users should be able to add up to 3 Qualifications” - The Client
  44. 44. NO PROBLEM - ANOTHER TEST function it_prevents_more_than_3_qualifications_being_added(Qualification $qualification) { $this->addQualification($qualification); $this->addQualification($qualification); $this->addQualification($qualification); $this->shouldThrow(Exception::class)->duringAddQualification($qualification); }
  45. 45. RUN IT $ vendor/bin/phpspec run Acme/User 37 - it prevents more than 3 qualifications being added expected to get exception, none got. 83% 16% 6 2 specs 6 examples (5 passed, 1 failed) 21ms
  46. 46. AND MAKE IT PASS… class User { private $qualifications = []; public function addQualification(Qualification $qualification) { if(count($this->qualifications) === 3) { throw new Exception("You can't add more than 3 qualifications"); } $this->qualifications[] = $qualification; } }
  47. 47. RUN IT $ vendor/bin/phpspec run 100% 6 2 specs 6 examples (6 passed) 17ms
  48. 48. COMMON MATCHERS http://phpspec.readthedocs.org/en/latest/cookbook/matchers.html
  49. 49. IDENTITY MATCHERS $this->getName()->shouldBe("Darren Craig"); $this->getName()->shouldBeEqualTo("Darren Craig"); $this->getName()->shouldReturn("Darren Craig"); $this->getName()->shouldEqual("Darren Craig");
  50. 50. COMPARISON MATCHER $this->getAge()->shouldBeLike('21');
  51. 51. THROW MATCHERS $this->shouldThrow(Exception::class)->duringAddQualification($qualification); $this->shouldThrow(Exception::class)->during('addQualification', [$qualification]);
  52. 52. TYPE MATCHERS $this->shouldHaveType('AcmeUser'); $this->shouldReturnAnInstanceOf('AcmeUser'); $this->shouldBeAnInstanceOf('AcmeUser'); $this->shouldImplement('AcmeUserInterface');
  53. 53. OBJECT STATE MATCHERS // calls $user->isOver18(); $this->shouldBeOver18(); // call $user->hasDOB(); $this->shouldHaveDOB(); A nice way of calling is* or has* methods on your object
  54. 54. COUNT MATCHER $this->getQualifications()->shouldHaveCount(3);
  55. 55. SCALAR TYPE MATCHER $this->getName()->shouldBeString(); $this->getQualifications()->shouldBeArray();
  56. 56. MORE WORK… LESS TEARS TDD encourages you to think first Smaller, single-responsibility classes More maintainable code More robust systems As your skill improves, so will your speed Less likely to spend hours debugging
  57. 57. QUESTIONS?
  58. 58. THANKS FOR LISTENING! DARREN CRAIG @minusdarren

×