This document discusses unit testing PHP code with PHPUnit. It begins with an introduction to the speaker and then covers why testing is important, how to get started with PHPUnit, writing basic tests for a sample class, using features like data providers and expecting exceptions, and more advanced topics like stubs, mocks and testing databases and Zend Framework applications. The overall message is that testing is crucial for any PHP project and PHPUnit makes it easy to start writing tests.
2. Who am I ?
Michelangelo van Dam
Independent Enterprise PHP consultant
Co-founder PHPBelgium
Mail me at dragonbe [at] gmail [dot] com
Follow me on http://twitter.com/DragonBe
Read my articles on http://dragonbe.com
See my profile on http://linkedin.com/in/michelangelovandam
2
3. Unit testing ?
• test smallest piece of code (unit)
• to verify its behavior is as expected
• exceptions are thrown
• code remains backwards compatible
3
4. Everyone should test !
• rule #1: you should test
• rule #2: see rule #1 (no excuses!!!)
• Why?
- tests are automated
- run over and over again
- detect flaws, errors, mistakes, screw ups
- progress indication of the project
4
5. Why PHPUnit ?
• part of xUnit family
• ported by Sebastian Bergmann
• PEAR package
- pear channel-discover pear.phpunit.de
- pear install phpunit/PHPUnit
5
7. <?php
My first class
class SayHello
{
public $name;
public function __construct($name = 'nobody')
{
$this->name = $name;
}
public function speak()
{
return "Hello {$this->name}!";
}
}
7
8. <?php
Run it
require_once 'sayhello.php';
$hello = new SayHello();
echo $hello->speak();
// outputs Hello nobody!
8
9. <?php
Test it !
require_once 'sayhello.php';
class SayHelloTest extends PHPUnit_Framework_TestCase
{
public function testSpeakWithoutParams ()
{
$hello = new SayHello();
$this->assertEquals("Hello nobody!", $hello->speak());
}
public function testSpeakWithParams ()
{
$hello = new SayHello('Marco');
$this->assertEquals("Hello Marco!", $hello->speak());
}
}
9
10. We’re good
$ phpunit SayHelloTest
PHPUnit 3.3.15 by Sebastian Bergmann.
..
Time: 0 seconds
OK (2 tests, 2 assertions)
10
15. <?php
CombineTest
class CombineTest extends PHPUnit_Framework_TestCase
{
/**
* @dataProvider provider
*/
public function testCombine($a, $b, $c)
{
$this->assertEquals($c, $a . ' ' . $b);
}
public function provider()
{
return array (
array ('Hello','World','Hello World'),
array ('Go','PHP','Go PHP'),
array ('This','Fails','This succeeds')
);
}
}
15
16. Testing CombineTest
# phpunit CombineTest CombineTest.php
PHPUnit 3.3.2 by Sebastian Bergmann.
..F
Time: 0 seconds
There was 1 failure:
1) testCombine(CombineTest) with data set #2 ('This', 'Fails',
'This succeeds')
Failed asserting that two strings are equal.
expected string <This succeeds>
difference < xxxxx???>
got string <This Fails>
/root/dev/phpunittutorial/CombineTest.php:9
FAILURES!
Tests: 3, Assertions: 3, Failures: 1.
16
17. Expected Exception
• testing exceptions
- that they are thrown
- are properly catched
17
18. OopsTest
<?php
class OopsTest extends PHPUnit_Framework_TestCase
{
public function testOops()
{
try {
throw new Exception('I just made a booboo');
} catch (Exception $expected) {
return;
}
$this->fail('An expected Exception was not thrown');
}
}
18
19. Testing OopsTest
# phpunit OopsTest OopsTest.php
PHPUnit 3.3.2 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 0 assertions)
19
20. Fixtures
• is a “known state” of an application
- needs to be ‘set up’ at start of test
- needs to be ‘torn down’ at end of test
- shares “states” over test methods
20
21. <?php
FixmeTest
class FixmeTest extends PHPUnit_Framework_TestCase
{
protected $fixme;
public function setUp()
{
$this->fixme = array ();
}
public function testFixmeEmpty()
{
$this->assertEquals(0, sizeof($this->fixme));
}
public function testFixmeHasOne()
{
array_push($this->fixme, 'element');
$this->assertEquals(1, sizeof($this->fixme));
}
}
21
22. Testing FixmeTest
# phpunit FixmeTest FixmeTest.php
PHPUnit 3.3.2 by Sebastian Bergmann.
..
Time: 0 seconds
OK (2 tests, 2 assertions)
22
24. Stubs
• isolates tests from external influences
- slow connections
- expensive and complex resources
• replaces a “system under test” (SUT)
- for the purpose of testing
24
25. StubTest
<?php
// example taken from phpunit.de
class StubTest extends PHPUnit_Framework_TestCase
{
public function testStub()
{
$stub = $this->getMock('SomeClass');
$stub->expects($this->any())
->method('doSometing')
->will($this->returnValue('foo'));
}
// Calling $stub->doSomething() will now return 'foo'
}
25
26. Testing StubTest
# phpunit StubTest StubTest.php
PHPUnit 3.3.2 by Sebastian Bergmann.
.
Time: 0 seconds
OK (1 test, 1 assertion)
26
27. Mocks
• simulated objects
• mimics API or behaviour
• in a controlled way
• to test a real object
27
28. <?php
ObserverTest
// example taken from Sebastian Bergmann’s slides on
// slideshare.net/sebastian_bergmann/advanced-phpunit-topics
class ObserverTest extends PHPUnit_Framework_TestCase
{
public function testUpdateIsCalledOnce()
{
$observer = $this->getMock('Observer', array('update'));
$observer->expects($this->once())
->method('update')
->with($this->equalTo('something'));
$subject = new Subject;
$subject->attach($observer)
->doSomething();
}
}
28
29. Database Testing
• ported by Mike Lively from DBUnit
• PHPUnit_Extensions_Database_TestCase
• for database-driven projects
• puts DB in know state between tests
• imports and exports DB data from/to XML
• easily added to existing tests
29
30. BankAccount Example
BankAccount example by Mike Lively
http://www.ds-o.com/archives/63-PHPUnit-Database-Extension-DBUnit-Port.html
http://www.ds-o.com/archives/64-Adding-Database-Tests-to-Existing-PHPUnit-Test-Cases.html
BankAccount Classs by Sebastian Bergmann
http://www.slideshare.net/sebastian_bergmann/testing-phpweb-applications-with-phpunit-and-selenium
The full BankAccount class
http://www.phpunit.de/browser/phpunit/branches/release/3.3/PHPUnit/Samples/BankAccountDB/
BankAccount.php
30
31. Database Testing Example
<?php
require_once 'PHPUnit/Extensions/Database/Tescase.php';
require_once 'PHPUnit/Extensions/Database/Dataset/FlatXmlDataSet.php';
require_once 'BankAccount.php';
class BankAccountDBTest extends PHPUnit_Extensions_Database_Testcase
{
public function getConnection()
{
$pdo = new PDO('sqlite::memory:');
return $this->createDefaultDBConnection($pdo, 'testdb');
}
public function getDataSet()
{
return $this->createFlatFileXMLDataSet(
dirname(__FILE__) . '/_files/dataset.xml');
}
...
31
32. Testing data store/retrieve
...
public function testNewAccountCreation()
{
$bank_account = new BankAccount('12345678912345678', $this->pdo);
$xml_dataset = $this->createFlatXMLDataSet(
dirname(__FILE__).'/_files/ba-new-account.xml');
$this->assertDataSetsEquals($xml_dataset,
$this->getConnection()->createDataSet());
}
...
32
33. Testing BankAccount
$ phpunit BankAccountDBTestPHPUnit 3.3.15 by Sebastian Bergmann.
.....
Time: 0 seconds
OK (5 tests, 10 assertions)
33
39. Interesting Readings
• PHPUnit by Sebastian Bergmann
http://phpunit.de
• Art of Unit Testing by Roy Osherove
http://artofunittesting.com
• Mike Lively’s blog
http://www.ds-o.com/archives/63-PHPUnit-Database-Extension-DBUnit-Port.html
39
40. Credits
Thumbs up monkey
http://www.flickr.com/photos/amberandclint/3266859324/
Lomo elePHPant
http://www.flickr.com/photos/jakobwesthoff/3231273333/
Sebastian Bergmann
http://www.flickr.com/photos/sebastian_bergmann/2293021853
40
42. License
This presentation is released under the Creative Commons
Attribution-Share Alike 3.0 Unported License
You are free:
- to share : to copy, distribute and transmit the work
- to remix : to adapt the work
Under the following conditions:
- attribution :You must attribute the work in the manner specified by the author or licensor
- share alike : If you alter, transform, or build upon this work, you may distribute the resulting work
only under the same, similar or a compatible license
See: http://creativecommons.org/licenses/by-sa/3.0/
42
43. Questions ?
Thank you
This presentation will be available on
http://slideshare.com/DragonBe
Vote this talk
http://joind.in/638
43