your code are my tests
TRANSCRIPT
![Page 1: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/1.jpg)
Your code are my tests
How to test legacy code
in it2PROFESSIONAL PHP SERVICES
![Page 2: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/2.jpg)
ADVISORYIN ORDER TO EXPLAIN CERTAIN SITUATIONS YOU MIGHT FACE IN YOUR DEVELOPMENT CAREER, WE WILL BE DISCUSSING THE USAGE OF PRIVATES AND PUBLIC EXPOSURE. IF THESE TOPICS OFFEND OR UPSET YOU, WE WOULD LIKE TO ASK YOU TO LEAVE THIS ROOM NOW.
THE SPEAKER NOR THE ORGANISATION CANNOT BE HELD ACCOUNTABLE FOR MENTAL DISTRESS OR ANY FORMS OF DAMAGE YOU MIGHT ENDURE DURING OR AFTER THIS PRESENTATION. FOR COMPLAINTS PLEASE INFORM ORGANISATION AT [email protected].
![Page 3: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/3.jpg)
Michelangelo van Dam
PHP Consultant Community Leader
President of PHPBenelux Contributor to PHP projects
T @DragonBe | F DragonBe
http
s://w
ww.
flick
r.com
/pho
tos/
akra
bat/8
7843
1881
3
![Page 4: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/4.jpg)
Using Social Media?Tag it #mytests
http
://w
ww.
flick
r.com
/pho
tos/
andy
ofne
/463
3356
197
http
://w
ww.
flick
r.com
/pho
tos/
andy
ofne
/463
3356
197
![Page 5: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/5.jpg)
Why bother with testing?
http
s://w
ww.
flick
r.com
/pho
tos/
vial
bost
/553
3266
530
![Page 6: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/6.jpg)
Most common excuses why developers don’t test
• no time
• no budget
• deliver tests after finish project (never)
• devs don’t know how
http
s://w
ww.
flick
r.com
/pho
tos/
dasp
rid/8
1479
8630
7
![Page 7: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/7.jpg)
No excuses!!!
Crea%ve Co
mmon
s -‐ h.p://www.flickr.com
/pho
tos/akrabat/8421560178
![Page 8: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/8.jpg)
Responsibility issue
• As a developer, it’s your job to
• write code & fixing bugs
• add documentation
• write & update unit tests
![Page 9: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/9.jpg)
Pizza principleTopping: your tests
Box: your documenta%on
Dough: your code
![Page 10: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/10.jpg)
Benefits of testing• Direct feedback (test fails)
• Once a test is made, it will always be tested
• Easy to refactor existing code (protection)
• Easy to debug: write a test to see if a bug is genuine
• Higher confidence and less uncertainty
![Page 11: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/11.jpg)
Rule of thumb
“Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead.”
— Source: Martin Fowler
![Page 12: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/12.jpg)
Warming up
http
s://w
ww.
flick
r.com
/pho
tos/
bobj
agen
dorf/
8535
3168
36
![Page 13: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/13.jpg)
PHPUnit• PHPUnit is a port of xUnit testing framework
• Created by “Sebastian Bergmann”
• Uses “assertions” to verify behaviour of “unit of code”
• Open source and hosted on GitHub
• See https://github.com/sebastianbergmann/phpunit
• Can be installed using:
• PEAR
• PHAR
• Composer
![Page 14: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/14.jpg)
Approach for testing
• Instantiate a “unit-of-code”
• Assert expected result against actual result
• Provide a custom error message
![Page 15: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/15.jpg)
Available assertions• assertArrayHasKey() • assertClassHasAttribute() • assertClassHasStaticAttribute() • assertContains() • assertContainsOnly() • assertContainsOnlyInstancesOf() • assertCount()• assertEmpty()• assertEqualXMLStructure() • assertEquals()• assertFalse()• assertFileEquals() • assertFileExists() • assertGreaterThan() • assertGreaterThanOrEqual() • assertInstanceOf() • assertInternalType() • assertJsonFileEqualsJsonFile() • assertJsonStringEqualsJsonFile() • assertJsonStringEqualsJsonString()
• assertLessThan() • assertLessThanOrEqual() • assertNull()• assertObjectHasAttribute() • assertRegExp() • assertStringMatchesFormat() • assertStringMatchesFormatFile() • assertSame()• assertSelectCount() • assertSelectEquals() • assertSelectRegExp() • assertStringEndsWith() • assertStringEqualsFile() • assertStringStartsWith() • assertTag() • assertThat() • assertTrue()• assertXmlFileEqualsXmlFile() • assertXmlStringEqualsXmlFile() • assertXmlStringEqualsXmlString()
![Page 16: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/16.jpg)
To protect and to serve
![Page 17: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/17.jpg)
Data is tainted, ALWAYS
HackersBAD DATA
Web ServicesStupid users
![Page 20: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/20.jpg)
OWASP top 10 exploits
https://www.owasp.org/index.php/Top_10_2013-Top_10
![Page 22: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/22.jpg)
Smallest unit of code
http
s://w
ww.
flick
r.com
/pho
tos/
tool
stop
/454
6017
269
![Page 23: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/23.jpg)
Example class<?php /** * Example class */ class MyClass { /** ... */ public function doSomething($requiredParam, $optionalParam = null) { if (!filter_var( $requiredParam, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH )) { throw new InvalidArgumentException('Invalid argument provided'); } if (null !== $optionalParam) { if (!filter_var( $optionalParam, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH )) { throw new InvalidArgumentException('Invalid argument provided'); } $requiredParam .= ' - ' . $optionalParam; } return $requiredParam; } }
![Page 24: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/24.jpg)
Testing for good /** ... */ public function testClassAcceptsValidRequiredArgument() { $expected = $argument = 'Testing PHP Class'; $myClass = new MyClass; $result = $myClass->doSomething($argument); $this->assertSame($expected, $result, 'Expected result differs from actual result'); }
/** ... */ public function testClassAcceptsValidOptionalArgument() { $requiredArgument = 'Testing PHP Class'; $optionalArgument = 'Is this not fun?!?'; $expected = $requiredArgument . ' - ' . $optionalArgument; $myClass = new MyClass; $result = $myClass->doSomething($requiredArgument, $optionalArgument); $this->assertSame($expected, $result, 'Expected result differs from actual result'); }
![Page 25: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/25.jpg)
Testing for bad /** * @expectedException InvalidArgumentException */ public function testExceptionIsThrownForInvalidRequiredArgument() { $expected = $argument = new StdClass; $myClass = new MyClass; $result = $myClass->doSomething($argument); $this->assertSame($expected, $result, 'Expected result differs from actual result'); } /** * @expectedException InvalidArgumentException */ public function testExceptionIsThrownForInvalidOptionalArgument() { $requiredArgument = 'Testing PHP Class'; $optionalArgument = new StdClass; $myClass = new MyClass; $result = $myClass->doSomething($requiredArgument, $optionalArgument); $this->assertSame($expected, $result, 'Expected result differs from actual result'); }
![Page 26: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/26.jpg)
Example: testing payments<?php namespace Myapp\Common\Payment; class ProcessTest extends \PHPUnit_Framework_TestCase { public function testPaymentIsProcessedCorrectly() { $customer = new Customer(/* data for customer */); $transaction = new Transaction(/* data for transaction */); $process = new Process('sale', $customer, $transaction); $process-‐>pay(); $this-‐>assertTrue($process-‐>paymentApproved()); $this-‐>assertEquals('PAY-‐17S8410768582940NKEE66EQ', $process-‐>getPaymentId()); } }
![Page 27: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/27.jpg)
We don’t live in a fairy tale!
http
s://w
ww.
flick
r.com
/pho
tos/
bertk
not/8
1752
1490
9
![Page 28: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/28.jpg)
Real code, real apps
![Page 29: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/29.jpg)
github.com/Telaxus/EPESI
![Page 30: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/30.jpg)
Running the project
![Page 31: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/31.jpg)
Where are the TESTS?
![Page 32: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/32.jpg)
Where are the TESTS?
![Page 33: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/33.jpg)
Oh noes, no tests!
http
s://w
ww.
flick
r.com
/pho
tos/
mjh
agen
/297
3212
926
![Page 34: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/34.jpg)
Let’s get started
http
s://w
ww.
flick
r.com
/pho
tos/
npob
re/2
6015
8225
6
![Page 35: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/35.jpg)
How to get about it?
![Page 36: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/36.jpg)
Setting up for testing<phpunit colors="true" stopOnError="true" stopOnFailure="true"> <testsuites> <testsuite name="EPESI admin tests"> <directory phpVersion="5.3.0">tests/admin</directory> </testsuite> <testsuite name="EPESI include tests"> <directory phpVersion="5.3.0">tests/include</directory> </testsuite> <testsuite name="EPESI modules testsuite"> <directory phpVersion="5.3.0">tests/modules</directory> </testsuite> </testsuites> <php> <const name="DEBUG_AUTOLOADS" value="1"/> <const name="CID" value="1234567890123456789"/> </php> <logging> <log type="coverage-html" target="build/coverage" charset="UTF-8"/> <log type="coverage-clover" target="build/logs/clover.xml"/> <log type="junit" target="build/logs/junit.xml"/> </logging></phpunit>
![Page 37: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/37.jpg)
ModuleManager• not_loaded_modules • loaded_modules • modules • modules_install • modules_common • root • processing • processed_modules • include_install • include_common • include_main • create_load_priority_array • check_dependencies • satisfy_dependencies • get_module_dir_path • get_module_file_name • list_modules • exists • register • unregister • is_installed
• upgrade • downgrade • get_module_class_name • install • uninstall • get_processed_modules • get_load_priority_array • new_instance • get_instance • create_data_dir • remove_data_dir • get_data_dir • load_modules • create_common_cache • create_root • check_access • call_common_methods • check_common_methods • required_modules • reset_cron
![Page 38: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/38.jpg)
ModuleManager::module_install
/** * Includes file with module installation class. * * Do not use directly. * * @param string $module_class_name module class name - underscore separated */ public static final function include_install($module_class_name) { if(isset(self::$modules_install[$module_class_name])) return true; $path = self::get_module_dir_path($module_class_name); $file = self::get_module_file_name($module_class_name); $full_path = 'modules/' . $path . '/' . $file . 'Install.php'; if (!file_exists($full_path)) return false; ob_start(); $ret = require_once($full_path); ob_end_clean(); $x = $module_class_name.'Install'; if(!(class_exists($x, false)) || !array_key_exists('ModuleInstall',class_parents($x))) trigger_error('Module '.$path.': Invalid install file',E_USER_ERROR); self::$modules_install[$module_class_name] = new $x($module_class_name); return true; }
![Page 39: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/39.jpg)
Testing first condition<?php
require_once 'include.php';
class ModuleManagerTest extends PHPUnit_Framework_TestCase { protected function tearDown() { ModuleManager::$modules_install = array (); }
public function testReturnImmediatelyWhenModuleAlreadyLoaded() { $module = 'Foo_Bar'; ModuleManager::$modules_install[$module] = 1; $result = ModuleManager::include_install($module); $this->assertTrue($result, 'Expecting that an already installed module returns true'); $this->assertCount(1, ModuleManager::$modules_install, 'Expecting to find 1 module ready for installation'); } }
![Page 40: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/40.jpg)
Run test
![Page 41: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/41.jpg)
Check coverage
![Page 42: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/42.jpg)
Test for second condition
public function testLoadingNonExistingModuleIsNotExecuted() { $module = 'Foo_Bar'; $result = ModuleManager::include_install($module); $this->assertFalse($result, 'Expecting failure for loading Foo_Bar'); $this->assertEmpty(ModuleManager::$modules_install, 'Expecting to find no modules ready for installation'); }
![Page 43: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/43.jpg)
Run tests
![Page 44: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/44.jpg)
Check coverage
![Page 45: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/45.jpg)
Test for third condition
public function testNoInstallationOfModuleWithoutInstallationClass() { $module = 'EssClient_IClient'; $result = ModuleManager::include_install($module); $this->assertFalse($result, 'Expecting failure for loading Foo_Bar'); $this->assertEmpty(ModuleManager::$modules_install, 'Expecting to find no modules ready for installation'); }
![Page 46: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/46.jpg)
Run tests
![Page 47: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/47.jpg)
Check code coverage
![Page 48: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/48.jpg)
Non-executable code
http
s://w
ww.
flick
r.com
/pho
tos/
dazj
ohns
on/7
7208
0682
4
![Page 49: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/49.jpg)
Test for success
public function testIncludeClassFileForLoadingModule() { $module = 'Base_About'; $result = ModuleManager::include_install($module); $this->assertTrue($result, 'Expected module to be loaded'); $this->assertCount(1, ModuleManager::$modules_install, 'Expecting to find 1 module ready for installation'); }
![Page 50: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/50.jpg)
Run tests
![Page 51: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/51.jpg)
Check code coverage
![Page 52: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/52.jpg)
Look at the global coverage
![Page 53: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/53.jpg)
Bridging gaps
http
s://w
ww.
flick
r.com
/pho
tos/
hugo
90/6
9807
1264
3
![Page 54: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/54.jpg)
Privates exposed
http
://w
ww.
slas
hgea
r.com
/form
er-ts
a-ag
ent-a
dmits
-we-
knew
-full-
body
-sca
nner
s-di
dnt-w
ork-
3131
5288
/
![Page 55: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/55.jpg)
Dependency• __construct
• get_module_name
• get_version_min
• get_version_max
• is_satisfied_by
• requires
• requires_exact
• requires_at_least
• requires_range
![Page 56: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/56.jpg)
A private constructor!<?php
defined("_VALID_ACCESS") || die('Direct access forbidden');
/** * This class provides dependency requirements * @package epesi-base * @subpackage module */ class Dependency {
private $module_name; private $version_min; private $version_max; private $compare_max;
private function __construct( $module_name, $version_min, $version_max, $version_max_is_ok = true) { $this->module_name = $module_name; $this->version_min = $version_min; $this->version_max = $version_max; $this->compare_max = $version_max_is_ok ? '<=' : '<'; }
/** ... */ }
![Page 57: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/57.jpg)
Don’t touch my junk!
http
s://w
ww.
flick
r.com
/pho
tos/
case
ymul
timed
ia/5
4122
9373
0
![Page 58: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/58.jpg)
House of Reflection
http
s://w
ww.
flick
r.com
/pho
tos/
tabo
r-roe
der/8
2507
7011
5
![Page 59: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/59.jpg)
Let’s do this…
<?php require_once 'include.php';
class DependencyTest extends PHPUnit_Framework_TestCase { public function testConstructorSetsProperSettings() { require_once 'include/module_dependency.php';
// We have a problem, the constructor is private! } }
![Page 60: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/60.jpg)
Let’s use the static$params = array ( 'moduleName' => 'Foo_Bar', 'minVersion' => 0, 'maxVersion' => 1, 'maxOk' => true, ); // We use a static method for this test $dependency = Dependency::requires_range( $params['moduleName'], $params['minVersion'], $params['maxVersion'], $params['maxOk'] );
// We use reflection to see if properties are set correctly $reflectionClass = new ReflectionClass('Dependency');
![Page 61: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/61.jpg)
Use the reflection to assert// Let's retrieve the private properties $moduleName = $reflectionClass->getProperty('module_name'); $moduleName->setAccessible(true); $minVersion = $reflectionClass->getProperty('version_min'); $minVersion->setAccessible(true); $maxVersion = $reflectionClass->getProperty('version_max'); $maxVersion->setAccessible(true); $maxOk = $reflectionClass->getProperty('compare_max'); $maxOk->setAccessible(true);
// Let's assert $this->assertEquals($params['moduleName'], $moduleName->getValue($dependency), 'Expected value does not match the value set’); $this->assertEquals($params['minVersion'], $minVersion->getValue($dependency), 'Expected value does not match the value set’); $this->assertEquals($params['maxVersion'], $maxVersion->getValue($dependency), 'Expected value does not match the value set’); $this->assertEquals('<=', $maxOk->getValue($dependency), 'Expected value does not match the value set');
![Page 62: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/62.jpg)
Run tests
![Page 63: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/63.jpg)
Code Coverage
![Page 64: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/64.jpg)
Yes, paradise exists
http
s://w
ww.
flick
r.com
/pho
tos/
rnug
raha
/200
3147
365
![Page 65: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/65.jpg)
Unit testing is not difficult!
![Page 66: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/66.jpg)
Get started
![Page 67: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/67.jpg)
PHP has all the tools
![Page 68: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/68.jpg)
And there are more roads to Rome
![Page 70: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/70.jpg)
Recommended reading
![Page 71: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/71.jpg)
http
s://w
ww.
flick
r.com
/pho
tos/
lwr/1
3442
5422
35
![Page 72: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/72.jpg)
Contact us
in it2PROFESSIONAL PHP SERVICES
Michelangelo van Dam [email protected]
www.in2it.be
PHP Consulting - Training - QA
![Page 74: Your code are my tests](https://reader030.vdocument.in/reader030/viewer/2022020208/55a5148f1a28ab866b8b4611/html5/thumbnails/74.jpg)
Thank youHave a great conference
http
://w
ww.
flick
r.com
/pho
tos/
drew
m/3
1918
7251
5