Refactoring, Agile Entwicklung, Continuous Integration – all diese für nachhaltigen Erfolg wichtigen Vorgehensweisen setzen Erfahrung mit Unit Testing voraus. Abseits von den üblichen "Bowling"-Beispielen möchten wir gerne einen Crashkurs inkl. Best Practices für das erfolgreiche Unit Testing durchführen. Anhand eines Beispielprojekts auf Basis des Zend Frameworks werden wir nach der Installation von PHPUnit auf allen Notebooks gemeinsam eine kleine Applikation aufbauen, die durchgehend Test-driven entwickelt wird.
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Unittests für Dummies
1. Unit Testing für Dummies
15.11.2009
Lars Jankowfsky, swoodoo AG
Thorsten Rinne, Mayflower GmbH
2. 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
3. About me:
Diplom-Informatiker (FH)
Certified Scrum Master
Zend 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
9. 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
10. Fixtures the Ruby way... Fixtures
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/
11. YAML loading Fixtures
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;
}
12. YAML storing Fixtures
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; }
}
}
13. YAML - cleanup Fixtures
if (!empty(self::$_usedTables)) {
foreach (array_reverse(self::$_usedTables) as $tableName) {
self::$_db->execute("TRUNCATE TABLE $tableName");
}
}
14. Fixtures the other side ... Fixtures
manual fixtures are too much work
use a test database
think about automatic creation of YAML files
16. 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
17. 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));
}
}
19. Code the unit test first. Pitfalls
OOP, public, private
Globals
Superglobals
Sessions
Cookies
20. Dependencies ... Pitfalls
Separate logic from view
create accessors, add all parameters in calls
21. Dependency Example Pitfalls
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();
}
}
22. Dependency Example Pitfalls
/**
* 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());
}
}
25. Layer Example Pitfalls
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);
}
}
26. Layer Example Pitfalls
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);
}
}