introductiontophpunitandbestpractices-090721111610-phpapp02
TRANSCRIPT
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 1/40
Introduction to PHPUnit & Best Practices
Sebastian Bergmann
July 20th 2009
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 2/40
Who I am
Sebastian Bergmann Involved in the PHP
project since 2000
Creator of PHPUnit Co-Founder and
Principal Consultantwith thePHP.cc
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 3/40
The Bank Account ExampleBankAccount.php<?php
class BankAccount {protected $balance = 0;
public function getBalance() {return $this->balance;
}
protected function setBalance($balance) {
if ($balance >= 0) { $this->balance = $balance;
} else {throw new RuntimeException;
}}
public function depositMoney($balance) { $this->setBalance($this->getBalance() + $balance);
}
public function withdrawMoney($balance) { $this->setBalance($this->getBalance() - $balance);
}}
?>
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 4/40
The Bank Account Example
<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {
}
BankAccountTest.php
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 5/40
The Bank Account ExampleBankAccountTest.php<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {public function testBalanceIsInitiallyZero() {
}
}
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 6/40
The Bank Account ExampleBankAccountTest.php<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {public function testBalanceIsInitiallyZero() {
$ba = new BankAccount; $this->assertEquals(0, $ba->getBalance());
return $ba;}
}
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 7/40
The Bank Account ExampleBankAccountTest.php<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {public function testBalanceIsInitiallyZero() {
$ba = new BankAccount;$this->assertEquals(0, $ba->getBalance());
return $ba;}
/*** @depends testBalanceIsInitiallyZero* @expectedException RuntimeException*/public function testBalanceCannotBecomeNegative(BankAccount $ba) {
$ba->withdrawMoney(1);}
}
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 8/40
The Bank Account ExampleBankAccountTest.php<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {public function testBalanceIsInitiallyZero() {
$ba = new BankAccount;$this->assertEquals(0, $ba->getBalance());
return $ba;}
/*** @depends testBalanceIsInitiallyZero* @expectedException RuntimeException*/public function testBalanceCannotBecomeNegative(BankAccount $ba) {
$ba->withdrawMoney(1);}
/*** @depends testBalanceIsInitiallyZero* @expectedException RuntimeException*/public function testBalanceCannotBecomeNegative2(BankAccount $ba) {
$ba->depositMoney(-1);}
// ...}
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 9/40
The Bank Account ExampleBankAccountTest.php<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {// ...
/*** @depends testBalanceIsInitiallyZero*/public function testDepositingMoneyWorks(BankAccount $ba) {
$ba->depositMoney(1); $this->assertEquals(1, $ba->getBalance());
return $ba;}
}
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 10/40
The Bank Account ExampleBankAccountTest.php<?phprequire_once 'BankAccount.php';
class BankAccountTest extends PHPUnit_Framework_TestCase {// ...
/*** @depends testBalanceIsInitiallyZero*/public function testDepositingMoneyWorks(BankAccount $ba) {
$ba->depositMoney(1);$this->assertEquals(1, $ba->getBalance());
return $ba;}
/*** @depends testDepositingMoneyWorks*/
public function testWithdrawingMoneyWorks(BankAccount $ba) { $ba->withdrawMoney(1); $this->assertEquals(0, $ba->getBalance());
}}
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 11/40
The Bank Account ExampleRunning tests
sb@ubuntu ~ % phpunit BankAccountTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.....
Time: 0 seconds
OK (5 tests, 5 assertions)
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 12/40
The Bank Account Example
<?phpclass BankAccount {
protected $balance = 1;
public function getBalance() {return $this->balance;
}
protected function setBalance($balance) {
if ($balance >= 0) {$this->balance = $balance;
} else {throw new RuntimeException;
}}
public function depositMoney($balance) {$this->setBalance($this->getBalance() + $balance);
}
public function withdrawMoney($balance) {$this->setBalance($this->getBalance() - $balance);
}}
?>
BankAccount.php
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 13/40
The Bank Account ExampleRunning tests
sb@ubuntu ~ % phpunit BankAccountTest
PHPUnit 3.4.0 by Sebastian Bergmann.
FSSSS
Time: 0 seconds
There was 1 failure:
1) BankAccountTest::testBalanceIsInitiallyZeroFailed asserting that <integer:1> matches expected <integer:0>./home/sb/BankAccountTest.php:7
FAILURES!Tests: 1, Assertions: 1, Failures: 1, Skipped: 4.
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 14/40
The Bank Account ExampleRunning tests
sb@ubuntu ~ % phpunit --verbose BankAccountTestPHPUnit 3.4.0 by Sebastian Bergmann.
FSSSS
Time: 0 seconds
There was 1 failure:
1) BankAccountTest::testBalanceIsInitiallyZeroFailed asserting that <integer:1> matches expected <integer:0>.
/home/sb/BankAccountTest.php:7
There were 4 skipped tests:
1) BankAccountTest::testBalanceCannotBecomeNegativeThis test depends on "BankAccountTest::testBalanceIsInitiallyZero" to pass.
2) BankAccountTest::testBalanceCannotBecomeNegative2This test depends on "BankAccountTest::testBalanceIsInitiallyZero" to pass.
3) BankAccountTest::testDepositingMoneyWorksThis test depends on "BankAccountTest::testBalanceIsInitiallyZero" to pass.
4) BankAccountTest::testWithdrawingMoneyWorksThis test depends on "BankAccountTest::testDepositingMoneyWorks" to pass.
FAILURES!Tests: 1, Assertions: 1, Failures: 1, Skipped: 4.
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 15/40
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 16/40
The Bank Account ExampleCode Coverage
sb@ubuntu ~ % phpunit --coverage-html /tmp/report BankAccountTest
PHPUnit 3.4.0 by Sebastian Bergmann.
.....
Time: 0 seconds
OK (5 tests, 5 assertions)
Generating code coverage report, this may take a moment.
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 17/40
”The secret in testing isin writing testable code”
Miš
ko Hevery
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 18/40
Untestable Code
Most people say Make things private
Use final keyword
Write long methods ...
Real issues Mix new with logic
Look for things
Work in constructor Singletons
Global state
Static methods
This slide contains material by Miško Hevery
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 19/40
Untestable Code
<?php
class Document {private $html;
public function __construct($url) { $client = new HttpClient; $this->html = $client->get($url);
}}
Mixing object graph
construction with work
Doing work in theconstructor
This slide contains material by Miško Hevery
Object Graph Construction Issues
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 20/40
<?php
class Document {private $html;
public function __construct(HttpClient $client, $url) { $this->html = $client->get($url);
}
}
Doing work in theconstructor
This slide contains material by Miško Hevery
Untestable CodeObject Graph Construction Issues
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 21/40
<?php
class DocumentFactory {public function __construct(HttpClient $client) { $this->client = $client;
}
public function build($url) {
return new Document($this->client->get($url));}}
This slide contains material by Miško Hevery
Untestable CodeDecouple object graph construction from other work
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 22/40
<?php
class DocumentFactory {public function __construct(HttpClient $client) {$this->client = $client;
}
public function build($url) {
return new Document($this->client->get($url));}}
class Document {private $html;
public function __construct($html) { $this->html = $html;
}}
This slide contains material by Miško Hevery
Untestable CodeDecouple object graph construction from other work
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 23/40
Untestable CodeGlobal State
Global variables Static variables in classes
Static variables in functions and methods
Persistent Data Database
Filesystem
Distributed Data memcached
bl d
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 24/40
Untestable CodeGlobal Variables in PHP
A variable $foo declared in the global scopeis stored in $GLOBALS['foo'].
$GLOBALS is a so-called superglobal.
Superglobals are built-in variables that areavailable in all scopes.
$GLOBALS['foo'] can be used to access theglobal variable $foo in the scope of a
function or method global $foo; can be used to create a local
variable with a reference to the globalvariable.
bl d
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 25/40
Untestable CodeGlobal Variables in PHP
$GLOBALS = array( 'GLOBALS' => &$GLOBALS, '_ENV' => array(), '_POST' => array(),
'_GET' => array(), '_COOKIE' => array(), '_SERVER' => array(), '_FILES' => array(),
'_REQUEST' => array());
bl d
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 26/40
Untestable CodePHPUnit and Global Variables
Default Behaviour Backup $GLOBALS before the execution of a test
Run the test
Restore $GLOBALS after the execution of a test
$backupGlobals = FALSE Do not backup and restore $GLOBALS
$backupGlobalsBlacklist = array() Backup and restore $GLOBALS but not the
variables listed in the array
bl C d
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 27/40
Untestable CodePHPUnit and Static Attributes
Default Behaviour
Backup static attributes of user-defined classes before theexecution of a test
Run the test
Restore static attributes of user-defined classes after theexecution of a test
$backupStaticAttributes = FALSE
Do not backup and restore static attributes of user-
defined classes
$backupStaticAttributesBlacklist = array()
Backup and restore static attributes of user-defined
classes but not the variables listed in the array
U bl C d
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 28/40
This slide contains material by Miško Hevery
Untestable CodeHidden Global State
Loaded source files Loaded classes and functions
Sent HTTP headers
new DateTime()
time()
mktime()
rand()
T bl C d
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 29/40
Testable Code
Few Branches More branches increase complexity
Higher complexity leads to more errors
Higher complexity makes testing harder
Few Dependencies
Improved isolation for defect localization Should be injectable to be replacable by
test-specific equivalents
Selected Aspects of Testability
PHPU i B P i
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 30/40
PHPUnit Best PracticesUse an XML Configuration File
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" backupStaticAttributes="false" syntaxCheck="false"></phpunit>
PHPU i B P i
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 31/40
PHPUnit Best PracticesUse an XML Configuration File
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"backupStaticAttributes="false"syntaxCheck="false">
<testsuites><testsuite name="My Test Suite"><directory>path/to/dir</directory>
</testsuite></testsuites>
</phpunit>
PHPU i B P i
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 32/40
PHPUnit Best PracticesUse an XML Configuration File
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"backupStaticAttributes="false"syntaxCheck="false">
<php><const name="foo" value="bar"/><var name="foo" value="bar"/><ini name="foo" value="bar"/>
</php></phpunit>
PHPU i B P i
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 33/40
PHPUnit Best PracticesUse Code Coverage Whitelisting
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"backupStaticAttributes="false"syntaxCheck="false">
<filter><whitelist addUncoveredFilesFromWhitelist="true"><directory suffix=".php">path/to/dir</directory>
</whitelist></filter>
</phpunit>
PHPU it B t P ti
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 34/40
PHPUnit Best PracticesMake the Code Coverage information more meaningful
/**
* @covers Object_Freezer::freeze*/public function testFreezingAnObjectWorks(){ $this->assertEquals(
array( /* ... */ ), $this->freezer->freeze(new A(1, 2, 3))
);}
PHPU it B t P ti
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 35/40
PHPUnit Best PracticesExploit dependencies between tests
/**
* @covers Object_Freezer::freeze* @covers Object_Freezer::thaw* @depends testFreezingAnObjectWorks*/public function testFreezingAndThawingAnObjectWorks(){ $object = new A(1, 2, 3);
$this->assertEquals( $object, $this->freezer->thaw(
$this->freezer->freeze($object))
);}
PHPU it B t P ti
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 36/40
Decouple test code from complex / large sets of test data
<?phpclass DataTest extends PHPUnit_Framework_TestCase{ /**
* @dataProvider providerMethod*/public function testAdd($a, $b, $c){
$this->assertEquals($c, $a + $b);}
public function providerMethod(){
return array(array(0, 0, 0),
array(0, 1, 1),array(1, 1, 3),array(1, 0, 1)
);}
}
PHPUnit Best Practices
PHPU it B t P ti
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 37/40
PHPUnit Best Practices
sb@ubuntu ~ % phpunit DataTestPHPUnit 3.4.0 by Sebastian Bergmann.
..F.
Time: 0 seconds
There was 1 failure:
1) testAdd(DataTest) with data (1, 1, 3)Failed asserting that <integer:2> matches expected value <integer:3>./home/sb/DataTest.php:19
FAILURES!Tests: 4, Assertions: 4, Failures: 1.
Decouple test code from complex / large sets of test data
PHPU it B st Pr ti s
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 38/40
PHPUnit Best PracticesWrite code that allows you to disable PHPUnit features
Syntax Check Backup/Restore of global variables
Backup/Restore of static attributes
The End
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 39/40
The End
Thank you for your interest!
These slides will be posted on
http://slideshare.net/sebastian_bergmann
License
8/6/2019 introductiontophpunitandbestpractices-090721111610-phpapp02
http://slidepdf.com/reader/full/introductiontophpunitandbestpractices-090721111610-phpapp02 40/40
License
This presentation material is published under the Attribution-Share Alike 3.0 Unportedlicense.
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 orlicensor (but not in any way that suggests that they endorse you or your use of thework).
● Share Alike. If you alter, transform, or build upon this work, you may distribute theresulting work only under the same, similar or a compatible license.
For any reuse or distribution, you must make clear to others the license terms of this
work. Any of the above conditions can be waived if you get permission from the copyright
holder.
Nothing in this license impairs or restricts the author's moral rights.