Unit Testing für Dummies
15.11.2009
Lars Jankowfsky, swoodoo AGThorsten Rinne, Mayflower GmbH
About me:
PHP, C++, Developer, Software Architect since 1992
PHP since 1998
Many successful projects from 2 to 20 developers
Running right now three projects using eXtreme Programming
CTO and (Co-)Founder swoodoo AG
(Co-)Founder OXID eSales AG
About me:
Diplom-Informatiker (FH) Certified Scrum MasterZend Certified Engineer
PHP since 1999
Many successful projects from 2 to 6 developers using Scrum, eXtreme Programming, Crystal Clear
Senior Developer / Team Lead at Mayflower GmbH
Master of phpMyFAQ
(c) aboutpixel.de(c) spackletoe http://www.flickr.com/photos/spackletoe/90811910/)
Unit Tests?
PHPUnit Fixtures
Stubs Pitfalls
PHPUnithttp://www.phpunit.de/
PHPUnit
phpunit myTest
user@workshop:/var/www/thorsten-zfguestbook/tests/application$ phpunit controllers_PostsControllerTest
PHPUnit
Fixtures
Fixtures Fixtures
Make sure that tests don‘t alter fixture
Fixture is FIXture
if you feel creating fixtures is too much work - refactor more!
Do never let tests leave altered tests
Fixtures the Ruby way...
Ruby uses YAML
www.yaml.org
PHP YAML support done by using Syck
Syck = YAML + fast
http://whytheluckystiff.net/syck/
http://www.frontalaufprall.com/2008/05/05/php-unit-database-fixtures-the-ruby-way/
Fixtures
YAML loading
public static function create($fileName) { $fileName = 'Fixtures'.DIRECTORY_SEPARATOR.$fileName; ob_start(); include $fileName; $fileContents = ob_get_contents(); ob_clean(); $yamlData = syck_load($fileContents); return $yamlData; }
Fixtures
YAML storing
public static function load($fixtures, $tableName) { if (is_array($fixtures) && count($fixtures)) { foreach ($fixtures as $fixture) { if (is_array($fixture) && is_array(current($fixture))) { Fixtures::load($fixture, $tableName); } $fields = array_keys($fixture); $statement = "INSERT INTO $tableName (" . implode(', ', $fields) . ") VALUES (:" . implode(", :", $fields) . ")"; $stmt = self::$_db->prepare($statement); if (count($fixture)) { foreach ($fixture as $key => $value ) { $stmt->bindValue(':'.$key, $value); } } $stmt->execute(); self::$_usedTables[$tableName] = $tableName; } } }
Fixtures
YAML - cleanup
if (!empty(self::$_usedTables)) { foreach (array_reverse(self::$_usedTables) as $tableName) { self::$_db->execute("TRUNCATE TABLE $tableName"); } }
Fixtures
Fixtures the other side ...
manual fixtures are too much work
use a test database
think about automatic creation of YAML files
Fixtures
Stubs
Mocking stubs? Stubs
Unittesting is about testing a unit of work, not a complete workflow
isolates your code from external dependencies
can be done with PHPUnit, but you don‘t need to
Mocking stubs The PHPUnit way Stubs
/** * A simple stub providing a simple result directly instead of using the database */class UserModelStub extends UserModel { public getUserCount() { return 10; }}
UserModelStub extends PHPUnit_Framework_Testcase { public function testGetUserCount() { $stub = $this->getMock(‘UserModel‘); $stub->expects($this->any())->method(‘getUserCount‘)->will($this->returnValue(10)); }}
Pitfalls
Code the unit test first. Pitfalls
OOP, public, private
Globals
Superglobals
Sessions
Cookies
Dependencies ...
Separate logic from view
create accessors, add all parameters in calls
Pitfalls
Dependency Example
class displayUserDetails(){ /** * Processes input and sends user first name, last name to display; */ function show() { global $dbLink; global $templateEngine; $itemId = (int) $_REQUEST['user_id']; $firstName = $dbLink->getOne("select first_name from users where id = $itemId"); $lastName = $dbLink->getOne("select last_name from users where id = $itemId"); $templateEngine->addTemplateVar('firstName', $firstName); $templateEngine->addTemplateVar('lastName', $lastName); $templateEngine->display(); }}
Pitfalls
Dependency Example
/** * A view class responsible for displaying user details. */class userView(){ /** * Loads user object and sends first name, last name to display */ public function show() { $userId = $this->_inputProcessor->getParameter("user_id"); $this->templateEngine->addTemplateVar('user', $this->model->loadUser(userId)); $this->templateEngine->display(); }} /** * And the corresponding model */class userModel(){ public function loadUser($userId) { $user = new User( $userId ); return array('firstName' => $user->getFirstName(), 'lastName' => $user->getLastName()); }}
Pitfalls
STOP
class someOtherClass { var $setting;
function calculateSomething($a, $b) { return $a+$b; }}
class myOldNastyClass {
function needToTestThisFunction() {
$class = new someOtherClass();
$z = $_GET['input'];
// ....
return $class->calculateSomething( $class->setting, $z); }}
Layer Example Pitfalls
Layer Example
class someOtherClass { private $setting;
public function calculateSomething($a, $b) { return $a+$b; }
public function setSetting($set) { $this->setting = $set; }
public function getSetting() { return $this->setting; }}
class myInput { public function getParameter($name) { return $_GET[$name]; }}
class myOldNastyClass {
private $input; // set e.g. in constructor
public function needToTestThisFunction(someOtherClass &$class, $z) {
$z = $input->getParameter('input'); // ....
return $class->calculateSomething( $class->getSetting(), $z); }}
Pitfalls
(c) istockphoto
„Questions?“