Testing untestable Code - PFCongres 2010

46
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

Transcript of Testing untestable Code - PFCongres 2010

Page 1: Testing untestable Code - PFCongres 2010

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 - PFCongres 2010

About me

Founder of bitExpert AG, Mannheim

Field of duty Department Manager of Research Labs Head of Development for bitFramework

Focusing on PHP Generative Programming

Contact me @shochdoerfer [email protected]

Page 3: Testing untestable Code - PFCongres 2010

Agenda

1. Theory

2. How to test untestable PHP Code

3. Generate testable code

4. Conclusion

Page 4: Testing untestable Code - PFCongres 2010

Theory

"There is no secret to writing tests, there

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

Page 5: Testing untestable Code - PFCongres 2010

Theory

What is „untestable code“?

Page 6: Testing untestable Code - PFCongres 2010

Theory

What is „untestable code“?

Global variables Singleton Registry Globale state

static method calls Control flow in a constructor Tight coupling

Dependencies to concrete implementations to many dependencies to other classes Dependencies without control

Page 7: Testing untestable Code - PFCongres 2010

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 8: Testing untestable Code - PFCongres 2010

Theory

Class toTest

Unittest

Requiredclass

Requiredclass

Page 9: Testing untestable Code - PFCongres 2010

Theory

Class totest

Unittest

Requiredclass

Requiredclass

Database

Externalresource

Requiredclass

Requiredclass

Webservice

Page 10: Testing untestable Code - PFCongres 2010

Theory

Class totest

Unittest

Requiredclass

Requiredclass

Database

Externalresource

Requiredclass

Requiredclass

Webservice

Page 11: Testing untestable Code - PFCongres 2010

Theory

How to achieve „testable“ code?

Page 12: Testing untestable Code - PFCongres 2010

Theory

How to achieve „testable“ code?

Refactoring

Page 13: Testing untestable Code - PFCongres 2010

Theory

"Before you start refactoring, check that you

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

Page 14: Testing untestable Code - PFCongres 2010

Agenda

1. Theory

2. Testing untestable PHP Code

3. Generate testable code

4. Conclusion

Page 15: Testing untestable Code - PFCongres 2010

Agenda

1. Theory

2. Testing untestable PHP Code– Hardcoded Dependencies– Procedural code– Overwrite internal PHP functions

3. Generate testable code

4. Conclusion

Page 16: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code

Assumption

Do not change existing code!

Page 17: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code | __autoload

<?phpclass Car {

private $Engine;

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

}

}

Page 18: Testing untestable Code - PFCongres 2010

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 19: Testing untestable Code - PFCongres 2010

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 20: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code | include_path

<?phpinclude('Engine.php');

class Car {private $Engine;

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

}}

Page 21: Testing untestable Code - PFCongres 2010

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 22: Testing untestable Code - PFCongres 2010

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 23: Testing untestable Code - PFCongres 2010

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

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

Page 24: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code | Namespaces

<?phpclass Car {

private $Engine;

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

}}

Page 25: Testing untestable Code - PFCongres 2010

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 26: Testing untestable Code - PFCongres 2010

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 27: Testing untestable Code - PFCongres 2010

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 28: Testing untestable Code - PFCongres 2010

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 29: Testing untestable Code - PFCongres 2010

Agenda

1. Theory

2. Testing untestable PHP Code– Hardcoded Dependencies– Procedural code– Overwrite internal PHP functions

3. Generate testable code

4. Conclusion

Page 30: Testing untestable Code - PFCongres 2010

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 31: Testing untestable Code - PFCongres 2010

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 32: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code | Test functions

How to test PHPUnit can call methods 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 33: Testing untestable Code - PFCongres 2010

Agenda

1. Theory

2. Testing untestable PHP Code– Hardcoded Dependencies– Procedural code– Overwrite internal PHP functions

3. Generate testable code

4. Conclusion

Page 34: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code | overwrite internal functions

<?phpfunction buyCar(Car $oCar) {

global $oDB;

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

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

Page 35: Testing untestable Code - PFCongres 2010

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 Do not load mysql extension. Provide own implementation Unfortunatly mail() is part of the PHP core

Page 36: Testing untestable Code - PFCongres 2010

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 mock internal functions

Page 37: Testing untestable Code - PFCongres 2010

Testing „untestable“ PHP Code | overwrite internal functions

<?php

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

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

?>

Page 38: Testing untestable Code - PFCongres 2010

Agenda

1. Theory

2. Testing untestable PHP Code

3. Generate testable code

4. Conclusion

Page 39: Testing untestable Code - PFCongres 2010

Generative Programming

Generate testable code

Configuration

Implementationcomponents

Generatorapplication

ProductGenerator

1 ... n

Page 40: Testing untestable Code - PFCongres 2010

Generative Programming

Generate testable code

Configuration

Implementationcomponents

Generatorapplication

Application

Generator

Testcases

Page 41: Testing untestable Code - PFCongres 2010

Course of action

Extraction „Mask“ parts of the code

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

Recombine Re-order parts of the code

Generate testable code

Page 42: Testing untestable Code - PFCongres 2010

Generate 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 43: Testing untestable Code - PFCongres 2010

Generate testable code

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

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

1. Example

Prefix: test_

Page 44: Testing untestable Code - PFCongres 2010

Generate testable code

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

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

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

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

?>

1. Example

Prefix: test_

2. Example

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

Page 45: Testing untestable Code - PFCongres 2010

Conclustion

Conclusion

Change mindset to write testable code Dependency Injection

Look for other options to raise the bar Work around limitations of PHP PHP is flexible, use it that way

Page 46: Testing untestable Code - PFCongres 2010