PHP and Unit Testing – PHPUnit – Survey



PHP and Unit Testing – PHPUnit – Survey

0 1


presentation-2013-04-29-phpunit

This is the presentation I'm going to give/already gave at http://www.meetup.com/Vancouver-PHP/

On Github saem / presentation-2013-04-29-phpunit

PHP and Unit Testing

PHPUnit

Created by Saem Ghani / @saemg

About Me

  • Professionally working in PHP for ~7 years (on and off).
  • Unprofessionally I dabble in a lot of different languages/technologies
  • I'm the organizer of Polyglot Vancouver
  • I've got a lot of experience bringing untestable code bases under test
  • First time using reveal.js so expect a lot of pointless slide wankery

Survey

Any unit testing experience

Currently unit testing

Have a high level of code coverage

PHP + Unit testing = PHPUnit

Your journey starts and ends here

What about the rest?

  • Unmaintained
  • Buggy
  • Poor feature set
  • ...

PHPUnit Testing

A Whirlwind Tour

Know that it's possible, not exactly how to do it

Hello, World!

              use PHPUnit_Framework_TestCase;
              class HelloWorldTest extends PHPUnit_Framework_TestCase {
                /**
                 * @test
                 */
                public function sayHello() {
                  $g = new Greeter();
                  $this->assertEquals("Hello, World!", $g->greet());
                }
              }
            
Notice the grammar. I assume an autoloader and some sort of bootstrapping

Before and After

Hug your code

            class FooerTest extends PHPUnit_Framework_TestCase {
              protected function setUp() {
                $this->fooer = new Fooer()
              }
              protected function tearDown() {
                $this->fooer->shutdown();
              }
              public function testHowAFooerFoos() {
                /* import test code */
              }
            }
            

Data Driven Tests

              class FooerTest extends PHPUnit_Framework_TestCase {
                /**
                 * @test
                 * @dataProvider myDataProvider
                 */
                public function fuzzTestFooer($input, $expected) {
                  /* ... setup code ... */
                  $this->assertEquals($expected, $fooer->foo($data));
                }
                
                public function myDataProvider() {
                  return [
                    ['a', 0],
                    ['b', 1],
                    /* ... */
                  ];
                }
              }
            

Exceptions

              /**
               * @test
               */
              public function whenPassedBadInputAnInvalidArgumentExceptionIsThrown() {
                $this->setExpectedException('InvalidArgumentException', 'Optionally, test message');
                /* ... Code that throws an exception ... */
              }
            
PHPUnit convers PHP errors to exceptions so test them that way

Inprogress Work

              /**
               * @test
               */
              public function importantSpecThatNeedsToBeImplemented() {
                $this->markTestIncomplete // can also skip tests
              }
            
PHPUnit convers PHP errors to exceptions so test them that way

Mocks/Stubs/Spies

I do all my own stunts

            class Fooer {
              public function __construct($logger) { /* ... */ }
              public function foo() {
                /* ... */
                $isLogged = $this->logger->log('I foo\'d.');
                /* ... */
              }
            }
            
            class FooerTest extends PHPUnit_Framework_TestCase {
              public function aFooerLogsEverytimeItFoos() {
                $logger = $this->getMock('Logger');
                $fooer = new Fooer($logger);
                $logger->expects($this->once())
                  ->method('log')
                  ->with($this->equalTo('I foo\'d.'))
                  ->will($this->returnValue(true));
                $fooer->foo();
              }
            }
            

Kids do do this at Home

How to make it go

phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
<phpunit
    backupGlobals               = "false"
    backupStaticAttributes      = "false"
    colors                      = "true"
    convertErrorsToExceptions   = "true"
    convertNoticesToExceptions  = "true"
    convertWarningsToExceptions = "true"
    processIsolation            = "false"
    stopOnFailure               = "false"
    syntaxCheck                 = "false"
    bootstrap                   = "bootstrap.php" >

    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>../src/Tests</directory>
        </testsuite>
    </testsuites>
</phpunit>
            

Away we go

            $> phpunit -c phpunit.xml
            

That's not all folks

Things I didn't cover

Code Coverage

xdebug, it's line based, and slows down test runs

Test Suites

After phpunit.xml, who cares?

Groups/Tags

Helps organizing, not all that handy with filter

Mocking the File System

vfsstream is awesome

Database Fixtures

I haven't found a need for this, I have doctrine

Test Dependencies

This is generally a bad sign

Testing PHP Errors

PHPUnit converts errors to exceptions and then setExpectedException can be used

Output (Echo)

Similar to setExpectedException

Logging Test Telemetry

Extending PHPUnit

Usually the furtest one goes is extending the PHPUnit_Framework_TestCase class

I'm now tired of typing out what I haven't covered, if you haven't guessed, it's a lot

Questions

Inspiration

  • How to deal with a giant code base of doom
  • How to deal with a specific gnarly type of code
  • What should my tests look like
  • How do I organize my tests

Thanks!