Вы находитесь на странице: 1из 40

IntroductiontoPHPUnit&BestPractices

SebastianBergmann
July20th 2009

WhoIam

Sebastian Bergmann Involved in the PHP project since 2000 Creator of PHPUnit Co-Founder and Principal Consultant with thePHP.cc

TheBankAccountExample
BankAccount.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); }

} ?>

TheBankAccountExample
BankAccountTest.php
<?php require_once 'BankAccount.php'; class BankAccountTest extends PHPUnit_Framework_TestCase {

TheBankAccountExample
BankAccountTest.php
<?php require_once 'BankAccount.php'; class BankAccountTest extends PHPUnit_Framework_TestCase { public function testBalanceIsInitiallyZero() {

TheBankAccountExample
BankAccountTest.php
<?php require_once 'BankAccount.php'; class BankAccountTest extends PHPUnit_Framework_TestCase { public function testBalanceIsInitiallyZero() { $ba = new BankAccount; $this->assertEquals(0, $ba->getBalance()); return $ba; }

TheBankAccountExample
BankAccountTest.php
<?php require_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); }

TheBankAccountExample
BankAccountTest.php
<?php require_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); } // ... }

TheBankAccountExample
BankAccountTest.php
<?php require_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; }

TheBankAccountExample
BankAccountTest.php
<?php require_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()); } }

TheBankAccountExample
Runningtests
sb@ubuntu ~ % phpunit BankAccountTest PHPUnit 3.4.0 by Sebastian Bergmann. ..... Time: 0 seconds OK (5 tests, 5 assertions)

TheBankAccountExample
BankAccount.php
<?php class 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); }

} ?>

TheBankAccountExample
Runningtests
sb@ubuntu ~ % phpunit BankAccountTest PHPUnit 3.4.0 by Sebastian Bergmann. FSSSS Time: 0 seconds There was 1 failure: 1) BankAccountTest::testBalanceIsInitiallyZero Failed asserting that <integer:1> matches expected <integer:0>. /home/sb/BankAccountTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1, Skipped: 4.

TheBankAccountExample
Runningtests
sb@ubuntu ~ % phpunit --verbose BankAccountTest PHPUnit 3.4.0 by Sebastian Bergmann. FSSSS Time: 0 seconds There was 1 failure: 1) BankAccountTest::testBalanceIsInitiallyZero Failed asserting that <integer:1> matches expected <integer:0>. /home/sb/BankAccountTest.php:7 There were 4 skipped tests: 1) BankAccountTest::testBalanceCannotBecomeNegative This test depends on "BankAccountTest::testBalanceIsInitiallyZero" to pass. 2) BankAccountTest::testBalanceCannotBecomeNegative2 This test depends on "BankAccountTest::testBalanceIsInitiallyZero" to pass. 3) BankAccountTest::testDepositingMoneyWorks This test depends on "BankAccountTest::testBalanceIsInitiallyZero" to pass. 4) BankAccountTest::testWithdrawingMoneyWorks This test depends on "BankAccountTest::testDepositingMoneyWorks" to pass. FAILURES! Tests: 1, Assertions: 1, Failures: 1, Skipped: 4.

TheBankAccountExample
TestDox:Reporttestresultasexecutablespecification
sb@ubuntu ~ % phpunit --testdox BankAccountTest PHPUnit 3.4.0 by Sebastian Bergmann. BankAccount [x] Balance is initially zero [x] Balance cannot become negative [x] Depositing money works [x] Withdrawing money works

TheBankAccountExample
CodeCoverage
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.

Thesecretintestingis inwritingtestablecode
MikoHevery

UntestableCode
Most people say

Real issues

Make things private Use final keyword Write long methods ...

Mix new with logic Look for things Work in constructor Singletons Global state Static methods

This slide contains material by Miko Hevery

UntestableCode
ObjectGraphConstructionIssues
<?php class Document { private $html;
Mixing object graph construction with work

public function __construct($url) { $client = new HttpClient; $this->html = $client->get($url); }

Doing work in the constructor

This slide contains material by Miko Hevery

UntestableCode
ObjectGraphConstructionIssues
<?php class Document { private $html; public function __construct(HttpClient $client, $url) { $this->html = $client->get($url); } }

Doing work in the constructor

This slide contains material by Miko Hevery

UntestableCode
Decoupleobjectgraphconstructionfromotherwork
<?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 Miko Hevery

UntestableCode
Decoupleobjectgraphconstructionfromotherwork
<?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 Miko Hevery

UntestableCode
GlobalState

Global variables Static variables in classes Static variables in functions and methods Persistent Data

Database Filesystem memcached

Distributed Data

UntestableCode
GlobalVariablesinPHP

A variable $foo declared in the global scope is stored in $GLOBALS['foo']. $GLOBALS is a so-called superglobal. Superglobals are built-in variables that are available in all scopes. $GLOBALS['foo'] can be used to access the global 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 global variable.

UntestableCode
GlobalVariablesinPHP

$GLOBALS = array( 'GLOBALS' => &$GLOBALS, '_ENV' => array(), '_POST' => array(), '_GET' => array(), '_COOKIE' => array(), '_SERVER' => array(), '_FILES' => array(), '_REQUEST' => array() );

UntestableCode
PHPUnitandGlobalVariables

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

UntestableCode
PHPUnitandStaticAttributes

Default Behaviour

Backup static attributes of user-defined classes before the execution of a test Run the test Restore static attributes of user-defined classes after the execution of a test

$backupStaticAttributes = FALSE

Do not backup and restore static attributes of userdefined classes

$backupStaticAttributesBlacklist = array()

Backup and restore static attributes of user-defined classes but not the variables listed in the array

UntestableCode
HiddenGlobalState

Loaded source files Loaded classes and functions Sent HTTP headers new DateTime() time() mktime() rand()
This slide contains material by Miko Hevery

TestableCode
SelectedAspectsofTestability

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

PHPUnitBestPractices
UseanXMLConfigurationFile
<?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="false" backupStaticAttributes="false" syntaxCheck="false"> </phpunit>

PHPUnitBestPractices
UseanXMLConfigurationFile
<?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>

PHPUnitBestPractices
UseanXMLConfigurationFile
<?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>

PHPUnitBestPractices
UseCodeCoverageWhitelisting
<?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>

PHPUnitBestPractices
MaketheCodeCoverageinformationmoremeaningful
/** * @covers Object_Freezer::freeze */ public function testFreezingAnObjectWorks() { $this->assertEquals( array( /* ... */ ), $this->freezer->freeze(new A(1, 2, 3)) ); }

PHPUnitBestPractices
Exploitdependenciesbetweentests
/** * @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) ) ); }

PHPUnitBestPractices
Decoupletestcodefromcomplex/largesetsoftestdata
<?php class 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) ); }

PHPUnitBestPractices
Decoupletestcodefromcomplex/largesetsoftestdata
sb@ubuntu ~ % phpunit DataTest PHPUnit 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.

PHPUnitBestPractices
WritecodethatallowsyoutodisablePHPUnitfeatures

Syntax Check Backup/Restore of global variables Backup/Restore of static attributes

TheEnd
Thank you for your interest! These slides will be posted on http://slideshare.net/sebastian_bergmann

License

This presentation material is published under the 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 (but not in any way that suggests that they endorse you or your use of the work). 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.

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.

Вам также может понравиться