your code are my tests - lonestarphp 2014
DESCRIPTION
After years of promoting PHPUnit I still hear it's hard to get started with unit testing. So instead of showing nice step-by-step examples on how to use PHPUnit, we're going to take examples straight from github and start writing tests for them where I explain the issue, how we test it and how we finally got things tested. So I take on the challenge to start writing tests for PHP projects that already have unit tests in place (like major frameworks) and projects that not even started with unit testing.TRANSCRIPT
2
Your code are my testsLoneStarPHP 2014
Dallas, TX
2
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, NOT EVEN IF YOUR NAME IS KEITH CASEY.
3
Michelangelo van Dam!!
PHP Consultant Community Leader
President of PHPBenelux Contributor
Warming up
4
http
s://w
ww.
flick
r.com
/pho
tos/
bobj
agen
dorf/
8535
3168
36
Smallest unit of code
5
http
s://w
ww.
flick
r.com
/pho
tos/
tool
stop
/454
6017
269
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; ! } !}
6
Tes9ng 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'); ! }
7
Tes9ng 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'); ! }
8
We don’t live in a fairy tale!
9
http
s://w
ww.
flick
r.com
/pho
tos/
bertk
not/8
1752
1490
9
Real code, real apps
10
github.com/Telaxus/EPESI
11
Running the project
12
Where are the TESTS?
13
Where are the TESTS?
14
Oh noes, no tests!
15
http
s://w
ww.
flick
r.com
/pho
tos/
mjh
agen
/297
3212
926
Let’s get started
16
http
s://w
ww.
flick
r.com
/pho
tos/
npob
re/2
6015
8225
6
How to get about it?
17
SeKng up for tes9ng<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>
18
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 • saBsfy_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
19
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; !}
20
Tes9ng first condi9on<?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'); ! } !}
21
Run test
22
Check coverage
23
Test for second condi9onpublic 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'); !}
24
Run tests
25
Check coverage
26
Test for third condi9onpublic 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'); !}
27
Run tests
28
Check code coverage
29
Non-‐executable code
30
http
s://w
ww.
flick
r.com
/pho
tos/
dazj
ohns
on/7
7208
0682
4
Test for successpublic 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'); !}
31
Run tests
32
Check code coverage
33
Look at the global coverage
34
Bridging gaps
35
http
s://w
ww.
flick
r.com
/pho
tos/
hugo
90/6
9807
1264
3
Privates exposed
36 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
/
Dependency• __construct • get_module_name • get_version_min • get_version_max • is_saBsfied_by • requires • requires_exact • requires_at_least • requires_range
37
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 ? '<=' : '<'; ! } !! /** ... */ !}
38
Don’t touch my junk!
39
http
s://w
ww.
flick
r.com
/pho
tos/
case
ymul
timed
ia/5
4122
9373
0
House of Reflec9on
40
http
s://w
ww.
flick
r.com
/pho
tos/
tabo
r-roe
der/8
2507
7011
5
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!! } !}
41
Let’s use the sta9c$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');
42
Use the reflec9on 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');
43
Run tests
44
Code Coverage
45
Yes, paradise exists
46
http
s://w
ww.
flick
r.com
/pho
tos/
rnug
raha
/200
3147
365
Unit tes9ng is not difficult!
47
You just need to get started
48
PHP has all the tools
49
And there are more roads to Rome
50
Ques9ons?
52
http
s://w
ww.
flick
r.com
/pho
tos/
mdp
ettit
t/867
1901
426
53
joind.in/10806