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

Chris Hartjes

May 16, 2013
Tweet

More Decks by Chris Hartjes

Other Decks in Programming

Transcript

  1. 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
  2. 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. ”
  3. HOW DO WE TEST THIS? “Protected and private methods and

    attributes are difficult to test properly”
  4. METHODS? class ObjectWithPrivate { ! private function myInaccessiblePrivateMethod() ! {

    ! ! return 'inaccessible'; ! } ! /** @accessibleForTesting */ ! private function myAccessiblePrivateMethod() { ! ! return 'accessible'; ! } }
  5. 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() ! ! ); ! } }
  6. METHODS? class Foo { ! protected $_message; ! protected function

    _bar() ! { ! ! $this->_message = 'WRITE TESTS OR I CUT YOU'; ! } }
  7. 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" ! ! ); ! } }
  8. HOW DO YOU TEST THIS? “If your unit test actually

    uses the database, you are doing it wrong”
  9. 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; ! } }
  10. 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)); ...! ! ! } }
  11. 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' ! ! ); ! } }
  12. 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(); ! } }
  13. 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)); ! ! } ... }
  14. 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' ! ! ); ! } }