Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Testable PHP Applications (php|tek 2013)

Building Testable PHP Applications (php|tek 2013)

Slides from my presentation at php|tek 2013

27601bca8f38e75cbcf9d2dc843f0b32?s=128

Chris Hartjes

May 16, 2013
Tweet

Transcript

  1. Building Testable PHP Applications Chris Hartjes php|tek 2013- May 16,

    2013 @grmpyprogrammer
  2. Text

  3. WHY DO WE TEST? Because programming is hard

  4. WHY DO WE TEST?

  5. A HUGE TOPIC

  6. UNCOMFORTABLE TRUTHS Some of this will not make sense to

    you
  7. UNCOMFORTABLE TRUTHS Some applications will resist all attempts to test

  8. UNCOMFORTABLE TRUTHS Testing is good Testable applications are better

  9. SO WHAT CAN WE DO?

  10. IT’S ABOUT TOOLS

  11. IT’S ABOUT STRATEGIES

  12. AUTOMATION IS KEY

  13. AUTOMATION IS KEY “Write a script that will run all

    your tests before you go live”
  14. AUTOMATION “Tell your version control system to run your tests

    on commit or push”
  15. AUTOMATION IS KEY http://jenkins-ci.org http://travis-ci.org

  16. ARCHITECTURE “Simple systems can display complex behavior but complex systems

    can only display simple behavior”
  17. ARCHITECTURE “Inside every great large application are many great small

    applications”
  18. ARCHITECTURE “Your framework is a detail, not the core of

    your application.” -- Bob Martin
  19. None
  20. ARCHITECTURE “One of the great bugaboos of software applications over

    the years has been infiltration of business logic into the user interface code.” -- Alistair Cockburn http://alistair.cockburn.us/Hexagonal+architecture
  21. None
  22. LAW OF DEMETER “The Law of Demeter for functions states

    that any method of an object should call only methods belonging to itself, any parameters that were passed in to the method, any objects it created, and any directly held component objects. ”
  23. DEPENDENCY INJECTION “Pass objects and their methods other objects and

    function that are required for the task.”
  24. DEPENDENCY INJECTION http://stackoverflow.com/questions/1638919/how-to- explain-dependency-injection-to-a-5-year-old/ 1638961#1638961

  25. DEPENDENCY INJECTION

  26. MOCK OBJECTS “Mock objects allow you to test code in

    proper isolation”
  27. MOCK OBJECTS Database connections Web services File system operations

  28. HOW DO WE TEST THIS?

  29. HOW DO WE TEST THIS? “Protected and private methods and

    attributes are difficult to test properly”
  30. METHODS? Etsy’s PHPUnit Extensions https:/ /github.com/etsy/phpunit-extensions Uses annotations to flag

    methods you wish to test
  31. METHODS? class ObjectWithPrivate { ! private function myInaccessiblePrivateMethod() ! {

    ! ! return 'inaccessible'; ! } ! /** @accessibleForTesting */ ! private function myAccessiblePrivateMethod() { ! ! return 'accessible'; ! } }
  32. METHODS? class ObjectWithPrivateTest extends PHPUnit_Framework_Testcase { ! public $accessible; !

    public function setUp() ! { ! ! parent::setUp(); ! ! $this->accessible = new PHPUnit_Extensions_Helper_AccessibleObject( ! ! ! new ObjectWithPrivate()); ! } ! public function testMyAccessiblePrivateMethod() ! { ! ! $this->assertEquals( ! ! ! 'accessible', ! ! ! $this->accessible->myAccessiblePrivateMethod() ! ! ); ! } }
  33. METHODS? PHP’S Reflection API http:/ /www.gpug.ca/2012/06/02/testing- protected-methods-with-phpunit/

  34. METHODS? class Foo { ! protected $_message; ! protected function

    _bar() ! { ! ! $this->_message = 'WRITE TESTS OR I CUT YOU'; ! } }
  35. METHODS? class FooTest extends PHPUnit_Framework_Testcase() { ! public function testProtectedBar()

    ! { ! ! $testFoo = new Foo(); ! ! $expectedMessage = 'WRITE TESTS OR I CUT YOU'; ! ! $reflectedFoo = new \ReflectionMethod($testFoo, '_bar'); ! ! $reflectedFoo->setAccessible(true); ! ! $reflectedFoo->invoke($testFoo); ! ! $testMessage = \PHPUnit_Framework_Assert::readAttribute( ! ! ! $testFoo, ! ! ! '_message') ! ! $this->assertEquals( ! ! ! $expectedMessage, ! ! ! $testMessage, ! ! ! "Did not get expected message" ! ! ); ! } }
  36. ATTRIBUTES? PHP’S Reflection API PHPUnit lets you check attribute values

    but not set them
  37. HOW DO YOU TEST THIS? “If your unit test actually

    uses the database, you are doing it wrong”
  38. HOW DO YOU TEST THIS? class Bar { ! public

    function getBazById($id) ! { ! ! $this->db->query("SELECT * FROM baz WHERE id = :bazId"); ! ! $this->db->bind('bazId', $id); ! ! $results = $this->db->execute(); ! ! $bazList = array(); ! ! if (count($results) > 0) { ! ! ! foreach ($results as $result) { ! ! ! ! $bazList[] = $result; ! ! ! } ! ! } ! ! return $bazList; ! } }
  39. HOW DO YOU TEST THIS? class BarTest extends PHPUnit_Framework_Testcase {

    ! public function testGetBazById() ! { ! ! $bazId = 666; ! ! $expectedResults = array(1, 2, 3, 4, 5); ! ! $mockDb = $this->getMockBuilder('\Grumpy\Db') ! ! ! ->disableOriginalConstructor() ! ! ! ->setMethods(array('query', 'execute', 'bind')) ! ! ! ->getMock(); ! ! $mockDb->expects($this->once()) ! ! ! ->method('query'); ! ! $mockDb->expects($this->once()) ! ! ! ->method('bind'); ! ! $mockDb->expects($this->once()) ! ! ! ->method('execute') ! ! ! ->will($this->returnValue($expectedResults)); ...! ! ! } }
  40. HOW DO YOU TEST THIS? class BarTest extends PHPUnit_Framework_Testcase {

    ! public function testGetBazById() ! { ! ! ... ! ! $testBar = new Bar(); ! ! $testBar->setDb($mockDb); ! ! $testResults = $testBar->getBazById($bazId); ! ! $this->assertEquals( ! ! ! $expectedResults, ! ! ! $testResults, ! ! ! 'Did not get expected baz result set' ! ! ); ! } }
  41. HOW DO YOU TEST THIS? “API calls should be done

    via wrapper methods”
  42. HOW DO YOU TEST THIS? <?php class HipsterApi { !

    public function getBands() ! { ! ! return $this->_call('/api/bands', $this->_apiKey); ! } } class HipsterApiWrapper { ! public function __construct($hipsterApi) ! { ! ! $this->_hipsterApi = $hipsterApi; ! } ! public function getBands() ! { ! ! return $this->_hipsterApi->getBands(); ! } }
  43. HOW DO YOU TEST THIS? class HipsterApiTest extends PHPUnit_Framework_Testcase {

    ! public function testGetBands() ! { ! ! $hipsterApiData = "[{'id': 17, 'Anonymous'}, {'id': 93, 'HipStaar'}]"; ! ! $mockHipsterApi = $this->getMockBuilder('HipsterApi') ! ! ! ->disableOriginalConstructor() ! ! ! ->getMock(); ! ! $mockHipsterApi->expects($this->once()) ! ! ! ->with('getBands') ! ! ! ->will($this->returnValue($hipsterApiData)); ! ! } ... }
  44. HOW DO WE TEST THIS? class HipsterApiTest extends PHPUnit_Framework_Testcase {

    ! public function testGetBands() ! { ! ! ... ! ! $hipsterApiWrapper = new HipsterApiWrapper($mockHipsterApi); ! ! $testData = $hipsterApiWrapper->getBands(); ! ! ! ! ! $this->assertEquals( ! ! ! $expectedData, ! ! ! $testData, ! ! ! 'Did not get expected getBands() result from HipsterApi' ! ! ); ! } }
  45. ENVIRONMENTS “Your app shouldn’t care what environment it runs in.”

  46. ENVIRONMENTS “Keep config files for each environment under version control”

    https:/ /github.com/flogic/ whiskey_disk
  47. RESOURCES

  48. RESOURCES

  49. RESOURCES

  50. QUESTIONS?

  51. THANK YOU! @grmpyprogrammer http:/ /www.littlehart.net/atthekeyboard https:/ /joind.in/8166