testing untestable code - phpbnl11

67
Testing untestable code Stephan Hochdörfer, bitExpert AG "Quality is a function of thought and reflection - precise thought and reflection. That’s the magic." Michael Feathers

Upload: stephan-hochdoerfer

Post on 28-Nov-2014

1.931 views

Category:

Documents


2 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Testing untestable code - PHPBNL11

Testing untestable codeStephan Hochdörfer, bitExpert AG

"Quality is a function of thought and reflection -

precise thought and reflection. That’s the magic."

Michael Feathers

Page 2: Testing untestable code - PHPBNL11

About me

Stephan Hochdörfer, bitExpert AG

Department Manager Research Labs

enjoying PHP since 1999

[email protected]

@shochdoerfer

Page 3: Testing untestable code - PHPBNL11

Warning! Use at your own risk...

Page 4: Testing untestable code - PHPBNL11

No excuse for writing bad code!

Page 5: Testing untestable code - PHPBNL11

Seriously, I am not kidding!

Page 6: Testing untestable code - PHPBNL11

Theory

"There is no secret to writing tests, there

are only secrets to write testable code!"Miško Hevery

Page 7: Testing untestable code - PHPBNL11

Theory

What is „untestable code“?

Page 8: Testing untestable code - PHPBNL11

s

Theory

What is „untestable code“?

„new“ is evil!

Page 9: Testing untestable code - PHPBNL11

Theory

What is „untestable code“?

Page 10: Testing untestable code - PHPBNL11

Theory

What is „untestable code“?

Page 11: Testing untestable code - PHPBNL11

Theory

"...our test strategy requires us to have more control or

visibility of the internal behavior of the system under test."Gerard Meszaros, xUnit Test Patterns: Refactoring Test Code

Page 12: Testing untestable code - PHPBNL11

Theory

Class toTest

Class toTestUnittest

Unittest

Requiredclass

Requiredclass

Requiredclass

Requiredclass

Page 13: Testing untestable code - PHPBNL11

Theory

Class totest

Class totestUnittest

Unittest

Requiredclass

Requiredclass

Requiredclass

Requiredclass

DatabaseDatabase

Externalresource

Externalresource

Requiredclass

RequiredclassRequired

class

Requiredclass Webservice

Webservice

Page 14: Testing untestable code - PHPBNL11

Theory

Class totest

Class totestUnittest

Unittest

Requiredclass

Requiredclass

Requiredclass

Requiredclass

DatabaseDatabase

Externalresource

Externalresource

Requiredclass

RequiredclassRequired

class

Requiredclass Webservice

Webservice

Page 15: Testing untestable code - PHPBNL11

Theory

How to achieve „testable“ code?

Page 16: Testing untestable code - PHPBNL11

Theory

How to achieve „testable“ code?

Refactoring

Page 17: Testing untestable code - PHPBNL11

Theory

"Before you start refactoring, check that you

have a solid suite of tests."Martin Fowler, Refactoring

Page 18: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

Let the work begin...

Page 19: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

Safty instructions

Do not change existing code!

Page 20: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | __autoload

<?phpclass Car {

private $Engine;

public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);

}

}

Page 21: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | __autoload

How to inject a dependency? Use __autoload

<?phpclass Car {

private $Engine;

public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);

}

}

Page 22: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | __autoload

<?phpfunction run_autoload($psClass) {

$sFileToInclude = strtolower($psClass).'.php';if(strtolower($psClass) == 'engine') {

$sFileToInclude = '/custom/mocks/'.$sFileToInclude;}include($sFileToInclude);

}

// Testcasespl_autoload_register('run_autoload');$oCar = new Car('Diesel');echo $oCar->run();

Page 23: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path

<?phpinclude('Engine.php');

class Car {private $Engine;

public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);

}}

Page 24: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path

<?phpinclude('Engine.php');

class Car {private $Engine;

public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);

}}

How to inject a dependency? Manipulate include_path setting

Page 25: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path

<?phpini_set('include_path',

'/custom/mocks/'.PATH_SEPARATOR.ini_get('include_path'));

// Testcaseinclude('car.php');

$oCar = new Car('Diesel');echo $oCar->run();

Page 26: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path alternative

<?phpinclude('Engine.php');

class Car {private $Engine;

public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);

}}

Page 27: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path alternative

<?phpinclude('Engine.php');

class Car {private $Engine;

public function __construct($sEngine) {$this->Engine = Engine::getByType($sEngine);

}}

How to inject a dependency? Custom Stream Wrapper behaviour

Idea by Alex Netkachov, http://www.alexatnet.com/node/203

Page 28: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path alternative

<?phpclass CustomFileStreamWrapper { private $_handler;

function stream_open($path, $mode, $options, &$opened_path) { stream_wrapper_restore('file');

// @TODO: modify $path before fopen $this->_handler = fopen($path, $mode); stream_wrapper_unregister('file'); stream_wrapper_register('file', 'CustomFileStreamWrapper'); return true; }

function stream_read($count) {}

function stream_write($data) {}

function stream_tell() {}

function stream_eof() {}

function stream_seek($offset, $whence) {}}

stream_wrapper_unregister('file');stream_wrapper_register('file', 'CustomFileStreamWrapper');

Page 29: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | include_path alternative

<?phpclass CustomFileStreamWrapper {

private $_handler;

function stream_open($path, $mode, $options, &$opened_path) {stream_wrapper_restore('file');$this->_handler = fopen($path, $mode);stream_wrapper_unregister('file');stream_wrapper_register('file', 'CustomFileStreamWrapper');return true;

}

function stream_read($count) {$content = fread($this->_handler, $count);$content = str_replace('Engine::getByType', 'AbstractEngine::get',

$content);return $content;

}}

stream_wrapper_unregister('file');stream_wrapper_register('file', 'CustomFileStreamWrapper');

include('engine.php');?>

Page 30: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

How to test private methods?

Page 31: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

How to test private methods?

There`s no need to!

Page 32: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

How to test private methods?

There`s no need to, but...

Page 33: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | private vs. protected

<?phpclass CustomFileStreamWrapper {

private $_handler;

function stream_open($path, $mode, $options, &$opened_path) {stream_wrapper_restore('file');$this->_handler = fopen($path, $mode);stream_wrapper_unregister('file');stream_wrapper_register('file', 'CustomFileStreamWrapper');return true;

}

function stream_read($count) {$content = fread($this->_handler, $count);$content = str_replace('private function', 'public function',

$content);return $content;

}}

stream_wrapper_unregister('file');stream_wrapper_register('file', 'CustomFileStreamWrapper');

include('engine.php');?>

Page 34: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Namespaces

<?phpclass Car {

private $Engine;

public function __construct($sEngine) {$this->Engine = \Car\Engine::getByType($sEngine);

}}

Page 35: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Namespaces

<?phpclass Car {

private $Engine;

public function __construct($sEngine) {$this->Engine = \Car\Engine::getByType($sEngine);

}}

How to inject a dependency? Use __autoload or manipulate the include_path

Page 36: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | vfsStream

<?phpclass Car {

private $Engine;

public function __construct($sEngine, $CacheDir) {$this->Engine = \Car\Engine::getByType($sEngine);

mkdir($CacheDir.'/cache/', 0700, true);}

}

Page 37: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | vfsStream

How mock a filesystem? Use vfsStream - http://code.google.com/p/bovigo/

<?phpclass Car {

private $Engine;

public function __construct($sEngine, $CacheDir) {$this->Engine = \Car\Engine::getByType($sEngine);

mkdir($CacheDir.'/cache/', 0700, true);}

}

Page 38: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | vfsStream

<?php

// setup vfsStreamvfsStreamWrapper::register();vfsStreamWrapper::setRoot(new vfsStreamDirectory('app'));

$oCar = new Car('Diesel', vfsStream::url('app'));

echo vfsStreamWrapper::getRoot()->hasChild('cache');

Page 39: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Database

Database Testing

Use the methods provided by your favourite framework

e.g Zend Framework

Implement Zend_Db_Statement_Interface

subclass Zend_Db_Adapter_Abstract

$db = new Custom_Db_Adapter(array());Zend_Db_Table::setDefaultAdapter($db);

Page 40: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Database

Database Testing

For low-level database access:

e.g MySQL

do not load the mysql extension

Add custom userland implementations of mysql_* functions

Page 41: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Database

Database Testing

Use the methods provided by your favourite unittest framework

e.g PHPUnit

extend PHPUnit_Extensions_Database_TestCase

implement getConnection() and getDataset()

Page 42: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Database

Database Testing

Use the methods provided by your favourite sql server (and tools)

e.g MySQL

use MySQL Proxy to transparently switch databases

Begin and rollback transactions

Page 43: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

„I have no idea how to unit-test procedural code. Unit-testing assumes that I can instantiate a piece of my application in

isolation.“ Miško Hevery

Page 44: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Test functions

<?phpfunction startsWith($sString, $psPre) {

return $psPre == substr($sString, 0, strlen($psPre));}

function contains($sString, $sSearch) {return false !== strpos($sString, $sSearch);

}

Page 45: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Test functions

How to test PHPUnit can call functions

<?phpfunction startsWith($sString, $psPre) {

return $psPre == substr($sString, 0, strlen($psPre));}

function contains($sString, $sSearch) {return false !== strpos($sString, $sSearch);

}

Page 46: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | Test functions

How to test PHPUnit can call functions

PHPUnit can save/restore globale state

<?phpfunction startsWith($sString, $psPre) {

return $psPre == substr($sString, 0, strlen($psPre));}

function contains($sString, $sSearch) {return false !== strpos($sString, $sSearch);

}

Page 47: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

mysql_query("INSERT INTO...", $oDB);

mail('[email protected]', 'New sale', '....');}

Page 48: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

mysql_query("INSERT INTO...", $oDB);

mail('[email protected]', 'New sale', '....');}

Page 49: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

mysql_query("INSERT INTO...", $oDB);

mail('[email protected]', 'New sale', '....');}

How to test Unfortunatley mail() is part of the PHP core and cannot be unloaded

Page 50: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

mysql_query("INSERT INTO...", $oDB);

mail('[email protected]', 'New sale', '....');}

How to test Use classkit extension to overwrite internal functions

Page 51: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code | overwrite internal functions

<?php

ini_set('runkit.internal_override', '1');

runkit_function_redefine('mail','','return true;');

?>

Page 52: Testing untestable code - PHPBNL11

Testing „untestable“ PHP Code

Page 53: Testing untestable code - PHPBNL11

What else?

Generating testable code

Remember: No changes to the source code!

Page 54: Testing untestable code - PHPBNL11

What else?

Generating testable code

Generative Programming

Page 55: Testing untestable code - PHPBNL11

Generating testable code

Generative Programming

ConfigurationConfiguration

Implementationcomponents

Implementationcomponents

Generatorapplication

Generatorapplication

ProductProductGenerator

Generator

1 ... n

Page 56: Testing untestable code - PHPBNL11

Generating testable code

Generative Programming

ConfigurationConfiguration

Implementationcomponents

Implementationcomponents

Generatorapplication

Generatorapplication

ApplicationApplication

GeneratorGenerator

TestcasesTestcases

Page 57: Testing untestable code - PHPBNL11

Generating testable code

Generative Programming

A frame is a data structure for representing knowledge.

Page 58: Testing untestable code - PHPBNL11

Generating testable code

FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('[email protected]', 'New sale', '....');";

public FILEIndex_php5() {setFilename("index.php5");setRelativePath("/");

}

private void assign() {BEGINCONTENT()<?phpfunction buyCar(Car $oCar) {global $oDB;

<!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB);<!{MailSlot}!>}

?>ENDCONTENT() }}

Page 59: Testing untestable code - PHPBNL11

Generating testable code

FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('[email protected]', 'New sale', '....');";

public FILEIndex_php5() {setFilename("index.php5");setRelativePath("/");

}

private void assign() {BEGINCONTENT()<?phpfunction buyCar(Car $oCar) {global $oDB;

<!{Prefix}!>mysql_query(„INSERT INTO...“, $oDB);<!{MailSlot}!>}

?>ENDCONTENT() }}

Page 60: Testing untestable code - PHPBNL11

Generating testable code

Generative Programming

Extraction Show / hide parts of the code

ExampleMailSlot: mail('[email protected]', 'New sale', '....');

Page 61: Testing untestable code - PHPBNL11

Generating testable code

Generative Programming

Extraction Show / hide parts of the code

ExampleMailSlot: mail('[email protected]', 'New sale', '....');

<?phpfunction buyCar(Car $oCar) { global $oDB;

mysql_query("INSERT INTO...", $oDB); mail('[email protected]', 'New sale', '....');}

?>

Page 62: Testing untestable code - PHPBNL11

Generating testable code

Course of action

Customizing Change content of global vars Pre/Postfixes for own functions, methods, classes

ExamplePrefix: test_

Page 63: Testing untestable code - PHPBNL11

Generating testable code

Course of action

Customizing Change content of global vars Pre/Postfixes for own functions, methods, classes

ExamplePrefix: test_

<?phpfunction buyCar(Car $oCar) { global $oDB;

test_mysql_query("INSERT INTO...", $oDB);}

Page 64: Testing untestable code - PHPBNL11

Conclusion

How much effort to take?

Page 65: Testing untestable code - PHPBNL11

Conclusion

Conclusion

Change your mindset to write testable code!

Page 66: Testing untestable code - PHPBNL11

Conclusion

Conclusion

PHP is a swiss army knife.

Page 67: Testing untestable code - PHPBNL11

http://joind.in/2420