Be Nice to Future You
Write Unit Tests With PHPUnit
John Norton
@jukebox42
Who is this guy?
My name is John Norton and I like to write code.
I work for Dyn (http://dyn.com).
Why You Should Write Unit Tests
- Because it's a good idea
-
Allows you to be confident that your change does not break anything else
- Helps other developers understand what your code does (and aids the onboarding process)
- Speeds up the development process in the future
- Helps keep you in check when writing code. It will force you to think things such as "how am I going to test this?"
- Helps you sleep at night
The Concept of Unit Tests
- Unit Tests are functions and classes designed to prove the code performs withing a set of guidelines.
- Unit Testing is most effective when your code does not contain "mammoth functions" (functions that scale hundreds of lines of code).
-
Unit Testing is much more than just writing tests. It's the corner stone of well-written, maintainable code.
Getting Started With PHPUnit
http://phpunit.de/
Installing
(the old way)
PEAR
Installing
(the new way)
PHAR or Composer
http://phpunit.de/manual/current/en/installation.html
Example
The Code
class Unicorns {
private $count = 8;
public function getCount() {
return $this->count;
}
public function steal($number) {
$this->count -= $number;
}
}
Example
The Unit Test
class UnicornsTest extends PHPUnit_Framework_TestCase {
public function test_can_steal() {
// Setup
$unicorns = Unicorns();
// Action
$unicorns->steal(5);
// Assert
$this->assertEquals(3, $unicorns->getCount()); }
}
Detour
The Assert
assertEquals($expected, $actual [, $message = '']);
-
Expected - The value you expect to get
-
Actual - The value you actually got (hopefully the same as expected)
-
Message - An optional param to describe the assert. Useful when running the tests.
Example
Running PHPUnit
$ phpunit tests/unicorns.php
PHPUnit 3.4.0 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 0.5Mb
OK (1 tests, 1 assertions)
Example
Changing The Class
class Unicorns() {
private $count = 8;
public function getCount() {
return $this->count;
}
public function steal($number) {
// Make sure we can't have negative unicorns
if($this->count - $number >= 0) { $this->count -= $number;
} }
}
Example
Changing The Tests
class UnicornsTest extends PHPUnit_Framework_TestCase() {
public function test_can_steal() {
$unicorns = Unicorns();
$unicorns->steal(5);
$this->assertEquals(3, $unicorns->getCount());
}
public function test_cant_steal() {
$unicorns = Unicorns();
$unicorns->steal(1000);
$this->assertEquals(8, $unicorns->getCount());
}}
Example
Running PHPUnit
$ phpunit tests/unicorns.php
PHPUnit 3.4.0 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 0.5Mb
OK (2 tests, 2 assertions)
Example
The "Optimizer"
class Unicorns() {
private $count = 8;
public function getCount() {
return $this->count;
}
public function steal($number) {
$this->count -= $this->count - $number >= 0 ? $number : 0; }
}
Example
Our Unit Tests
class UnicornsTest extends PHPUnit_Framework_TestCase() {
public function test_can_steal() {
$unicorns = Unicorns();
$unicorns->steal(5);
$this->assertEquals(3, $unicorns->getCount());
}
public function test_cant_steal() {
$unicorns = Unicorns();
$unicorns->steal(1000);
$this->assertEquals(8, $unicorns->getCount());
}
}
Example
Running PHPUnit
$ phpunit tests/unicorns.php
PHPUnit 3.4.0 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 0.5Mb
OK (2 tests, 2 assertions)
Example
The Bad "Optimizer"
class Unicorns() {
private $count = 8;
public function getCount() {
return $this->count;
}
public function steal($num) {
$this->count -= $this->count - $num >= 0 ? $num : $this->count;
}
}
Example
Running PHPUnit (again)
$ phpunit tests/unicorns.php
PHPUnit 3.4.0 by Sebastian Bergmann.
.F
Time: 0 seconds, Memory: 0.5Mb
There was 1 failure:
1) UnicornsTest::test_cant_steal
Failed asserting that 0 matches expected 8.
/path/to/test/unicorns.php:11
FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
Auto Detect Tests
Running PHPUnit
$ phpunit tests/
PHPUnit 3.4.0 by Sebastian Bergmann.
..
Time: 0 seconds, Memory: 0.5Mb
OK (2 tests, 2 assertions)
Files must be named properly *Test
A Journey Through PHPUnit
http://phpunit.de/documentation.html
setUp()
A function that runs before every test in a test class
class UnicornsTest extends PHPUnit_Framework_TestCase() {
protected function setUp() {
// Create the unicorns object for each test
$this->unicorns = Unicorns();
}
public function test_can_steal() {
$this->unicorns->steal(5);
$this->assertEquals(3, $this->unicorns->getCount());
}
public function test_cant_steal() {
$this->unicorns->steal(1000);
$this->assertEquals(8, $this->unicorns->getCount());
}
}
tearDown()
A function that runs after every test in a test class
class UnicornsTest extends PHPUnit_Framework_TestCase() {
protected function tearDown() {
// Clean up the mess you've made
}
public function test_can_steal() {
$this->unicorns->steal(5);
$this->assertEquals(3, $this->unicorns->getCount());
}
public function test_cant_steal() {
$this->unicorns->steal(1000);
$this->assertEquals(8, $this->unicorns->getCount());
}
}
A Bunch Of Asserts
- assertEquals()
- assertTrue()
- assertFalse()
-
assertLessThan()
- assertNull()
- assertRegExp()
I Mean TONS Of Asserts
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertContainsOnlyInstancesOf()
assertCount()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertJsonFileEqualsJsonFile()
assertJsonStringEqualsJsonFile()
assertJsonStringEqualsJsonString()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
assertLessThan()
class ReactorTest extends PHPUnit_Framework_TestCase() {
public function test_not_over() {
$reactor = Reactor();
$reactor->setTemp(9000);
$this->assertLessThan(100, $reactor->getTemp());
}
}
PHPUnit 4.1.0 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) ReactorTest::test_not_over
Failed asserting that 9000 is less than 100.
/path/to/test/reactor.php:7
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertRegExp()
class RegExpTest extends PHPUnit_Framework_TestCase {
public function testFailure() {
$this->assertRegExp('/foo/', 'bar');
}
}
Output
PHPUnit 4.1.0 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 5.00Mb
There was 1 failure:
1) RegExpTest::testFailure
Failed asserting that 'bar' matches PCRE pattern "/foo/".
/path/to/test/RegExpTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
assertStringEndsWith()
class StringEndsWithTest extends PHPUnit_Framework_TestCase
{
public function testFailure()
{
$this->assertStringEndsWith('suffix', 'foo');
}
}
PHPUnit 4.1.0 by Sebastian Bergmann.
F
Time: 1 second, Memory: 5.00Mb
There was 1 failure:
1) StringEndsWithTest::testFailure
Failed asserting that 'foo' ends with "suffix".
/path/to/test/StringEndsWithTest.php:6
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
http://phpunit.de/manual/current/en/appendixes.assertions.html
Exceptions
Our Code
class Unicorns() {
private $count = 8;
public function getCount() {
return $this->count;
}
public function steal($num) {
if ($num > $this->count) {
throw new Exception('Not enough unicorns to steal');
}
$this->count -= $num;
}
}
Exceptions
class UnicornsTest extends PHPUnit_Framework_TestCase() {
/**
* @expectedException Exception
* @expectedExceptionMessage Not enough unicorns to steal
*/
public function test_cant_steal() {
$unicorns = Unicorns();
$unicorns->steal(1000);
}
}
PHPUnit 3.4.0 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 0.5Mb
OK (1 tests, 1 assertions)
Annotations
Special decorators for classes and functions to give additional metadata about the test and occasionally give them some them magical powers.
@depends
Set test dependencies
class DependsTest extends PHPUnit_Framework_TestCase {
public function test_one() {
$this->assertTrue(false);
}
/**
* @depends test_one
*/
public function test_two() {
$this->assertTrue(true);
}
}
This does NOT define the order the tests will get run. This means that when you run the test, if a dependent fails, then the output will declare any test depending on it as "skipped".
@small @medium @large
The test will fail if the test takes longer than it "should"
class TimeTest extends PHPUnit_Framework_TestCase {
/**
* @small
*/
public function test_two() {
$result do_a_bunch_of_crazy_things();
$this->assertTrue($result);
}
}
@requires
You can define dependencies for whether a test should run or not
/**
* @requires extension mysqli
*/
class DatabaseTest extends PHPUnit_Framework_TestCase {
...
}
class DatabaseTest extends PHPUnit_Framework_TestCase {
public function setUp() {
if(something_is_not_met()) {
$this->markTestSkipped('Don\'t run this test!'); }
}
}
All The Annotations
@author
@after
@afterClass
@backupGlobals
@backupStaticAttributes
@before
@beforeClass
@codeCoverageIgnore*
@covers
@coversDefaultClass
@coversNothing
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@large
@medium
@preserveGlobalState
@requires
@runTestsInSeparateProcesses
@runInSeparateProcess
@small
@test
@testdox
@ticket
@uses
http://phpunit.de/manual/current/en/appendixes.annotations.html
Code Coverage
Building Confidence
What Is Code Coverage?
A tool that determines how much of your code is covered by unit tests. It also shows you what is and isn't being covered.
And so much more...
Example
$ phpunit --coverage-html sometest.php

(image stolen from PHPUnit's website)
Something To Think About
Be careful and don't blindly trust your coverage analysis. Code coverage can only tell you which lines were hit when you ran your unit tests. It doesn't "know" if your tests are actually testing it.
Other Tools Available
- Selenium - GUI Testing (Integrates with PHPUnit)
- phpt - unit testing for php
- SimpleTest - Alternative to PHPUnit
John Norton
@jukebox42
@jukebox42
Questions?
Be Nice To Future You - Write Unit Tests With PHPUnit
By John Norton
Be Nice To Future You - Write Unit Tests With PHPUnit
Getting started with unit testing in PHP with PHPUnit
- 9,600