Fighting Fear-Driven-Development With PHPUnit
Upcoming SlideShare
Loading in...5
×
 

Fighting Fear-Driven-Development With PHPUnit

on

  • 3,762 views

This talk was designed for PHP developers with limited or no experience in unit testing. I focus on describing the problem of fear-driven-development, and how test-driven-development can be used to ...

This talk was designed for PHP developers with limited or no experience in unit testing. I focus on describing the problem of fear-driven-development, and how test-driven-development can be used to improve the quality of your code.

Statistics

Views

Total Views
3,762
Views on SlideShare
3,477
Embed Views
285

Actions

Likes
4
Downloads
43
Comments
0

6 Embeds 285

http://www.jblotus.com 251
http://feedly.com 28
http://127.0.0.1 2
http://www.digg.com 2
http://localhost 1
http://www.newsblur.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Fighting Fear-Driven-Development With PHPUnit Fighting Fear-Driven-Development With PHPUnit Presentation Transcript

    • South Florida PHP Meetup Group
    •  James Fuller Web Developer http://www.jblotus.com Started unit testing in response to a new job developing on a large, buggy legacy application used by millions
    •  Afraid to make even tiny code changes due to unpredictable side-effects? Been burned by bugs that came back to life? Does sketchy code keep you up at night?
    • Imagine your code as a dark twisted forest thatyou must navigate.Nothing makes sense, there are no clear pathsand you are surrounded by bugs. Lots of bugs.How would you handle being stuck in thissituation?
    •  Some will be brave and foolish, cutting corners to move quickly through the forest. Some will be paralyzed by the thought of making the wrong move. Most will succumb to the bugs or die a horrible death trying to find a way out.
    • Modify code Brokesomething Run featureGod I hope Doesnt workthis works Modify Code
    •  Write tests Use PHPUnit to automate your tests Practice Test-Driven-Development Improve your design Enjoy the added value
    •  Can you prove it works? automatically? Can you change it and know it still works? without loading the browser? Can you promise that if it breaks, it will never break like that again? What about edge cases? You do care about edge cases, right?
    •  All developers practice some form of testing. Most PHP developers do it in the browser. What we want to do is automate testing with PHPUnit. Dont confuse apathy with laziness. Good coders automate repetitive tasks because they are lazy.
    •  Before you write new code When you fix a bug Before you begin refactoring code As you go, improving code coverageWe can use Test-Driven-Development to helpus maintain disclipline in writing our unit tests.
    •  The principle of unit testing is to verify that a unit of code will function correctly in isolation. A unit is the smallest testable part of an application, usually a function or method.
    • Understand RequirementsRefactor Write testsRun Tests Run Tests (PASS) (FAIL) Write Code
    •  Focus on design of system before implementation Changes are easier to make, with less side effects Documentation via example Less FEAR, more CONFIDENCE You have tests at the end!
    •  PHPUnit is an automated testing framework PHP development based upon xUnit (java). PHPUnit gives us an easy way to write tests, generate code coverage and improve the quality of our code.
    • Command line test runnerMature and expansive assertion libraryCode coverageMock objectsLots of extensionsFast & Configurable
    •  PEAR  best for system wide access to phpunit  hard to downgrade or change versions  dependencies can be tricky Github  allows the most control  easy to pack up in your version control system  lots of steps Phar (PHP Archive)  a single phar can be easily included in your projects  in development  many features dont work or dont work as expected
    •  Upgrade PEAR to the latest version  sudo pear upgrade PEAR Install PHPUnit  pear config-set auto_discover 1  pear install pear.phpunit.de/PHPUnit This will install the latest release of PHPUnit. Installing older versions is a bit trickier.
    •  pear install phpunit/DbUnit pear install phpunit/PHPUnit_Selenium pear install phpunit/PHPUnit_Story pear install phpunit/PHPUnit_TestListener_DBUS pear install phpunit/PHPUnit_TestListener_DBUS pear install phpunit/PHPUnit_TestListener_XHProf pear install phpunit/PHPUnit_TicketListener_Fogbugz pear install phpunit/PHPUnit_TicketListener_GitHub pear install phpunit/PHPUnit_TicketListener_GoogleCode pear install phpunit/PHPUnit_TicketListener_Trac pear install phpunit/PHP_Invoker
    •  Checkout source from github mkdir phpunit && cd phpunit git clone git://github.com/sebastianbergmann/phpunit.git git clone git://github.com/sebastianbergmann/dbunit.git … Still need PEAR to install YAML component pear install pear.symfony-project.com/YAML Make sure to add all these directories to your include_path Checking out specific versions of packages: cd phpunit git checkout 3.6
    •  Usage  phpunit [switches] UnitTest [UnitTest.php]  phpunit [switches] <directory> Common Switches  --stop-on-error Stop upon first error.  --stop-on-failure Stop upon first error or failure.  --debug Output debug info (test name)  -v|--verbose Output verbose information.  --bootstrap <file> Load this file before tests  --configuration <file> Specify config file (default phpunix.xml)
    •  PHPUnit runtime options can be defined using command line switches and/or xml (phpunit.xml) Bootstrap your application as needed before tests run (bootstrap.php) Define paths for code coverage output, logs, etc.
    • Its easy!! Include the autoloader in yourbootstrap or in your test cases:<?phprequire_once PHPUnit/Autoload.php;
    •  A test suite contains any number of test cases. A test case is a class that contains individual tests. A test is a method that contains the code to test something.
    • / lib/ Foo.php tests/ suite/ lib/ FooTest.php
    • <phpunit> <testsuites> <testsuite name="All Tests"> <directory>tests/suite</directory> </testsuite> <testsuite name="Some Tests"> <file>tests/suite/FooTest.php</file> <file>tests/suite/BarTest.php</file> </testsuite> </testsuites></phpunit>
    • A Fixture represents the known state of theworld. Object instantiation Database Records Preconditions & PostconditionsPHPUnit offers several built-in methods toreduce the clutter of setting up fixtures.
    • class FooTest extends PHPUnit_Framework_TestCase { //runs before each test method protected function setUp() {} //runs after each test method protected function tearDown() {} //runs before any tests public static function setUpBeforeClass() {} //runs after all tests public static function tearDownAfterClass() {}}
    • class FooTest extends PHPUnit_Framework_TestCase { protected $Foo; protected function setUp() { $this->Foo = new Foo; } public function testFoo () { $this->assertNull($this->Foo->method()); } public function testFoo1() { $this->assertNull($this->Foo->otherMethod()); }}
    • //method being tested in class Foopublic function reverseConcat($a, $b) { return strrev($a . $b);}//test method in class FooTestpublic function testReverseConcat() { $sut = new Foo; $actual = $sut->reverseConcat(Foo, Bar); $expected = raBooF; $this->assertEquals($expected, $actual);}
    •  An assertion is how we declare an expectation. Well-tested systems rely upon verification, not developer intuition. Things we can assert with PHPUnit  return values  certain methods/objects were called via Test Doubles  echo/print output
    • assertArrayHasKey() assertNull()assertClassHasAttribute() assertObjectHasAttribute()assertClassHasStaticAttribute() assertRegExp()assertContains() assertStringMatchesFormat()assertContainsOnly() assertStringMatchesFormatFile()assertCount() assertSame()assertEmpty() assertSelectCount()assertEqualXMLStructure() assertSelectEquals()assertEquals() assertSelectRegExp()assertFalse() assertStringEndsWith()assertFileEquals() assertStringEqualsFile()assertFileExists() assertStringStartsWith()assertGreaterThan() assertTag()assertGreaterThanOrEqual() assertThat()assertInstanceOf() assertTrue()assertInternalType() assertXmlFileEqualsXmlFile()assertLessThan() assertXmlStringEqualsXmlFile()assertLessThanOrEqual() assertXmlStringEqualsXmlString()
    • When assertions evaluate to TRUE the test will emit a PASS $this->assertTrue(true); $this->assertFalse(false); $this->assertEquals(foo, foo); $this->assertEmpty(array()); $this->assertNotIsSame(new stdClass, new stdClass); $this->assertArrayHasKey(foo, array(foo => bar)); $this->assertCount(2, array(apple, orange));
    • When assertions evaluate to FALSE the test will emit a FAILURE$this->assertTrue(false);$this->assertFalse(true);$this->assertEquals(foo, bar);$this->assertEmpty(array(foo));$this->assertIsSame(new stdClass, new stdClass);$this->assertArrayHasKey(foo, array());$this->assertCount(2, array(apple));
    •  game has 2 players players pick either rock, paper, or scissors rock beats scissors paper beats rock scissors beats paper round is a draw if both players pick the same object
    •  Isolation means that a unit of code is not affected by external factors. In practice we cant always avoid external dependencies, but we benefit from trying to reduce their impact on our tests. This means we need to be able to substitute external dependencies with test doubles.
    •  Test doubles are objects we use to substitute dependencies in our system under test. Stubs return prebaked values when its methods are called. Mocks are like programmable stubs. You can verify that methods are called correctly and perform more complex verification.
    •  PHPUnit provides a built in mocking framework. You can easily create test doubles that you can inject into your system under test. This allows you to swap out complex dependencies with controllable and verifiable objects.
    • public function testStub() { $stub = $this->getMock(Stub, array(foo)); $stub->expects($this->once()) ->method(foo) ->will($this->returnValue(bar)); $actual = $stub->foo(); $this->assertEquals(bar, $actual, "shouldhave returned bar");}
    •  Two ways to generate mock objects getMock() with arguments Mock Builder API
    • $this->getMock( $class_name, $mocked_methods, $constructor_args, $mock_class_name, $call_original_constructor, $call_original_clone_constructor, $disable_autoload);
    • $this->getMockBuilder($class_name) ->setMethods(array $methods) ->disableOriginalConstructor() ->disableOriginalClone() ->setConstructorArgs(array $args) ->setMockClassName($class_name) ->getMock();
    • $mock = $this->getMockBuilder(Foo) ->setMethods(array(someMethod)) ->getMock();$mock->expects($this->any()) ->method(someMethod) ->will( $this->onConsecutiveCalls(bar, baz) );$mock->someMethod(); //returns bar$mock->someMethod(); //returns baz
    • $mock = $this->getMockBuilder(Foo) ->setMethods(array(someMethod)) ->getMock();$mock->expects($this->any()) ->method(someMethod) ->will($this->throwException( new RuntimeException ));//throws a RuntimeException$mock->someMethod();
    • class WebService { protected $Http; public function __construct(Http $http) { $this->Http = $http; } public function getUrl($url = ) { return $this->Http->get($url); }}
    • require_once PHPUnit/Autoload.php;require_once WebService.php;class TestWebService extends PHPUnit_Framework_TestCase { public function testGetUrl_callsHttpCorrectly() { $http = $this->getMockBuilder(FakeClassName) ->setMethods(array(get)) ->setMockClassName(Http) ->getMock(); $http->expects($this->once()) ->method(get) ->with($url); //inject the mock via constructor $sut = new WebService($http); //test Http get $sut->getUrl(http://google.com); }}
    •  Static method calls Instantiating objects directly (hard coupling) External resources (network, file system) Globals ($_GLOBALS, Singletons)
    • class Dependency {}class SystemUnderTest { public function badIdea() { $dependency = new Dependency; }}
    • class TwitterClient { function getTweets() { $url = api.twitter.com/v2/tweets/...; return Http::consume($url); }}
    • class TwitterClient { protected $Http; public function __construct(Http $http) { $this->Http = $http; } public function getTweets() { $url = api.twitter.com/v2/tweets/...; return $this->Http->consume($url); }}
    • class TestTwitterClient extendsPHPUnit_Framework_TestCase { public function testCantMockTwitter() { $mock = $this->getMockBuilder(Http) ->setMethods(array(consume)) ->getMock(); $sut = new TwitterClient($mock); $sut->getTweets(); }}
    • class Singleton { public $data; protected static $instance; private function __construct() {} private function __clone() {} public function getInstance() { if (!self::$instance) { self::$instance = new self; } return self::$instance; }}
    • class SystemUnderTest { public function setData($data) { Singleton::getInstance()->data = $data; } public function getData() { return Singleton::getInstance()->data; }}
    • class TestSystemUnderTest extendsPHPUnit_Framework_TestCase { public function testSingletonsAreFineAtFirst() { $sut = new SystemUnderTest; $sut->setData(foo); //passes $this->assertEquals(foo, $sut->getData()); } public function testSingletonsNeverDie() { $sut = new SystemUnderTest; $actual = $sut->getData(); //fails $this->assertNull($actual); }}
    •  Many frameworks come with built in testing facilities, or the ability to easily integrate with PHPUnit. Try to use the built-in testing tools. They usually do the heavy lifting of bootstrapping your application.
    •  Get your test harness working Get your feet wet first, testing smaller/trivial code Manual verification is prudent Improve your design, refactor Write tests for new code Write tests opportunistically