SlideShare a Scribd company logo
UNIT TESTING
WITH PHPUNIT
Paweł Michalik
THERE'S LIFE OUTSIDE OF TDD
Hofmeister, ideals are a beautiful thing, but
over the hills is too far away.
Loose translation from "The loony tales" by Kabaret Potem
WHAT, FOR WHAT AND WITH WHAT?
WHAT?
Tests that check if a certain unit of code (whatever it means)
works properly
Tests for given unit are independent from the rest of the
codebase and other tests
FOR WHAT?
Easy regression finding
Can make you more inclined to use certain good practices
(dependency injection FTW)
Helps catching dead code
Documentation without documenting
WITH WHAT?
PhpUnit!
There should be an installation manual
But let's be serious - nobody will remember that
WHAT TO TEST?
A UNIT OF CODE
class LoremIpsum
{
private $dependency;
public function getDependency()
{
return $this->dependency;
}
public function setDependency(Dependency $dependency)
{
$this->dependency = $dependency;
}
}
A UNIT OF CODE CONTINUED
class LoremIpsum
{
public function doStuff()
{
if (!$this->dependency) {
throw new Exception("I really need this, mate");
}
$result = array();
foreach ($this->dependency->getResults() as $value) {
$value = 42 / $value;
$value .= ' suffix';
$result[$value] = true;
}
return $result;
}
}
WE CAN'T TEST THESE
class LoremIpsum
{
private $dependency;
}
WE DON'T WANT TO TEST THOSE
class LoremIpsum
{
public function getDependency()
{
return $this->dependency;
}
public function setDependency(Dependency $dependency)
{
$this->dependency = $dependency;
}
}
WHAT DO WE NEED TO TEST HERE?
public function doStuff()
{
if (!$this->dependency) {
throw new Exception("I really need this, mate");
}
$result = array();
foreach ($this->dependency->getResults() as $value) {
$value = 42 / $value;
$value .= ' suffix';
$result[$value] = true;
}
return $result;
}
WHAT DO WE NEED TO TEST HERE?
public function doStuff()
{
if (!$this->dependency) {
throw new Exception("I really need this, mate");
}
$result = array();
foreach ($this->dependency->getResults() as $value) {
$value = 42 / $value;
$value .= ' suffix';
$result[$value] = true;
}
return $result;
}
WHAT DO WE NEED TO TEST HERE?
public function doStuff()
{
if (!$this->dependency) {
throw new Exception("I really need this, mate");
}
$result = array();
foreach ($this->dependency->getResults() as $value) {
$value = 42 / $value;
$value .= ' suffix';
$result[$value] = true;
}
return $result;
}
Unit testing with PHPUnit - there's life outside of TDD
WHY NOT THIS?
public function doStuff()
{
if (!$this->dependency) {
throw new Exception("I really need this, mate");
}
$result = array();
foreach ($this->dependency->getResults() as $value) {
$value = 42 / $value;
$value .= ' suffix';
$result[$value] = true;
}
return $result;
}
THIS TOO, BUT...
1. Unit testing doesn't replace debugging.
2. Unit testing can make your code better, but won't really do
anything for you.
3. Unit testing focus your attention on what the code does, so
you can spot potential problems easier.
LET'S CHECK IF IT WORKS
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
public function testDoingStuffTheRightWay()
{
$testedObject = new LoremIpsum();
$testedObject->setDependency(new Dependency());
$this->assertInternalType('array', $testedObject->doStuff());
}
}
LET'S CHECK IF IT DOESN'T WORK
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
/**
* @expectedException Exception
* @expectedExceptionMessage I really need this, mate
*/
public function testDoingStuffTheWrongWay()
{
$testedObject = new LoremIpsum();
$testedObject->doStuff();
}
}
Unit testing with PHPUnit - there's life outside of TDD
Unit testing with PHPUnit - there's life outside of TDD
ASSERTIONS
Assertions have to check if the expected value corresponds
to an actuall result from the class we test.
Fufilling all of the assertions in a test means a positive result,
failing to meet any of them means a negative result.
CHOICE APLENTY
assertContains
assertCount
assertEmpty
assertEquals
assertFalse
assertFileExists
assertInstanceOf
assertSame
assertNull
...
CHOICE APLENTY
assertContains
assertCount
assertEmpty
assertEquals
assertFalse
assertFileExists
assertInstanceOf
assertSame
assertNull
...
EXCEPTIONS:
When we want to check if a method throws an exception,
instead of using assertX, we use annotations that will provide
the same service.
/**
* @expectedException ClassName
* @expectedExceptionCode 1000000000
* @expectedExceptionMessage Exception message (no quotes!)
* @expectedExceptionMessageRegExp /^Message as regex$/
*/
EXCEPTIONS:
Or methods, named in the same way as annotations:
$this->expectException('ClassName');
$this->expectExceptionCode(1000000000);
$this->expectExceptionMessage('Exception message');
$this->expectExceptionMessageRegExp('/^Message as regex$/');
TO SUMMARIZE:
1. Testing for edge cases (check for conditional expression
evaluating to both true and false)
2. Testing for match of actuall and expected result
3. And thrown exceptions
4. We can think of unit test as a way of contract
5. We don't test obvious things (PHP isn't that
untrustworthy)
WHAT TO TEST WITH?
WHERE DO WE GET DEPENDENCY FROM??
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
public function testDoingStuffTheRightWay()
{
$testedObject = new LoremIpsum();
$testedObject->setDependency(new Dependency());
$this->assertInternalType('array', $testedObject->doStuff());
}
}
WHAT DOES THE DEPENDENCY DO?
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
public function testDoingStuffTheRightWay()
{
$testedObject = new LoremIpsum();
$testedObject->setDependency(new Dependency());
$this->assertInternalType('array', $testedObject->doStuff());
}
}
Unit testing with PHPUnit - there's life outside of TDD
UNIT TEST FOR A GIVEN CODE UNIT ARE INDEPENDENT FROM
OTHER CODE UNITS
UNIT TEST FOR A GIVEN CLASS ARE INDEPENDENT FROM ITS
DEPENDENCIES
We can test the dependency and make sure it returns some
kind of data, but what if we pass a different object of the same
type instead?
In that case we need to check what happend if the
dependency returns:
Values of different types
Values in different formats
Empty value
How many additional classes do we need?
ZERO
TEST DOUBLES
Objects imitating objects of a given type
Used only to perform tests
We declare what we expect of them
We declare what they can expect of us
And see what happens
TERMINOLOGY
Dummy - object with methods returning null values
Stub - object with methods returning given values
Mock - as above and also having some assumptions in
regard of executing the method (arguments passed, how
many times it's executed)
And a lot more
YOU DON'T HAVE TO REMEMBER THAT THOUGH
Terms from the previous slide are often confused, unclear or
just not used.
You should use whatever terms are clear for you and your
team or just deal with whatever is thrown at you.
Often the test double framework will determine it for us.
CASE A
CASE B
CASE C
PHPUnit comes with a mocking framework, but lets you use
any other you want.
LET'S MAKE A DEPENDENCY!
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
/**
* @var PHPUnit_Framework_MockObject_MockObject|Dependency
*/
private $dependencyMock;
public function setUp()
{
$this->dependencyMock = $this->getMockBuilder(Dependency::class)
->disableOriginalConstructor()
->setMethods(array('getResults'))
->getMock();
}
}
WILL IT WORK?
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
public function testDoingStuffTheRighterWay()
{
$testedObject = new LoremIpsum();
$testedObject->setDependency($this->dependencyMock);
$this->assertInternalType('array', $testedObject->doStuff());
$this->assertEmpty($testedObject->doStuff());
}
}
OH NOES! D:
LETS DESCRIBE OUR REQUIREMENTS
class LoremIpsumTest extends PHPUnit_Framework_TestCase
{
public function testDoingStuffTheRighterWay()
{
$this->dependencyMock->expects($this->once())
->method('getResults')
->willReturn(array());
$testedObject = new LoremIpsum();
$testedObject->setDependency($this->dependencyMock);
$this->assertInternalType('array', $testedObject->doStuff());
$this->assertEmpty($testedObject->doStuff());
}
}
YISS! :D
WHAT IS PROVIDED IN MOCKBUILDER?
Defining mocked methods
If we won't use setMethods - all methods will return null
If we pass an array to setMethods:
Methods which names we passed can be overwritten
or will return null
Methods which names we didn't pass will behave as
specified in the mocked class
If we passed null to setMethods - all methods will behave
as specified in the mocked class
WHAT ELSE IS PROVIDED IN MOCKBUILDER?
Disabling the constructor
Passing arguments to the constructor (if it's public)
Mocking abstract classes (if we overwrite all abstract
methods)
Mocking traits
GREAT EXPECTATIONS
expects() method lets us define how many times (if ever) a
method should be executed in given conditions.
What can we pass to it?
$this->any()
$this->once()
$this->exactly(...)
$this->never()
$this->atLeast(...)
...
WHAT WE CAN OFFER?
with() methods allows us to inform the mock what
parameters are expected to be passed to method.
What can we pass to it?
Concrete value (or many if we have many arguments)
$this->isInstanceOf(...)
$this->callback(...)
IF A METHOD DOES NOT MEET THE EXPECTATIONS OR DOESN'T
GET REQUIRED PARAMETERS THE TEST WILL FAIL!
WHAT DO WE EXPECT IN RETURN?
willX() methods allow us to define what should be returned
from a method in given circumstances.
While previous methods were more like assertions, willX()
allows us to define methods behaviour.
That allows us to test different cases without creating any
additional classes.
WHAT CAN WE USE?
willReturn()
willReturnSelf()
willReturnCallback()
...
SUMMARY:
1. With mock objects we cas pass dependencies of a given
type without creating an actual object (isolation1)
2. We can test different cases without creating any new
classes or parametrisation
3. They free us from the necessity of creating dependencies
of dependencies
4. Make test independent from external resources as
webservices or database
5. Create additional test rules
LETS FIX LOREM IPSUM
AFTER CHANGES
public function divideAndSuffixDependencyResults()
{
if (!$this->dependency) {
throw new Exception("You need to specify the dependency");
}
$result = array();
foreach ($this->dependency->getResults() as $value) {
$sanitizedValue = (float)$value;
if ($sanitizedValue == 0) {
continue;
}
$sanitizedValue = 42 / $sanitizedValue;
$sanitizedValue .= ' suffix';
$result[] = $sanitizedValue;
}
return $result;
}
EQUIVALENCE CLASSES
ANSWER
public function testDivideByZeroIgnored() {
$this->dependencyMock->expects($this->once())
->method('getResults')
->willReturn(array(0));
$testedObject = new LoremIpsum();
$testedObject->setDependency($this->dependencyMock);
$result = $testedObject->divideAndSuffixDependencyResults();
$this->assertEmpty($result);
$this->assertInternalType('array', $result);
}
ANOTHER ANSWER
public function testDivideByZeroIgnored2() {
$this->dependencyMock->expects($this->once())
->method('getResults')
->willReturn(array(0,2));
$testedObject = new LoremIpsum();
$testedObject->setDependency($this->dependencyMock);
$result = $testedObject->divideAndSuffixDependencyResults();
$this->assertEquals($result, array('21 suffix'));
}
YET ANOTHER ANSWER
public function testDivideByZeroIgnored3() {
$this->dependencyMock->expects($this->once())
->method('getResults')
->willReturn(array(0,2));
$testedObject = new LoremIpsum();
$testedObject->setDependency($this->dependencyMock);
$result = $testedObject->divideAndSuffixDependencyResults();
$this->assertCount(1, $result);
}
PROPER ANSWER DOESN'T MATTER IF WE ASK FOR SOMETHING
ELSE THAN WE ACTUALLY HAVE TO KNOW
HOW SHOULD WE TEST?LIVE
ORGANISING TEST
Libraries should have test directory on the same level as
directory with sources
Applications should have test directory on the same level
as directory with modules
Bootstrap.php (autoloader) and PHPUnit configuration
should be inside the test directory
Directory hierarchy inside the test directory should be the
same as in the sources directory
If we use different types of tests - the test directory should
be also divided into subdirectories (unit, integration,
functional)
Unit testing with PHPUnit - there's life outside of TDD
<!--?xml version="1.0" encoding="UTF-8"?-->
<phpunit bootstrap="Bootstrap.php" colors="true">
<testsuites>
<testsuite name="Application">
<directory>./ApplicationTests</directory>
</testsuite>
</testsuites>
</phpunit>
Unit testing with PHPUnit - there's life outside of TDD
ONE CLASS - AT LEAST ONE TEST SUITE
Two simple rules:
1. If a few tests need a different setup than others - we should
move the setup operations into those tests (or extract a
method that creates that setup)
2. If many tests need a different setup than others - we should
move those test to a different suite and have a separate
setup
WHY SETUP() WHEN YOU CAN __CONSTRUCT()?
setUp() is executed before every test, providing a "fresh"
testing environment every time.
Its counterpart is takeDown(), executed after every test.
TESTS AS DOCUMENTATION
Lets call our tests a little different:
public function testDivisionAndSuffixReturnsArray
WhenDependencyIsProvided();
public function testDivisionAndSuffixThrowsException
WhenDependencyWasNotProvided();
public function testDivisionAndSuffixIgnoresResultsEquivalentToZero();
public function testDivisionAndSuffixIgnoresResultsEquivalentToZero2();
public function testDivisionAndSuffixIgnoresResultsEquivalentToZero3();
TESTS AS DOCUMENTATION
Lets make our configuration a little different:
<!--?xml version="1.0" encoding="UTF-8"?-->
<phpunit bootstrap="Bootstrap.php" colors="true">
<testsuites>
<testsuite name="Application">
<directory>./ApplicationTests</directory>
</testsuite>
</testsuites>
<logging>
<log type="testdox-text" target="php://stdout">
</log></logging>
</phpunit>
TESTS AS DOCUMENTATION
Run it and...
WE CAN MAKE IT BETTER
OR EVEN BETTER
APPLICATIONTESTSSERVICELOREMIPSUMDIV
ISIONANDSUFFIX
Returns array when dependency is provided
Throws exception when dependency was not provided
Results equivalent to zero are not processed
All we have to do is to change testdox-text to testdox-html
and provide a file path!
Or use --testdox-html parameter in terminal.
JUST ADVANTAGES!
JUST ADVANTAGES!
1. Two tasks done at once
2. Clear naming convention...
3. ...which also helps to decide what to test
4. Plus a convention of separating test into suites
CODE COVERAGE
1. Easy way to see what was already tested and what we still
have to test
2. Can help with discovering dead code
3. Is not a measure of test quality
ONCE AGAIN
CODE COVERAGE
1. Easy way to see what was already tested and what we still
have to test
2. Can help with discovering dead code
3. IS NOT A MEASURE OF TEST QUALITY
RUN IT AND...
HOW DID DEPENDENCY GOT INTO THIS?
ACTUAL RESULT
ANYTHING ELSE?
C.R.A.P. (Change Risk Analysis and Predictions) index -
relation of cyclomatic complexity of a method to its code
coverage.
Low complexity means low risk, even without testing
(getters, setters)
Medium complexity risks can be countered with high code
coverage
High complexity means that even testing won't help us
HOW TO TEST WHAT WE CAN'T REACH?
abstract class AbstractIpsum
{
protected $dependency;
public function __construct($parameter)
{
if (is_object($parameter)) {
$this->dependency = $parameter;
} else if (is_string($parameter)) {
if (class_exists($parameter)) {
$this->dependency = new $parameter;
} else {
throw new Exception($parameter." does not exist");
}
} else {
throw new Exception("Invalid argument");
}
}
}
NOT THIS WAY FOR SURE
public function testPassingObjectAsParameterAssignsObjectToProperty()
{
$expectedValue = new DateTime();
$mock = $this->getMockBuilder(AbstractIpsum::class)
->setConstructorArgs(array($expectedValue))
->getMockForAbstractClass();
$this->assertSame($expectedValue, $mock->dependency);
}
:(
REFLECTION TO THE RESCUE!
public function testPassingObjectAsParameterAssignsObjectToProperty()
{
$expectedValue = new DateTime();
$mock = $this->getMockBuilder(AbstractIpsum::class)
->setConstructorArgs(array($expectedValue))
->getMockForAbstractClass();
$reflectedClass = new ReflectionClass(AbstractIpsum::class);
$property = $reflectedClass->getProperty('dependency');
$property->setAccessible(true);
$actualValue = $property->getValue($mock);
$this->assertSame($expectedValue, $actualValue);
}
We'll go through only one test, but it's easy to spot that this
class have bigger potential. If we wrote more tests we could
use a different approach.
Instead of creating mock in every test we could create in
during setup, don't call the constructor and use reflection to
call it in our tests.
PROBLEMS WITH REFLECTION
NO PROBLEMS WITH REFLECTION
IN ACTION
public function testPassingObjectAsParameterAssignsObjectToProperty2()
{
$expectedValue = new DateTime();
$mock = $this->getMockBuilder(AbstractIpsum::class)
->setConstructorArgs(array($expectedValue))
->getMockForAbstractClass();
$mockClosure = Closure::bind(
function (AbstractIpsum $abstractIpsum) {
return $abstractIpsum->dependency;
},
null,
AbstractIpsum::class
);
$actualValue = $mockClosure($mock);
$this->assertSame($expectedValue, $actualValue);
}
CAN WE TEST PRIVATE METHODS THIS WAY?
NO
It is possible in practice, but worthless. We test only the
public methods and their calls should cover private methods.
Private methods are what's called 'implementation detail' and
as such should not be tested.
OK, BUT I HAVE LOTS OF LOGIC IN A PRIVATE METHOD AND I
WON'T TEST IT THROUGH API
This is a hint that there's a bigger problem behind. You should
think if you actually shouldn't:
1. Change it to public
2. Extract it to its own class (method object pattern)
3. Extract it, along with similar methods, to their own class
(maybe we failed with the whole single responsibility
principle thing)
4. Bite the bullet and test through the API
SUMMARY
1. Tests organisation - directories and files resemble the
project hierarchy
2. We don't need to test a whole class in one test suite
3. We can make documentation writing tests
4. If we need to access private fields and methods we can use
reflection and Closure API
WHAT'S NEXT?
PHPUNIT PLUGINS
https://phpunit.de/plugins.html
PHPUNIT PLUGINS
https://github.com/whatthejeff/nyancat-phpunit-
resultprinter
PROPHECY
https://github.com/phpspec/prophecy
MUTATION TESTING
Testing tests
Unit tests are executed on slightly changed classes
Changes are made to logic conditions, arithmetic
operations, literals, returned values, and so on
Shows us if code regressions were found by our tests
https://github.com/padraic/humbug
RECOMMENDED READING
xUnit Patterns
PHPUnit manual
Michelangelo van Dam - Your code are my tests!
Marco Pivetta - Accessing private PHP class members
without reflection
THE END!
ANY QUESTIONS?
THANKS FOR WATCHING!
http://pawelmichalik.net/presentations

More Related Content

PDF
Introduction to Unit Testing with PHPUnit
PPT
Advanced PHPUnit Testing
PDF
New Features PHPUnit 3.3 - Sebastian Bergmann
 
PPTX
PHPUnit: from zero to hero
PDF
PhpUnit Best Practices
PPT
Unit Testing using PHPUnit
PPTX
Unit Testing Presentation
PDF
PHPUnit best practices presentation
Introduction to Unit Testing with PHPUnit
Advanced PHPUnit Testing
New Features PHPUnit 3.3 - Sebastian Bergmann
 
PHPUnit: from zero to hero
PhpUnit Best Practices
Unit Testing using PHPUnit
Unit Testing Presentation
PHPUnit best practices presentation

What's hot (20)

PPT
Test Driven Development with PHPUnit
PDF
Unit testing PHP apps with PHPUnit
PDF
Test your code like a pro - PHPUnit in practice
PPT
Phpunit testing
PPTX
Unit Testng with PHP Unit - A Step by Step Training
PDF
Beginning PHPUnit
PPT
Unit testing
PDF
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
KEY
PHPUnit testing to Zend_Test
PPTX
Test in action week 2
PPTX
Laravel Unit Testing
PDF
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
PDF
JUnit Kung Fu: Getting More Out of Your Unit Tests
PDF
Workshop quality assurance for php projects - ZendCon 2013
PPTX
Test in action week 4
PDF
UA testing with Selenium and PHPUnit - PFCongres 2013
PDF
An introduction to Google test framework
PDF
Test Driven Development With Python
PDF
C++ Unit Test with Google Testing Framework
Test Driven Development with PHPUnit
Unit testing PHP apps with PHPUnit
Test your code like a pro - PHPUnit in practice
Phpunit testing
Unit Testng with PHP Unit - A Step by Step Training
Beginning PHPUnit
Unit testing
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
PHPUnit testing to Zend_Test
Test in action week 2
Laravel Unit Testing
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
JUnit Kung Fu: Getting More Out of Your Unit Tests
Workshop quality assurance for php projects - ZendCon 2013
Test in action week 4
UA testing with Selenium and PHPUnit - PFCongres 2013
An introduction to Google test framework
Test Driven Development With Python
C++ Unit Test with Google Testing Framework
Ad

Similar to Unit testing with PHPUnit - there's life outside of TDD (20)

PDF
Objects, Testing, and Responsibility
PDF
Mocking Demystified
PPT
SystemVerilog OOP Ovm Features Summary
PDF
Beyond Design Principles and Patterns
PDF
TDD step patterns
PPT
PHP Unit Testing
PPT
Mocking Dependencies in PHPUnit
PPT
Mocking Dependencies in PHPUnit
PDF
Unit testing with PHPUnit
PDF
Effective Unit Testing
PDF
Java Class Design
PPT
Unit testing
PPT
Testing And Drupal
PDF
Fighting Fear-Driven-Development With PHPUnit
PPTX
Professional-grade software design
PDF
Testing untestable code - IPC12
PPT
Java tutorial PPT
PPT
Java tutorial PPT
KEY
Test-driven Development for TYPO3
Objects, Testing, and Responsibility
Mocking Demystified
SystemVerilog OOP Ovm Features Summary
Beyond Design Principles and Patterns
TDD step patterns
PHP Unit Testing
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
Unit testing with PHPUnit
Effective Unit Testing
Java Class Design
Unit testing
Testing And Drupal
Fighting Fear-Driven-Development With PHPUnit
Professional-grade software design
Testing untestable code - IPC12
Java tutorial PPT
Java tutorial PPT
Test-driven Development for TYPO3
Ad

Recently uploaded (20)

PPTX
VVF-Customer-Presentation2025-Ver1.9.pptx
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
System and Network Administration Chapter 2
PDF
medical staffing services at VALiNTRY
PPTX
Online Work Permit System for Fast Permit Processing
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
System and Network Administraation Chapter 3
PDF
Nekopoi APK 2025 free lastest update
PDF
top salesforce developer skills in 2025.pdf
PPTX
history of c programming in notes for students .pptx
PPTX
ManageIQ - Sprint 268 Review - Slide Deck
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PPTX
Transform Your Business with a Software ERP System
PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
VVF-Customer-Presentation2025-Ver1.9.pptx
Wondershare Filmora 15 Crack With Activation Key [2025
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PTS Company Brochure 2025 (1).pdf.......
Which alternative to Crystal Reports is best for small or large businesses.pdf
System and Network Administration Chapter 2
medical staffing services at VALiNTRY
Online Work Permit System for Fast Permit Processing
How Creative Agencies Leverage Project Management Software.pdf
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
System and Network Administraation Chapter 3
Nekopoi APK 2025 free lastest update
top salesforce developer skills in 2025.pdf
history of c programming in notes for students .pptx
ManageIQ - Sprint 268 Review - Slide Deck
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Transform Your Business with a Software ERP System
Softaken Excel to vCard Converter Software.pdf
Design an Analysis of Algorithms II-SECS-1021-03
Adobe Illustrator 28.6 Crack My Vision of Vector Design

Unit testing with PHPUnit - there's life outside of TDD

  • 2. THERE'S LIFE OUTSIDE OF TDD Hofmeister, ideals are a beautiful thing, but over the hills is too far away. Loose translation from "The loony tales" by Kabaret Potem
  • 3. WHAT, FOR WHAT AND WITH WHAT?
  • 4. WHAT? Tests that check if a certain unit of code (whatever it means) works properly Tests for given unit are independent from the rest of the codebase and other tests
  • 5. FOR WHAT? Easy regression finding Can make you more inclined to use certain good practices (dependency injection FTW) Helps catching dead code Documentation without documenting
  • 6. WITH WHAT? PhpUnit! There should be an installation manual But let's be serious - nobody will remember that
  • 8. A UNIT OF CODE class LoremIpsum { private $dependency; public function getDependency() { return $this->dependency; } public function setDependency(Dependency $dependency) { $this->dependency = $dependency; } }
  • 9. A UNIT OF CODE CONTINUED class LoremIpsum { public function doStuff() { if (!$this->dependency) { throw new Exception("I really need this, mate"); } $result = array(); foreach ($this->dependency->getResults() as $value) { $value = 42 / $value; $value .= ' suffix'; $result[$value] = true; } return $result; } }
  • 10. WE CAN'T TEST THESE class LoremIpsum { private $dependency; }
  • 11. WE DON'T WANT TO TEST THOSE class LoremIpsum { public function getDependency() { return $this->dependency; } public function setDependency(Dependency $dependency) { $this->dependency = $dependency; } }
  • 12. WHAT DO WE NEED TO TEST HERE? public function doStuff() { if (!$this->dependency) { throw new Exception("I really need this, mate"); } $result = array(); foreach ($this->dependency->getResults() as $value) { $value = 42 / $value; $value .= ' suffix'; $result[$value] = true; } return $result; }
  • 13. WHAT DO WE NEED TO TEST HERE? public function doStuff() { if (!$this->dependency) { throw new Exception("I really need this, mate"); } $result = array(); foreach ($this->dependency->getResults() as $value) { $value = 42 / $value; $value .= ' suffix'; $result[$value] = true; } return $result; }
  • 14. WHAT DO WE NEED TO TEST HERE? public function doStuff() { if (!$this->dependency) { throw new Exception("I really need this, mate"); } $result = array(); foreach ($this->dependency->getResults() as $value) { $value = 42 / $value; $value .= ' suffix'; $result[$value] = true; } return $result; }
  • 16. WHY NOT THIS? public function doStuff() { if (!$this->dependency) { throw new Exception("I really need this, mate"); } $result = array(); foreach ($this->dependency->getResults() as $value) { $value = 42 / $value; $value .= ' suffix'; $result[$value] = true; } return $result; }
  • 17. THIS TOO, BUT... 1. Unit testing doesn't replace debugging. 2. Unit testing can make your code better, but won't really do anything for you. 3. Unit testing focus your attention on what the code does, so you can spot potential problems easier.
  • 18. LET'S CHECK IF IT WORKS class LoremIpsumTest extends PHPUnit_Framework_TestCase { public function testDoingStuffTheRightWay() { $testedObject = new LoremIpsum(); $testedObject->setDependency(new Dependency()); $this->assertInternalType('array', $testedObject->doStuff()); } }
  • 19. LET'S CHECK IF IT DOESN'T WORK class LoremIpsumTest extends PHPUnit_Framework_TestCase { /** * @expectedException Exception * @expectedExceptionMessage I really need this, mate */ public function testDoingStuffTheWrongWay() { $testedObject = new LoremIpsum(); $testedObject->doStuff(); } }
  • 22. ASSERTIONS Assertions have to check if the expected value corresponds to an actuall result from the class we test. Fufilling all of the assertions in a test means a positive result, failing to meet any of them means a negative result.
  • 25. EXCEPTIONS: When we want to check if a method throws an exception, instead of using assertX, we use annotations that will provide the same service. /** * @expectedException ClassName * @expectedExceptionCode 1000000000 * @expectedExceptionMessage Exception message (no quotes!) * @expectedExceptionMessageRegExp /^Message as regex$/ */
  • 26. EXCEPTIONS: Or methods, named in the same way as annotations: $this->expectException('ClassName'); $this->expectExceptionCode(1000000000); $this->expectExceptionMessage('Exception message'); $this->expectExceptionMessageRegExp('/^Message as regex$/');
  • 27. TO SUMMARIZE: 1. Testing for edge cases (check for conditional expression evaluating to both true and false) 2. Testing for match of actuall and expected result 3. And thrown exceptions 4. We can think of unit test as a way of contract 5. We don't test obvious things (PHP isn't that untrustworthy)
  • 28. WHAT TO TEST WITH?
  • 29. WHERE DO WE GET DEPENDENCY FROM?? class LoremIpsumTest extends PHPUnit_Framework_TestCase { public function testDoingStuffTheRightWay() { $testedObject = new LoremIpsum(); $testedObject->setDependency(new Dependency()); $this->assertInternalType('array', $testedObject->doStuff()); } }
  • 30. WHAT DOES THE DEPENDENCY DO? class LoremIpsumTest extends PHPUnit_Framework_TestCase { public function testDoingStuffTheRightWay() { $testedObject = new LoremIpsum(); $testedObject->setDependency(new Dependency()); $this->assertInternalType('array', $testedObject->doStuff()); } }
  • 32. UNIT TEST FOR A GIVEN CODE UNIT ARE INDEPENDENT FROM OTHER CODE UNITS
  • 33. UNIT TEST FOR A GIVEN CLASS ARE INDEPENDENT FROM ITS DEPENDENCIES
  • 34. We can test the dependency and make sure it returns some kind of data, but what if we pass a different object of the same type instead?
  • 35. In that case we need to check what happend if the dependency returns: Values of different types Values in different formats Empty value How many additional classes do we need?
  • 36. ZERO
  • 37. TEST DOUBLES Objects imitating objects of a given type Used only to perform tests We declare what we expect of them We declare what they can expect of us And see what happens
  • 38. TERMINOLOGY Dummy - object with methods returning null values Stub - object with methods returning given values Mock - as above and also having some assumptions in regard of executing the method (arguments passed, how many times it's executed) And a lot more
  • 39. YOU DON'T HAVE TO REMEMBER THAT THOUGH Terms from the previous slide are often confused, unclear or just not used. You should use whatever terms are clear for you and your team or just deal with whatever is thrown at you. Often the test double framework will determine it for us.
  • 43. PHPUnit comes with a mocking framework, but lets you use any other you want.
  • 44. LET'S MAKE A DEPENDENCY! class LoremIpsumTest extends PHPUnit_Framework_TestCase { /** * @var PHPUnit_Framework_MockObject_MockObject|Dependency */ private $dependencyMock; public function setUp() { $this->dependencyMock = $this->getMockBuilder(Dependency::class) ->disableOriginalConstructor() ->setMethods(array('getResults')) ->getMock(); } }
  • 45. WILL IT WORK? class LoremIpsumTest extends PHPUnit_Framework_TestCase { public function testDoingStuffTheRighterWay() { $testedObject = new LoremIpsum(); $testedObject->setDependency($this->dependencyMock); $this->assertInternalType('array', $testedObject->doStuff()); $this->assertEmpty($testedObject->doStuff()); } }
  • 47. LETS DESCRIBE OUR REQUIREMENTS class LoremIpsumTest extends PHPUnit_Framework_TestCase { public function testDoingStuffTheRighterWay() { $this->dependencyMock->expects($this->once()) ->method('getResults') ->willReturn(array()); $testedObject = new LoremIpsum(); $testedObject->setDependency($this->dependencyMock); $this->assertInternalType('array', $testedObject->doStuff()); $this->assertEmpty($testedObject->doStuff()); } }
  • 49. WHAT IS PROVIDED IN MOCKBUILDER? Defining mocked methods If we won't use setMethods - all methods will return null If we pass an array to setMethods: Methods which names we passed can be overwritten or will return null Methods which names we didn't pass will behave as specified in the mocked class If we passed null to setMethods - all methods will behave as specified in the mocked class
  • 50. WHAT ELSE IS PROVIDED IN MOCKBUILDER? Disabling the constructor Passing arguments to the constructor (if it's public) Mocking abstract classes (if we overwrite all abstract methods) Mocking traits
  • 51. GREAT EXPECTATIONS expects() method lets us define how many times (if ever) a method should be executed in given conditions. What can we pass to it? $this->any() $this->once() $this->exactly(...) $this->never() $this->atLeast(...) ...
  • 52. WHAT WE CAN OFFER? with() methods allows us to inform the mock what parameters are expected to be passed to method. What can we pass to it? Concrete value (or many if we have many arguments) $this->isInstanceOf(...) $this->callback(...)
  • 53. IF A METHOD DOES NOT MEET THE EXPECTATIONS OR DOESN'T GET REQUIRED PARAMETERS THE TEST WILL FAIL!
  • 54. WHAT DO WE EXPECT IN RETURN? willX() methods allow us to define what should be returned from a method in given circumstances. While previous methods were more like assertions, willX() allows us to define methods behaviour. That allows us to test different cases without creating any additional classes.
  • 55. WHAT CAN WE USE? willReturn() willReturnSelf() willReturnCallback() ...
  • 56. SUMMARY: 1. With mock objects we cas pass dependencies of a given type without creating an actual object (isolation1) 2. We can test different cases without creating any new classes or parametrisation 3. They free us from the necessity of creating dependencies of dependencies 4. Make test independent from external resources as webservices or database 5. Create additional test rules
  • 57. LETS FIX LOREM IPSUM
  • 58. AFTER CHANGES public function divideAndSuffixDependencyResults() { if (!$this->dependency) { throw new Exception("You need to specify the dependency"); } $result = array(); foreach ($this->dependency->getResults() as $value) { $sanitizedValue = (float)$value; if ($sanitizedValue == 0) { continue; } $sanitizedValue = 42 / $sanitizedValue; $sanitizedValue .= ' suffix'; $result[] = $sanitizedValue; } return $result; }
  • 60. ANSWER public function testDivideByZeroIgnored() { $this->dependencyMock->expects($this->once()) ->method('getResults') ->willReturn(array(0)); $testedObject = new LoremIpsum(); $testedObject->setDependency($this->dependencyMock); $result = $testedObject->divideAndSuffixDependencyResults(); $this->assertEmpty($result); $this->assertInternalType('array', $result); }
  • 61. ANOTHER ANSWER public function testDivideByZeroIgnored2() { $this->dependencyMock->expects($this->once()) ->method('getResults') ->willReturn(array(0,2)); $testedObject = new LoremIpsum(); $testedObject->setDependency($this->dependencyMock); $result = $testedObject->divideAndSuffixDependencyResults(); $this->assertEquals($result, array('21 suffix')); }
  • 62. YET ANOTHER ANSWER public function testDivideByZeroIgnored3() { $this->dependencyMock->expects($this->once()) ->method('getResults') ->willReturn(array(0,2)); $testedObject = new LoremIpsum(); $testedObject->setDependency($this->dependencyMock); $result = $testedObject->divideAndSuffixDependencyResults(); $this->assertCount(1, $result); }
  • 63. PROPER ANSWER DOESN'T MATTER IF WE ASK FOR SOMETHING ELSE THAN WE ACTUALLY HAVE TO KNOW
  • 64. HOW SHOULD WE TEST?LIVE
  • 65. ORGANISING TEST Libraries should have test directory on the same level as directory with sources Applications should have test directory on the same level as directory with modules Bootstrap.php (autoloader) and PHPUnit configuration should be inside the test directory Directory hierarchy inside the test directory should be the same as in the sources directory If we use different types of tests - the test directory should be also divided into subdirectories (unit, integration, functional)
  • 67. <!--?xml version="1.0" encoding="UTF-8"?--> <phpunit bootstrap="Bootstrap.php" colors="true"> <testsuites> <testsuite name="Application"> <directory>./ApplicationTests</directory> </testsuite> </testsuites> </phpunit>
  • 69. ONE CLASS - AT LEAST ONE TEST SUITE Two simple rules: 1. If a few tests need a different setup than others - we should move the setup operations into those tests (or extract a method that creates that setup) 2. If many tests need a different setup than others - we should move those test to a different suite and have a separate setup
  • 70. WHY SETUP() WHEN YOU CAN __CONSTRUCT()? setUp() is executed before every test, providing a "fresh" testing environment every time. Its counterpart is takeDown(), executed after every test.
  • 71. TESTS AS DOCUMENTATION Lets call our tests a little different: public function testDivisionAndSuffixReturnsArray WhenDependencyIsProvided(); public function testDivisionAndSuffixThrowsException WhenDependencyWasNotProvided(); public function testDivisionAndSuffixIgnoresResultsEquivalentToZero(); public function testDivisionAndSuffixIgnoresResultsEquivalentToZero2(); public function testDivisionAndSuffixIgnoresResultsEquivalentToZero3();
  • 72. TESTS AS DOCUMENTATION Lets make our configuration a little different: <!--?xml version="1.0" encoding="UTF-8"?--> <phpunit bootstrap="Bootstrap.php" colors="true"> <testsuites> <testsuite name="Application"> <directory>./ApplicationTests</directory> </testsuite> </testsuites> <logging> <log type="testdox-text" target="php://stdout"> </log></logging> </phpunit>
  • 74. WE CAN MAKE IT BETTER
  • 76. APPLICATIONTESTSSERVICELOREMIPSUMDIV ISIONANDSUFFIX Returns array when dependency is provided Throws exception when dependency was not provided Results equivalent to zero are not processed
  • 77. All we have to do is to change testdox-text to testdox-html and provide a file path! Or use --testdox-html parameter in terminal.
  • 79. JUST ADVANTAGES! 1. Two tasks done at once 2. Clear naming convention... 3. ...which also helps to decide what to test 4. Plus a convention of separating test into suites
  • 80. CODE COVERAGE 1. Easy way to see what was already tested and what we still have to test 2. Can help with discovering dead code 3. Is not a measure of test quality
  • 82. CODE COVERAGE 1. Easy way to see what was already tested and what we still have to test 2. Can help with discovering dead code 3. IS NOT A MEASURE OF TEST QUALITY
  • 84. HOW DID DEPENDENCY GOT INTO THIS?
  • 86. ANYTHING ELSE? C.R.A.P. (Change Risk Analysis and Predictions) index - relation of cyclomatic complexity of a method to its code coverage. Low complexity means low risk, even without testing (getters, setters) Medium complexity risks can be countered with high code coverage High complexity means that even testing won't help us
  • 87. HOW TO TEST WHAT WE CAN'T REACH? abstract class AbstractIpsum { protected $dependency; public function __construct($parameter) { if (is_object($parameter)) { $this->dependency = $parameter; } else if (is_string($parameter)) { if (class_exists($parameter)) { $this->dependency = new $parameter; } else { throw new Exception($parameter." does not exist"); } } else { throw new Exception("Invalid argument"); } } }
  • 88. NOT THIS WAY FOR SURE public function testPassingObjectAsParameterAssignsObjectToProperty() { $expectedValue = new DateTime(); $mock = $this->getMockBuilder(AbstractIpsum::class) ->setConstructorArgs(array($expectedValue)) ->getMockForAbstractClass(); $this->assertSame($expectedValue, $mock->dependency); }
  • 89. :(
  • 90. REFLECTION TO THE RESCUE! public function testPassingObjectAsParameterAssignsObjectToProperty() { $expectedValue = new DateTime(); $mock = $this->getMockBuilder(AbstractIpsum::class) ->setConstructorArgs(array($expectedValue)) ->getMockForAbstractClass(); $reflectedClass = new ReflectionClass(AbstractIpsum::class); $property = $reflectedClass->getProperty('dependency'); $property->setAccessible(true); $actualValue = $property->getValue($mock); $this->assertSame($expectedValue, $actualValue); }
  • 91. We'll go through only one test, but it's easy to spot that this class have bigger potential. If we wrote more tests we could use a different approach. Instead of creating mock in every test we could create in during setup, don't call the constructor and use reflection to call it in our tests.
  • 93. NO PROBLEMS WITH REFLECTION
  • 94. IN ACTION public function testPassingObjectAsParameterAssignsObjectToProperty2() { $expectedValue = new DateTime(); $mock = $this->getMockBuilder(AbstractIpsum::class) ->setConstructorArgs(array($expectedValue)) ->getMockForAbstractClass(); $mockClosure = Closure::bind( function (AbstractIpsum $abstractIpsum) { return $abstractIpsum->dependency; }, null, AbstractIpsum::class ); $actualValue = $mockClosure($mock); $this->assertSame($expectedValue, $actualValue); }
  • 95. CAN WE TEST PRIVATE METHODS THIS WAY?
  • 96. NO It is possible in practice, but worthless. We test only the public methods and their calls should cover private methods. Private methods are what's called 'implementation detail' and as such should not be tested.
  • 97. OK, BUT I HAVE LOTS OF LOGIC IN A PRIVATE METHOD AND I WON'T TEST IT THROUGH API This is a hint that there's a bigger problem behind. You should think if you actually shouldn't: 1. Change it to public 2. Extract it to its own class (method object pattern) 3. Extract it, along with similar methods, to their own class (maybe we failed with the whole single responsibility principle thing) 4. Bite the bullet and test through the API
  • 98. SUMMARY 1. Tests organisation - directories and files resemble the project hierarchy 2. We don't need to test a whole class in one test suite 3. We can make documentation writing tests 4. If we need to access private fields and methods we can use reflection and Closure API
  • 103. MUTATION TESTING Testing tests Unit tests are executed on slightly changed classes Changes are made to logic conditions, arithmetic operations, literals, returned values, and so on Shows us if code regressions were found by our tests https://github.com/padraic/humbug
  • 104. RECOMMENDED READING xUnit Patterns PHPUnit manual Michelangelo van Dam - Your code are my tests! Marco Pivetta - Accessing private PHP class members without reflection