SlideShare a Scribd company logo
1 of 106
Download to read offline
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;
}
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();
}
}
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 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)
<!--?xml version="1.0" encoding="UTF-8"?-->
<phpunit bootstrap="Bootstrap.php" colors="true">
<testsuites>
<testsuite name="Application">
<directory>./ApplicationTests</directory>
</testsuite>
</testsuites>
</phpunit>
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

What's hot

Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnitMindfire Solutions
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitMichelangelo van Dam
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceSebastian Marek
 
Unit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingUnit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingRam Awadh Prasad, PMP
 
Beginning PHPUnit
Beginning PHPUnitBeginning PHPUnit
Beginning PHPUnitJace Ju
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEnterprise PHP Center
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2Yi-Huan Chan
 
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)ENDelt260
 
JUnit Kung Fu: Getting More Out of Your Unit Tests
JUnit Kung Fu: Getting More Out of Your Unit TestsJUnit Kung Fu: Getting More Out of Your Unit Tests
JUnit Kung Fu: Getting More Out of Your Unit TestsJohn Ferguson Smart Limited
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Michelangelo van Dam
 
Test in action week 4
Test in action   week 4Test in action   week 4
Test in action week 4Yi-Huan Chan
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013Michelangelo van Dam
 
An introduction to Google test framework
An introduction to Google test frameworkAn introduction to Google test framework
An introduction to Google test frameworkAbner Chih Yi Huang
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With PythonSiddhi
 
C++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkC++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkHumberto Marchezi
 

What's hot (20)

Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnit
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
 
Phpunit testing
Phpunit testingPhpunit testing
Phpunit testing
 
PHPUnit
PHPUnitPHPUnit
PHPUnit
 
Unit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingUnit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step Training
 
Beginning PHPUnit
Beginning PHPUnitBeginning PHPUnit
Beginning PHPUnit
 
Unit testing
Unit testingUnit testing
Unit testing
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
PHPUnit testing to Zend_Test
PHPUnit testing to Zend_TestPHPUnit testing to Zend_Test
PHPUnit testing to Zend_Test
 
Test in action week 2
Test in action   week 2Test in action   week 2
Test in action week 2
 
Laravel Unit Testing
Laravel Unit TestingLaravel Unit Testing
Laravel Unit Testing
 
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)Unlock The Mystery Of PHPUnit (Wave PHP 2018)
Unlock The Mystery Of PHPUnit (Wave PHP 2018)
 
JUnit Kung Fu: Getting More Out of Your Unit Tests
JUnit Kung Fu: Getting More Out of Your Unit TestsJUnit Kung Fu: Getting More Out of Your Unit Tests
JUnit Kung Fu: Getting More Out of Your Unit Tests
 
Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013Workshop quality assurance for php projects - ZendCon 2013
Workshop quality assurance for php projects - ZendCon 2013
 
Test in action week 4
Test in action   week 4Test in action   week 4
Test in action week 4
 
UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013UA testing with Selenium and PHPUnit - PFCongres 2013
UA testing with Selenium and PHPUnit - PFCongres 2013
 
An introduction to Google test framework
An introduction to Google test frameworkAn introduction to Google test framework
An introduction to Google test framework
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With Python
 
C++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing FrameworkC++ Unit Test with Google Testing Framework
C++ Unit Test with Google Testing Framework
 

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

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

Objects, Testing, and Responsibility
Objects, Testing, and ResponsibilityObjects, Testing, and Responsibility
Objects, Testing, and Responsibility
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
SystemVerilog OOP Ovm Features Summary
SystemVerilog OOP Ovm Features SummarySystemVerilog OOP Ovm Features Summary
SystemVerilog OOP Ovm Features Summary
 
Beyond Design Principles and Patterns
Beyond Design Principles and PatternsBeyond Design Principles and Patterns
Beyond Design Principles and Patterns
 
TDD step patterns
TDD step patternsTDD step patterns
TDD step patterns
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Mocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnitMocking Dependencies in PHPUnit
Mocking Dependencies in PHPUnit
 
Unit testing with PHPUnit
Unit testing with PHPUnitUnit testing with PHPUnit
Unit testing with PHPUnit
 
Effective Unit Testing
Effective Unit TestingEffective Unit Testing
Effective Unit Testing
 
Java Class Design
Java Class DesignJava Class Design
Java Class Design
 
Unit testing
Unit testingUnit testing
Unit testing
 
Testing And Drupal
Testing And DrupalTesting And Drupal
Testing And Drupal
 
Fighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnitFighting Fear-Driven-Development With PHPUnit
Fighting Fear-Driven-Development With PHPUnit
 
Professional-grade software design
Professional-grade software designProfessional-grade software design
Professional-grade software design
 
Testing untestable code - IPC12
Testing untestable code - IPC12Testing untestable code - IPC12
Testing untestable code - IPC12
 
Java tutorial PPT
Java tutorial PPTJava tutorial PPT
Java tutorial PPT
 
Java tutorial PPT
Java tutorial  PPTJava tutorial  PPT
Java tutorial PPT
 
Test-driven Development for TYPO3
Test-driven Development for TYPO3Test-driven Development for TYPO3
Test-driven Development for TYPO3
 
Java tutorials
Java tutorialsJava tutorials
Java tutorials
 

Recently uploaded

%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrandmasabamasaba
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdfPearlKirahMaeRagusta1
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
The Top App Development Trends Shaping the Industry in 2024-25 .pdf
The Top App Development Trends Shaping the Industry in 2024-25 .pdfThe Top App Development Trends Shaping the Industry in 2024-25 .pdf
The Top App Development Trends Shaping the Industry in 2024-25 .pdfayushiqss
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfkalichargn70th171
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfVishalKumarJha10
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisamasabamasaba
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfonteinmasabamasaba
 
SHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions PresentationSHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions PresentationShrmpro
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplatePresentation.STUDIO
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburgmasabamasaba
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...SelfMade bd
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...masabamasaba
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech studentsHimanshiGarg82
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...masabamasaba
 

Recently uploaded (20)

%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
The Top App Development Trends Shaping the Industry in 2024-25 .pdf
The Top App Development Trends Shaping the Industry in 2024-25 .pdfThe Top App Development Trends Shaping the Industry in 2024-25 .pdf
The Top App Development Trends Shaping the Industry in 2024-25 .pdf
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
SHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions PresentationSHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions Presentation
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
%in Lydenburg+277-882-255-28 abortion pills for sale in Lydenburg
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
 

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; }
  • 15.
  • 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(); } }
  • 20.
  • 21.
  • 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()); } }
  • 31.
  • 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)
  • 66.
  • 67. <!--?xml version="1.0" encoding="UTF-8"?--> <phpunit bootstrap="Bootstrap.php" colors="true"> <testsuites> <testsuite name="Application"> <directory>./ApplicationTests</directory> </testsuite> </testsuites> </phpunit>
  • 68.
  • 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