Practical Approach 
for testing your 
software with PHPUnit 
Mario Bittencourt
Agenda 
 Current situation 
 PHPUnit as part of the solution 
 Best Practices
Current situation
Complexity, dependencies 
and pace over time
Software Development 
Life Cycle
Testing 
 Tedious 
 Can only cover a small subset of the use cases 
 Takes a lot of time 
 Change the code to test it
Automation is key 
Goal is to create tests that can be executed 
automatically to verify that the expected behaviour 
is met
PHPUnit as part 
of the solution
Example 
class Account 
{ 
protected $balance; 
public function __construct($initialAmount) 
{ 
$this->balance = $initialAmount; 
} 
public function getBalance() 
{ 
return $this->balance; 
} 
public function deposit($amount) 
{ 
$this->balance += $amount; 
} 
}
Unit Test 
class AccountTest extends PHpUnit_Framework_TestCase 
{ 
public function testDepositIncreasesBalance() 
{ 
$account = new Account(10); 
$account->deposit(1); 
$this->assertEquals(11, $account->getBalance()); 
} 
}
Unit Test 
PHPUnit 4.3.1 by Sebastian Bergmann. 
Account 
[x] Deposit increases balance
Best Practices
Give meaningful names to your 
tests 
public function testDeposit() 
{ 
$account = new Account(10); 
$account->deposit(1); 
$this->assertEquals( 
11, 
$account->getBalance() 
); 
} 
 DON’T
Give meaningful names to your 
tests 
PHPUnit 4.3.1 by Sebastian Bergmann. 
Account 
[ ] Deposit
Give meaningful names to your 
tests 
public function testDepositIncreaseBalance() 
{ 
$account = new Account(10); 
$account->deposit(1); 
$this->assertEquals( 
11, 
$account->getBalance() 
); 
}
Use the most specific 
assertion available 
public function testDepositIncreasesBalance() 
{ 
$account = new Account(10); 
$account->deposit(10); 
$this->assertGreaterThan( 
10,$account->getBalance() 
); 
} 
 DON’T
Use the most specific 
assertion available 
public function testDepositIncreasesBalance() 
{ 
$account = new Account(10); 
$account->deposit(10); 
$this->assertEquals(20, $account->getBalance()); 
}
Use the most specific 
assertion available 
public function deposit($amount) 
{ 
$this->balance += $amount; 
if ($amount >= 10) { 
$this->balance += 1; 
} 
return true; 
}
Use the most specific 
assertion available 
There was 1 failure: 
1) AccountTest::testDepositIncreasesBalance 
Failed asserting that 21 matches expected 20. 
AccountTest.php:14
Don’t forget about protected 
methods 
class Account 
{ 
public function deposit($amount) 
{ 
if ($this->validateAmount($amount)) { 
$this->balance += $amount; 
} else { 
throw new Exception('Invalid amount'); 
} 
} 
}
Don't forget about 
your protected methods 
protected function validateAmount($amount) 
{ 
if ($amount < 0) { 
return false; 
} else { 
return true; 
} 
}
Don't forget about 
your protected methods 
public function testValidateAmountWithNegativeIsFalse() 
{ 
$account = new Account(10); 
$this->assertFalse($account->validateAmount(-1)); 
} 
Fatal error: Call to protected method Account::validateAmount()
Don't forget about 
your protected methods 
public function testValidateAmountWithNegativeIsFalse() 
{ 
$account = new Account(10); 
$reflection = new ReflectionObject($account); 
$method = $reflection->getMethod('validateAmount'); 
$method->setAccessible(true); 
$this->assertFalse( 
$method->invokeArgs($account, array(-1)) 
); 
}
Don't forget about 
your protected methods 
PHPUnit 4.3.1 by Sebastian Bergmann. 
Account 
[x] Deposit increases balance 
[x] Validate amount with negative is false
Test just one thing at a time 
public function deposit($amount) 
{ 
if ($amount == 0) { 
return false; 
} 
$this->balance += $amount; 
return true; 
}
Test just one thing at a time 
public function testDepositIncreasesBalance() 
{ 
$account = new Account(10); 
$this->assertTrue($account->deposit(1)); 
$this->assertEquals(11, $account->getBalance()); 
$this->assertFalse($account->deposit(0)); 
}
Test just one thing at a time 
PHPUnit 4.3.1 by Sebastian Bergmann. 
. 
Time: 35 ms, Memory: 3.00Mb 
OK (1 test, 3 assertions)
Test just one thing at a time 
public function deposit($amount) 
{ 
if ($amount < 0) { 
NEW CRITERIA 
return false; 
} 
$this->balance += $amount; 
return true; 
}
Test just one thing at a time 
PHPUnit 4.3.1 by Sebastian Bergmann. 
There was 1 failure: 
1) AccountTest::testDepositIncreasesBalance 
Failed asserting that true is false. 
AccountTest.php:15 
FAILURES! 
Tests: 1, Assertions: 3, Failures: 1.
Test just one thing at a time 
public function testDepositIncreasesBalance() 
{ 
$account = new Account(10); 
$this->assertTrue($account->deposit(1)); 
$this->assertEquals(11, $account->getBalance()); 
} 
public function 
testDepositWithNegativeShouldReturnFalse() 
{ 
$account = new Account(10); 
$this->assertFalse($account->deposit(-1)); 
}
Test just one thing at a time 
PHPUnit 4.3.1 by Sebastian Bergmann. 
.. 
Time: 49 ms, Memory: 3.00Mb 
OK (2 tests, 3 assertions)
Use Data Providers for 
repetitive tests 
class Score 
{ 
protected $score; 
public function update($balance, $numberCc, 
$amountInLoans) 
{ 
// do something ... 
return $newScore; 
} 
}
Use Data Providers for 
repetitive tests 
class ScoreTest extends PHpUnit_Framework_TestCase 
{ 
public function testScoreSomething1() 
{ 
$score = new Score(10); 
$this->assertEquals(300, $score->update(100, 0, 1)); 
} 
public function testScoreSomething2() 
{ 
$score = new Score(10); 
$this->assertEquals(99, $score->update(100, 1, 2)); 
} 
}
Use Data Providers for 
repetitive tests 
/** 
* @dataProvider scoreProvider 
*/ 
public function testScore($balance, $numberCc, 
$amountInLoans, $expectedScore) 
{ 
$score = new Score(10); 
$this->assertEquals( 
$expectedScore, 
$score->update($balance, $numberCc, $amountInLoans) 
); 
}
Use Data Providers for 
repetitive tests 
public function scoreProvider() 
{ 
return array( 
array(100, 0, 1, 300), 
array(100, 1, 2, 99), 
); 
}
Use Data Providers for 
repetitive tests 
PHPUnit 4.3.1 by Sebastian Bergmann. 
.. 
Time: 25 ms, Memory: 3.00Mb 
OK (2 tests, 2 assertions)
Isolate your test 
public function wire($amount, SwiftInterface $targetBank) 
{ 
if ($targetBank->transfer($amount)) { 
$this->balance -= $amount; 
return true; 
} else { 
return false; 
} 
}
Isolate your test 
class Hsbc implements SwiftInterface { 
public function transfer($amount) 
{ 
// slow method 
} 
}
Isolate your test 
public function 
testWireTransferShouldReturnTrueIfSuccessful() 
{ 
$account = new Account(10); 
$bank = new Hsbc(); 
$this->assertTrue($account->wire(5, $bank)); 
}
Isolate your test 
public function 
testWireTransferShouldReturnFalseIfFailed() 
{ 
// how to fail? 
$account = new Account(10); 
$bank = new Hsbc(); 
$this->assertFalse($account->wire(5, $bank)); 
}
Isolate your test 
public function 
testWireTransferShouldReturnTrueIfSuccessful() 
{ 
$account = new Account(10); 
$bank = $this->getMock(‘Hsbc'); 
$bank->expects($this->any()) 
->method('transfer') 
->will($this->returnValue(true)); 
$this->assertTrue($account->wire(5, $bank)); 
}
Isolate your test 
public function 
testWireTransferShouldReturnFalseIfFailed() 
{ 
$account = new Account(10); 
$bank = $this->getMock('Hsbc'); 
$bank->expects($this->any()) 
->method('transfer') 
->will($this->returnValue(false)); 
$this->assertFalse($account->wire(5, $bank)); 
}
Specify what you are testing 
with @covers 
 Use --coverage-html to generate the code 
coverage report of your code
Specify what you are testing 
with @covers
Specify what you are testing 
with @covers 
class Exchange { 
public function convert($amountUsd, $targetCurrency) 
{ 
// Do some conversion 
} 
}
Specify what you are testing 
with @covers 
public function convertBalance($targetCurrency) 
{ 
$exchange = new Exchange(); 
return $exchange->convert($this->balance, $targetCurrency); 
}
Specify what you are testing 
with @covers 
public function testConvertBalance() 
{ 
$account = new Account(10); 
$this->assertLessThan( 
10, $account->convertBalance('GBP') 
); 
}
Specify what you are testing 
with @covers
Specify what you are testing 
with @covers
Specify what you are testing 
with @covers 
/** 
* @covers Account::convertBalance 
*/ 
public function testConvertBalance() 
{ 
… 
}
Specify what you are testing 
with @covers
That is all folks! 
Contacts 
 Twitter -@bicatu 
 Email – mbneto@gmail.com 
 https://joind.in/12279 
 http://slidesha.re/1xtcT4y

Practical approach for testing your software with php unit

  • 1.
    Practical Approach fortesting your software with PHPUnit Mario Bittencourt
  • 2.
    Agenda  Currentsituation  PHPUnit as part of the solution  Best Practices
  • 3.
  • 4.
  • 5.
  • 6.
    Testing  Tedious  Can only cover a small subset of the use cases  Takes a lot of time  Change the code to test it
  • 7.
    Automation is key Goal is to create tests that can be executed automatically to verify that the expected behaviour is met
  • 8.
    PHPUnit as part of the solution
  • 9.
    Example class Account { protected $balance; public function __construct($initialAmount) { $this->balance = $initialAmount; } public function getBalance() { return $this->balance; } public function deposit($amount) { $this->balance += $amount; } }
  • 10.
    Unit Test classAccountTest extends PHpUnit_Framework_TestCase { public function testDepositIncreasesBalance() { $account = new Account(10); $account->deposit(1); $this->assertEquals(11, $account->getBalance()); } }
  • 11.
    Unit Test PHPUnit4.3.1 by Sebastian Bergmann. Account [x] Deposit increases balance
  • 12.
  • 13.
    Give meaningful namesto your tests public function testDeposit() { $account = new Account(10); $account->deposit(1); $this->assertEquals( 11, $account->getBalance() ); }  DON’T
  • 14.
    Give meaningful namesto your tests PHPUnit 4.3.1 by Sebastian Bergmann. Account [ ] Deposit
  • 15.
    Give meaningful namesto your tests public function testDepositIncreaseBalance() { $account = new Account(10); $account->deposit(1); $this->assertEquals( 11, $account->getBalance() ); }
  • 16.
    Use the mostspecific assertion available public function testDepositIncreasesBalance() { $account = new Account(10); $account->deposit(10); $this->assertGreaterThan( 10,$account->getBalance() ); }  DON’T
  • 17.
    Use the mostspecific assertion available public function testDepositIncreasesBalance() { $account = new Account(10); $account->deposit(10); $this->assertEquals(20, $account->getBalance()); }
  • 18.
    Use the mostspecific assertion available public function deposit($amount) { $this->balance += $amount; if ($amount >= 10) { $this->balance += 1; } return true; }
  • 19.
    Use the mostspecific assertion available There was 1 failure: 1) AccountTest::testDepositIncreasesBalance Failed asserting that 21 matches expected 20. AccountTest.php:14
  • 20.
    Don’t forget aboutprotected methods class Account { public function deposit($amount) { if ($this->validateAmount($amount)) { $this->balance += $amount; } else { throw new Exception('Invalid amount'); } } }
  • 21.
    Don't forget about your protected methods protected function validateAmount($amount) { if ($amount < 0) { return false; } else { return true; } }
  • 22.
    Don't forget about your protected methods public function testValidateAmountWithNegativeIsFalse() { $account = new Account(10); $this->assertFalse($account->validateAmount(-1)); } Fatal error: Call to protected method Account::validateAmount()
  • 23.
    Don't forget about your protected methods public function testValidateAmountWithNegativeIsFalse() { $account = new Account(10); $reflection = new ReflectionObject($account); $method = $reflection->getMethod('validateAmount'); $method->setAccessible(true); $this->assertFalse( $method->invokeArgs($account, array(-1)) ); }
  • 24.
    Don't forget about your protected methods PHPUnit 4.3.1 by Sebastian Bergmann. Account [x] Deposit increases balance [x] Validate amount with negative is false
  • 25.
    Test just onething at a time public function deposit($amount) { if ($amount == 0) { return false; } $this->balance += $amount; return true; }
  • 26.
    Test just onething at a time public function testDepositIncreasesBalance() { $account = new Account(10); $this->assertTrue($account->deposit(1)); $this->assertEquals(11, $account->getBalance()); $this->assertFalse($account->deposit(0)); }
  • 27.
    Test just onething at a time PHPUnit 4.3.1 by Sebastian Bergmann. . Time: 35 ms, Memory: 3.00Mb OK (1 test, 3 assertions)
  • 28.
    Test just onething at a time public function deposit($amount) { if ($amount < 0) { NEW CRITERIA return false; } $this->balance += $amount; return true; }
  • 29.
    Test just onething at a time PHPUnit 4.3.1 by Sebastian Bergmann. There was 1 failure: 1) AccountTest::testDepositIncreasesBalance Failed asserting that true is false. AccountTest.php:15 FAILURES! Tests: 1, Assertions: 3, Failures: 1.
  • 30.
    Test just onething at a time public function testDepositIncreasesBalance() { $account = new Account(10); $this->assertTrue($account->deposit(1)); $this->assertEquals(11, $account->getBalance()); } public function testDepositWithNegativeShouldReturnFalse() { $account = new Account(10); $this->assertFalse($account->deposit(-1)); }
  • 31.
    Test just onething at a time PHPUnit 4.3.1 by Sebastian Bergmann. .. Time: 49 ms, Memory: 3.00Mb OK (2 tests, 3 assertions)
  • 32.
    Use Data Providersfor repetitive tests class Score { protected $score; public function update($balance, $numberCc, $amountInLoans) { // do something ... return $newScore; } }
  • 33.
    Use Data Providersfor repetitive tests class ScoreTest extends PHpUnit_Framework_TestCase { public function testScoreSomething1() { $score = new Score(10); $this->assertEquals(300, $score->update(100, 0, 1)); } public function testScoreSomething2() { $score = new Score(10); $this->assertEquals(99, $score->update(100, 1, 2)); } }
  • 34.
    Use Data Providersfor repetitive tests /** * @dataProvider scoreProvider */ public function testScore($balance, $numberCc, $amountInLoans, $expectedScore) { $score = new Score(10); $this->assertEquals( $expectedScore, $score->update($balance, $numberCc, $amountInLoans) ); }
  • 35.
    Use Data Providersfor repetitive tests public function scoreProvider() { return array( array(100, 0, 1, 300), array(100, 1, 2, 99), ); }
  • 36.
    Use Data Providersfor repetitive tests PHPUnit 4.3.1 by Sebastian Bergmann. .. Time: 25 ms, Memory: 3.00Mb OK (2 tests, 2 assertions)
  • 37.
    Isolate your test public function wire($amount, SwiftInterface $targetBank) { if ($targetBank->transfer($amount)) { $this->balance -= $amount; return true; } else { return false; } }
  • 38.
    Isolate your test class Hsbc implements SwiftInterface { public function transfer($amount) { // slow method } }
  • 39.
    Isolate your test public function testWireTransferShouldReturnTrueIfSuccessful() { $account = new Account(10); $bank = new Hsbc(); $this->assertTrue($account->wire(5, $bank)); }
  • 40.
    Isolate your test public function testWireTransferShouldReturnFalseIfFailed() { // how to fail? $account = new Account(10); $bank = new Hsbc(); $this->assertFalse($account->wire(5, $bank)); }
  • 41.
    Isolate your test public function testWireTransferShouldReturnTrueIfSuccessful() { $account = new Account(10); $bank = $this->getMock(‘Hsbc'); $bank->expects($this->any()) ->method('transfer') ->will($this->returnValue(true)); $this->assertTrue($account->wire(5, $bank)); }
  • 42.
    Isolate your test public function testWireTransferShouldReturnFalseIfFailed() { $account = new Account(10); $bank = $this->getMock('Hsbc'); $bank->expects($this->any()) ->method('transfer') ->will($this->returnValue(false)); $this->assertFalse($account->wire(5, $bank)); }
  • 43.
    Specify what youare testing with @covers  Use --coverage-html to generate the code coverage report of your code
  • 44.
    Specify what youare testing with @covers
  • 45.
    Specify what youare testing with @covers class Exchange { public function convert($amountUsd, $targetCurrency) { // Do some conversion } }
  • 46.
    Specify what youare testing with @covers public function convertBalance($targetCurrency) { $exchange = new Exchange(); return $exchange->convert($this->balance, $targetCurrency); }
  • 47.
    Specify what youare testing with @covers public function testConvertBalance() { $account = new Account(10); $this->assertLessThan( 10, $account->convertBalance('GBP') ); }
  • 48.
    Specify what youare testing with @covers
  • 49.
    Specify what youare testing with @covers
  • 50.
    Specify what youare testing with @covers /** * @covers Account::convertBalance */ public function testConvertBalance() { … }
  • 51.
    Specify what youare testing with @covers
  • 52.
    That is allfolks! Contacts  Twitter -@bicatu  Email – mbneto@gmail.com  https://joind.in/12279  http://slidesha.re/1xtcT4y

Editor's Notes

  • #19 The test should break because the code it is testing changed but it did not