Testing untestable code - PHPBNL11

Preview:

DESCRIPTION

 

Citation preview

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

About me

Stephan Hochdörfer, bitExpert AG

Department Manager Research Labs

enjoying PHP since 1999

S.Hochdoerfer@bitExpert.de

@shochdoerfer

Warning! Use at your own risk...

No excuse for writing bad code!

Seriously, I am not kidding!

Theory

"There is no secret to writing tests, there

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

Theory

What is „untestable code“?

s

Theory

What is „untestable code“?

„new“ is evil!

Theory

What is „untestable code“?

Theory

What is „untestable code“?

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

Theory

Class toTest

Class toTestUnittest

Unittest

Requiredclass

Requiredclass

Requiredclass

Requiredclass

Theory

Class totest

Class totestUnittest

Unittest

Requiredclass

Requiredclass

Requiredclass

Requiredclass

DatabaseDatabase

Externalresource

Externalresource

Requiredclass

RequiredclassRequired

class

Requiredclass Webservice

Webservice

Theory

Class totest

Class totestUnittest

Unittest

Requiredclass

Requiredclass

Requiredclass

Requiredclass

DatabaseDatabase

Externalresource

Externalresource

Requiredclass

RequiredclassRequired

class

Requiredclass Webservice

Webservice

Theory

How to achieve „testable“ code?

Theory

How to achieve „testable“ code?

Refactoring

Theory

"Before you start refactoring, check that you

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

Testing „untestable“ PHP Code

Let the work begin...

Testing „untestable“ PHP Code

Safty instructions

Do not change existing code!

Testing „untestable“ PHP Code | __autoload

<?phpclass Car {

private $Engine;

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

}

}

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);

}

}

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();

Testing „untestable“ PHP Code | include_path

<?phpinclude('Engine.php');

class Car {private $Engine;

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

}}

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

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();

Testing „untestable“ PHP Code | include_path alternative

<?phpinclude('Engine.php');

class Car {private $Engine;

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

}}

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

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');

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');?>

Testing „untestable“ PHP Code

How to test private methods?

Testing „untestable“ PHP Code

How to test private methods?

There`s no need to!

Testing „untestable“ PHP Code

How to test private methods?

There`s no need to, but...

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');?>

Testing „untestable“ PHP Code | Namespaces

<?phpclass Car {

private $Engine;

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

}}

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

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);}

}

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);}

}

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');

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);

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

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()

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

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

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);

}

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);

}

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);

}

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

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

mail('order@domain.org', 'New sale', '....');}

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

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

mail('order@domain.org', 'New sale', '....');}

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

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

mail('order@domain.org', 'New sale', '....');}

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

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

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

mail('order@domain.org', 'New sale', '....');}

How to test Use classkit extension to overwrite internal functions

Testing „untestable“ PHP Code | overwrite internal functions

<?php

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

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

?>

Testing „untestable“ PHP Code

What else?

Generating testable code

Remember: No changes to the source code!

What else?

Generating testable code

Generative Programming

Generating testable code

Generative Programming

ConfigurationConfiguration

Implementationcomponents

Implementationcomponents

Generatorapplication

Generatorapplication

ProductProductGenerator

Generator

1 ... n

Generating testable code

Generative Programming

ConfigurationConfiguration

Implementationcomponents

Implementationcomponents

Generatorapplication

Generatorapplication

ApplicationApplication

GeneratorGenerator

TestcasesTestcases

Generating testable code

Generative Programming

A frame is a data structure for representing knowledge.

Generating testable code

FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('order@domain.org', '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() }}

Generating testable code

FileFrm FILEIndex_php5 { private String Prefix = "test_"; private String MailSlot = "mail('order@domain.org', '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() }}

Generating testable code

Generative Programming

Extraction Show / hide parts of the code

ExampleMailSlot: mail('order@domain.org', 'New sale', '....');

Generating testable code

Generative Programming

Extraction Show / hide parts of the code

ExampleMailSlot: mail('order@domain.org', 'New sale', '....');

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

mysql_query("INSERT INTO...", $oDB); mail('order@domain.org', 'New sale', '....');}

?>

Generating testable code

Course of action

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

ExamplePrefix: test_

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);}

Conclusion

How much effort to take?

Conclusion

Conclusion

Change your mindset to write testable code!

Conclusion

Conclusion

PHP is a swiss army knife.

http://joind.in/2420

Recommended