Unit Testing and PHP
Robert Ames
Types of testing
- Unit ...
1 + 1 == 2
- Integation ...
$lib->balance() == 100.00; $lib->bill( 10 ); $lib->balance() == 110.00;
- Browser ...
home.php => login.php => checkBalance.php
- Specifications ...
"When balance due is over $100, no new charges can be accrued."
Why Test?
- Developers add 1 feature per week
- Manual QA tests 10 features per day...
- Automated tests turnaround in seconds or minutes
- Manual QA gives results in hours or days
- Development velocity + quality!
What to test?
- Test risky things
- Calculations, financial impact, "the purpose" of your program
- Test new things
- New code is inherently risky
- Tests at this point serve multiple purposes:
- Validate your assumptions
- Define your interface / API
- Let you know when you're done
- Oh yeah- you also get repeatable, automated tests
- Test broken things
- You know... the module that always has bugs reported
- Test core things
- You know... the module that your business depends on
What not to test?
- Don't test "old" things
- Odds are they are not risky, broken, or new
- However, add tests as you find bugs
- Don't test the GUI level (if you can avoid it)
- Do basic tests, but GUI tests are very hard to keep updated
- Plus it's at least 10x slower than other options
- Don't test "throwaway code"
- Testing can increase up-front costs
- Make sure you'll get a good ROI on your "I"
Why to test?
- Testing improves confidence
- Testing makes you go faster
- Testing tells you when you're done
- Testing makes your code cleaner
- Testing lets you go home earlier
- Tests don't lie
How to test?
- Mmmmmhhh PHP
- Ideally: PHP4 and PHP5 compatible
- if not at source level, at least similar interfaces
- Test continuously
- What works for you
- But KEEP IT SIMPLE!!!!
- if ( !testFooBar() ) print "FAIL";
- doesn't need to be more complicated than that
A Good Test is...
- ...specific
- ...repeatable
- ...quick
- ...independent
Bad Test
function testChargeBad() {
$x = new BusinessAPI();
$before = $x->getBalance( 'testUser' );
$x->addCharge( 'testUser', 10 );
$after = $x->getBalance( 'testUser' );
// this duplicates business logic in your test
assert( 10 == ($after - $before) );
}
Better Test
function testChargeBetter() {
$x = new BusinessAPI();
$x->setBalance( 'testUser', 100 );
$x->addCharge( 'testUser', 10 );
$res = $x->getBalance( 'testUser' );
// this test is declarative, and can run multiple times
assert( 110 == $res );
}
TDD - Test Driven Development
- Feels "backwards"
- Very PowerfulTM
- Designs a good API
- Get tests for free
TDD - Dominoes
You need to implement a feature
- Prove that it isn't already implemented.
- How would you prove that it works?
TDD - Dominoes
- Playing a "5:5" on the board is worth 10 points.
- assert( 10 == $board->play( new Domino(5,5) ) );
TDD - Dominoes
- Can't play a "6:6" after playing a "5:5"
- assert( 10 == $board->play( new Domino(5,5) ) );
- assert( -1 == $board->play( new Domino(6,6) ) );
TDD - Dominoes
- "6:6", "6:4", "4:4" is worth 20 points
- assert( 0 == $board->play( new Domino(6,6) ) );
- assert( 0 == $board->play( new Domino(6,4), "east" ) );
- assert( 20 == $board->play( new Domino(4,4), "east" ) );
TDD - Now write the code
// the simplest thing to make the
// tests runnable (some should fail)
class BOARD {
function play( $dom )
{
return 0;
}
}
TDD - Dominoes
- Playing a "5:5" on the board is worth 10 points.
- assert( 10 == $board->play( new Domino(5,5) ) );
TDD - Dominoes
- Can't play a "6:6" after playing a "5:5"
- assert( 10 == $board->play( new Domino(5,5) ) );
- assert( -1 == $board->play( new Domino(6,6) ) );
TDD - Dominoes
- "6:6", "6:4", "4:4" is worth 20 points
- assert( 0 == $board->play( new Domino(6,6) ) );
- assert( 0 == $board->play( new Domino(6,4), "east" ) );
- assert( 20 == $board->play( new Domino(4,4), "east" ) );
TDD - Now write more code
- Playing a "5:5" on the board is worth 10 points.
- assert( 10 == $board->play( new Domino(5,5) ) );
class BOARD {
function play( $dom )
{
}
}
Unit Testing
- Often considered an advanced topic
- Unit tests can help anybody
- Not hard to get started
Tactical Goals
In the short term, focus on these:
- Verify a function works properly
- Catch errors quickly (via continuous testing)
- Make changes with confidence
Strategic Goals
Longer term benefits:
- Swap technologies (MySQL → PostgreSQL, PHP4 → PHP5)
- Guaranteed Up-to-date Documentation
- Integrate new developers quicker
Resources
- Pragmatic Project Automation
- Pragmatic Unit Testing (in Java / C#)
- Very good introduction to Unit Testing
- Easy to transfer to PHP
- Advanced PHP Programming