of 45
Unit Tests: Using PHPUnit to Test Your Code
With Your Host Juan Treminio
http://jtreminio.com http://github.com/jtreminio @juantreminio #phpc I love writing tests I like to work from home I sometimes write things for my website My first presentation!!!
Moderator of /r/php
You Already Test
Setting up temporary code
Write code then execute
Hitting F5
Abuse F5 to see changes
Deleting temporary code
Delete test code
Have to write it again
Why Test with PHPUnit? Automate testing
Make machine do the work
Many times faster than you
Run 3,000 tests in under a minute
Uncover bugs
Previously unidentified paths What happens if I do this?
Change in behavior
Test was passing, now failing. Red light!
Teamwork
Bob may not know your code!
Projects require tests
Cant contribute without tests
Installing PHPUnit
Dont use PEAR Old version No autocomplete Keeping multiple devs in sync
Use Composer
Easy! Fast!
composer.json { "require": { "EHER/PHPUnit": "1.6" }, "minimum-stability": "dev" }
Your First (Useless) Test
Dependency Injection Dont use new Pass in dependencies in method parameters Learn yourself some DI [1]
// Bad method public function processPayment(array $paymentDetails) { $transaction = new AuthorizeNetAIM(API_ID, TRANS_KEY); //
// Good method public function processPayment( array $paymentDetails, AuthorizeNetAIM $transaction ){ //
[1] http://fabien.potencier.org/article/11/what-is-dependency-injection
Introducing Mocks and Stubs
Mocks
Mimic the original method closely
Execute actual code
Give you some control
Stubs
Methods are completely overwritten
Allow complete control
Both are used for outside dependencies we dont want to our test to have to deal with.
How to Mock an Object
Create separate files Lots of work Lots of files to keep track of
Use getMock() Too many optional parameters! public function getMock($originalClassName, $methods = array(), array
$arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
Use getMockBuilder() ! Uses chained methods Much easier to work with
Mockery [1] Once you master getMockBuilder() it is no longer necessary
[1] https://github.com/padraic/mockery
->getMockBuilder()
Create a basic mock
Creates a mocked object of the AuthorizeNetAIM class
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->getMock();
Mocked method created at runtime
->getMockBuilder()->setMethods() 1/4
setMethods() has 4 possible outcomes
Dont call setMethods()
All methods in mocked object are stubs
Return null
Methods easily overridable
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->getMock();
Passes is_a() checks!
->getMockBuilder()->setMethods() 2/4
setMethods() has 4 possible outcomes
Pass an empty array
Same as if not calling setMethods()
All methods in mocked object are stubs
Return null
Methods easily overridable
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->setMethods(array())
->getMock();
->getMockBuilder()->setMethods() 3/4
setMethods() has 4 possible outcomes
Pass null
All methods in mocked object are mocks
Run actual code in method
Not overridable
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->setMethods(null)
->getMock();
->getMockBuilder()->setMethods() 4/4
setMethods() has 4 possible outcomes
Pass an array with method names Methods identified are stubs
Return null Easily overridable
Methods *not* identified are mocks Actual code is ran Unable to override
$payment = $this->getMockBuilder('Payment') ->setMethods( array('authorizeAndCapture',) ) ->getMock();
Other getMockBuilder() helpers
disableOriginalConstructor() Returns a mock with the class __construct() overriden
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->disableOriginalConstructor()
->getMock();
setConstructorArgs() Passes arguments to the __construct()
$payment = $this->getMockBuilder('AuthorizeNetAIM ')
->setConstructorArgs(array(API_LOGIN_ID, TRANSACTION_KEY))
->getMock();
getMockForAbstractClass() Returns a mocked object created from abstract class
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->getMockForAbstractClass();
Using Stubbed Methods 1/3
->expects()
$this->once()
$this->any()
$this->never()
$this->exactly(10)
$this->onConsecutiveCalls()
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->getMock();
$payment->expects($this->once())
->method('authorizeAndCapture');
Using Stubbed Methods 2/3
->method('name')
->will($this->returnValue('value'))
Overriding stub method means specifying what it returns.
Doesnt run any code
Expected call count
Can return anything
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->getMock();
$payment->expects($this->once())
->method('authorizeAndCapture')
->will($this->returnValue(array('baz' => 'boo')));
Using Stubbed Methods 3/3
A stubbed method can return a mock object!
$payment = $this->getMockBuilder('AuthorizeNetAIM')
->getMock();
$invoice = $this->getMockBuilder('Invoice')
->getMock();
$payment->expects($this->once())
->method('getInvoice')
->will($this->returnValue($invoice));
Assertions Define what you expect to happen Assertions check statement is true 36 assertions as of PHPUnit 3.6
$foo = true; $this->assertTrue($foo); $foo = false; $this->assertFalse($foo); $foo = 'bar'; $this->assertEquals( 'bar', $foo ); $arr = array('baz' => 'boo'); $this->assertArrayHasKey( 'baz', $arr );
Mocking Object Being Tested public function testProcessPaymentThrowsExceptionOnUnapproved() { $exceptionMessage = 'Grats on failing lol'; $this->setExpectedException( '\phpunitTests\PaymentException', $expectedExceptionMessage ); $authorizeNetAIM = $this ->getMockBuilder('\phpunitTests\AuthorizeNetAIM') ->disableOriginalConstructor() ->setConstructorArgs( array( \phpunitTests\Payment::API_ID, \phpunitTests\Payment::TRANS_KEY ) ) ->setMethods(array('authorizeAndCapture')) ->getMock(); $authorizeNetResponse = new \stdClass(); $authorizeNetResponse->approved = false; $authorizeNetResponse->error_message = $exceptionMessage; $authorizeNetAIM->expects($this->once()) ->method('authorizeAndCapture') ->will($this->returnValue($authorizeNetResponse)); $arrayDetails = array( 'amount' => 123, 'card_num' => '1234567812345678', 'exp_date' => '04/07', ); $payment = $this ->getMockBuilder('\phpunitTests\Payment') ->setMethods(array('hash')) ->getMock(); $payment->processPayment($arrayDetails, $authorizeNetAIM); }
Stub one method
Statics are Evil Or Are They?
Statics are convenient
Statics are quick to use
Statics are now easy to mock* *Only if both caller and callee are in same class
Statics create dependencies within your code
Static properties keep values PHPUnit has a backupStaticAttributes flag
Cant Mock This
Cant mock static calls to outside classes!
When to Use Statics?
Same class
Non-complicated operations
Never
Annotations
@covers Tells what method is being tested
Great for coverage reports
@group Separate tests into named groups
Dont run full test suite
@test May as well!
@dataProvider Run single test with different input
Many more!
setUp() && tearDown()
setUp() Runs code before *each* test method
Set up class variables
tearDown() Runs code after *each* test method
Useful for database interactions
XML Config File
phpunit.xml ./tests/
Errors and Failures
Failures
Errors
Mocking Native PHP Functions
DONT USE RUNKIT! Allows redefining PHP functions at runtime
Wrap functions in class methods Allows for easy mocking and stubbing
Why mock native PHP functions? Mostly shouldnt
cURL, crypt
Classes Should Remind Ignorant
Should not know they are being tested
Never change original files with test-only code
Creating wrappers for mocks is OK
No ifs or Loops in Tests
Tests should remain simple
Consider using @dataProvider
Consider splitting out the test
Consider refactoring original class
Few Assertions!
As few assertions as possible per method
Max one master assertion
Further Reading
Upcoming Series http://www.jtreminio.com
Multi-part
Much greater detail
Chris Hartjes The Grumpy Programmer's Guide
To Building Testable PHP Applications