
Happy partying!
A journey in web development, [computer] science, engineering:
getting to know what lies under the hood -- Giorgio Sironi
I had some time during the past few days so I read your book and solved the exercises found in the book.I hope you can find these examples useful, but try not to read a solution before actually trying to solve an exercise. I have not proof read this code but it seems that its quality is good. There is always room for improvement, especially in the stubs section of the tests, and in refactoring of the production code.
I'm pretty sure not all of my solutions are the best and some could probably be improved / changed so I decided to create a Github repository with my solutions (http://github.com/Kami/practical-php-testing- exercise-solutions) so others can fork it and improve / refactor my solutions.
<?php /** * @param string $a name your parameter better than this one * @return boolean */ function doSomething($a) { // code... }These annotations are parsed by phpDocumentor to automatically produce Api documentation in various formats, such as html and pdf.
<?php /** * @return Zend_Form_Element */ function doSomething() { // code... }Since this is a widely employed standard for php frameworks, I decided to rely on @return annotations as the mean to define domain model relationships in my project NakedPhp. This is not different from the relationships phpDocumentor infers to generate links between html documents: for instance the Zend_Form_Element return type definition would be printed as a link to its actual Api documentation html page, to allow fast navigation.
<?php /** * @return array */ function doSomething() { // code... }Not very clear, as the question that arises is "What is the type of array elements?"
/** * @return Doctrine\Common\Collections\Collection */or:
/** * @return ArrayObject */At least in the former case you know that there are homogeneous elements in the returned collection, but the situation is the same.
/** * @return array of @see Zend_Form_Element */But this is not a standard, and different developers would use different annotations:
/** * @return array this contains Zend_Form_Element instances */ /** * @return array of Zend_Form_Element */These annotations would be parsed by phpDocumentor, but the class name would be mangled in a string and not manageable anymore. It's like scraping a blog versus using a feed.
/** * @return array[string]Zend_Form_Element */They also say that phpDocumentor already support it, but there is no trace of that in its own documentation:
The datatype should be a valid PHP type (int, string, bool, etc), a class name for the type of object returned, or simply "mixed".The original Naked Objects implementation is written in Java and takes advantage of generics (not available in Php):
/** * @return List<FormElement> */When javadoc or Naked Objects parse annotations, they know instantly the collection elements type, thanks to a reasonable standard that imitates the language syntax: I would be glad to do the same in Php, but there is no syntax to refer to.
@giorgiosironi we've considered adding it, but like the idea of crosslinking with our authors' blogs when they create an entry. /cc @chwenzThe problem is that there is no duplicate post on the author's blog, at least for the majority of them. The only place where leaving a comment makes sense is on the original article, which admits no feedback.
var jsonString = "({name: 'value', toString: function() { return this.name; }})";
What's the problem? That this is not Json, it's plain javascript syntax... Json.org says a value is string|number|object|array|true|false|null and an object is something which is enclosed in {}. Do you see anonymous functions in this specification?Or, put some effort into writing more general, commonly-used classes rather than lots of little ones.Never, ever, heard of Single Responsibility Principle? Cohesion? Decoupling? That design should not be driven by how fast including source files will be? I actually refactor every class that surpasses 400-500 lines of code.
@giorgiosironi agreed. complete crap. Another meaningless artificial benchmark. Not even with repro code. that post is a big #fail.There are very valid posts in this year's PHP Advent, but these hidden "gems" are dangerous and I wonder why no feedback is accepted. Open discussion would benefit the community more than lectures on php practices.
Probabilmente ancora non siamo riusciti a formalizzare una analisi teoretica sopra gli oggetti che descrivono software. Non so nemmeno se la cosa, allo stato attuale sia verosimile o abbia un senso. Tuttavia, cominciare a pensare in questa direzione credo possa essere un punto di partenza proprio per trasformare la Programmazione da "Arte" a "Scienza", obiettivo perseguito anche dallo stesso Misko.
Maybe we have not yet formalized a theorical framework on software objects. I don't even know if this would make sense at this point. However, I think beginning to move in this direction can be a starting point to transform programming from Art to Science, an objective pursued from Misko too. (translation of mine)An abstraction such an object graph let us make statements which do not depend on the technology (Java or Php) but only on the object-oriented paradigm, and that thus will be true in many languages and platforms, or 10 years from now when Php 9.3 and Java 14.0 will be released (provided that we maintain the OO paradigm; considering that Smalltalk is from the 1970s, it may last for a long time).
<?php /** * Naked Php is a framework that implements the Naked Objects pattern. * @copyright Copyright (C) 2009 Giorgio Sironi * @license http://www.gnu.org/licenses/lgpl-2.1.txt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * @category Example * @package Example_Model */ /** * @Entity */ class Example_Model_City { /** * @Id @Column(type="integer") * @GeneratedValue(strategy="AUTO") */ private $_id; /** * @Column(type="string") */ private $_name; public function __construct($name) { $this->setName($name); } /** * @return string the name */ public function getName() { return $this->_name; } public function setName($name) { $this->_name = $name; } public function __toString() { return (string) $this->_name; } }Private properties are accessed via reflection.
You used Zend Framework for this small php application. From 1 to 10 [which is the grade framework in Italian secondary schools], how much is this technology sophisticated comparing to the other ones in the php world?I answered:
8 or 9, I can't think about a more advanced php technology (and with a so much steep learning curve), unless I think about Doctrine 2.Before Symfony and CodeIgniter developers bite me: given the occasion, I would say quite the same of applications built with your frameworks, since I'm making a comparison with the legacy code I had to deal with in the past.
Please pardon me for my unsolicited email, but I saw your blog and I believe that you are one of the best in the software community. My name is [omissis], and I'm a C#/ASP.NET programmer from the Philippines, but I really want to learn and understand Unit Testing and TDD the right way. I didn't take Computer Science or a similar course in college. I really want to learn software design and development, on how to develop an application from ground-up using TDD. I hope you can give me advices, since I'm not able to afford a good book.I am no particular expert in C# since I mostly work in php. As you may know, I have written a free CreativeCommons-licensed ebook on php applications testing.
<?php /** * Naked Php is a framework that implements the Naked Objects pattern. * @copyright Copyright (C) 2009 Giorgio Sironi * @license http://www.gnu.org/licenses/lgpl-2.1.txt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * @category NakedPhp * @package NakedPhp_Storage */ namespace NakedPhp\Storage; use Doctrine\ORM\UnitOfWork; use NakedPhp\Mvc\EntityContainer; use NakedPhp\Stubs\User; /** * Exercise the Doctrine storage driver, which should reflect to the database * the changes in entities kept in an EntityCollection. */ class DoctrineTest extends \PHPUnit_Framework_TestCase { private $_storage; public function setUp() { $config = new \Doctrine\ORM\Configuration(); $config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ArrayCache); $config->setProxyDir('/NOTUSED/Proxies'); $config->setProxyNamespace('StubsProxies'); $connectionOptions = array( 'driver' => 'pdo_sqlite', 'path' => '/var/www/nakedphp/tests/database.sqlite' ); $this->_em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config); $this->_regenerateSchema(); $this->_storage = new Doctrine($this->_em); } private function _regenerateSchema() { $tool = new \Doctrine\ORM\Tools\SchemaTool($this->_em); $classes = array( $this->_em->getClassMetadata('NakedPhp\Stubs\User') ); $tool->dropSchema($classes); $tool->createSchema($classes); } public function testSavesNewEntities() { $container = $this->_getContainer(array( 'Picard' => EntityContainer::STATE_NEW )); $this->_storage->save($container); $this->_assertExistsOne('Picard'); } /** * @depends testSavesNewEntities */ public function testSavesIdempotently() { $container = $this->_getContainer(array( 'Picard' => EntityContainer::STATE_NEW )); $this->_storage->save($container); $this->_simulateNewPage(); $this->_storage->save($container); $this->_assertExistsOne('Picard'); } public function testSavesUpdatedEntities() { $picard = $this->_getDetachedUser('Picard'); $picard->setName('Locutus'); $container = $this->_getContainer(); $key = $container->add($picard, EntityContainer::STATE_DETACHED); $this->_storage->save($container); $this->_assertExistsOne('Locutus'); $this->_assertNotExists('Picard'); } public function testRemovesPreviouslySavedEntities() { $picard = $this->_getDetachedUser('Picard'); $container = $this->_getContainer(); $key = $container->add($picard, EntityContainer::STATE_REMOVED); $this->_storage->save($container); $this->_assertNotExists('Picard'); $this->assertFalse($container->contains($picard)); } private function _getNewUser($name) { $user = new User(); $user->setName($name); return $user; } private function _getDetachedUser($name) { $user = $this->_getNewUser($name); $this->_em->persist($user); $this->_em->flush(); $this->_em->detach($user); return $user; } private function _getContainer(array $fixture = array()) { $container = new EntityContainer; foreach ($fixture as $name => $state) { $user = $this->_getNewUser($name); $key = $container->add($user); $container->setState($key, $state); } return $container; } private function _assertExistsOne($name) { $this->_howMany($name, 1); } private function _assertNotExists($name) { $this->_howMany($name, 0); } private function _howMany($name, $number) { $q = $this->_em->createQuery("SELECT COUNT(u._id) FROM NakedPhp\Stubs\User u WHERE u._name = '$name'"); $result = $q->getSingleScalarResult(); $this->assertEquals($number, $result, "There are $result instances of $name saved instead of $number."); } private function _simulateNewPage() { $this->_em->clear(); // detach all entities } }Do you have other suggestions to further refactor this code?
[12:29:32][giorgio@Indy:~]$ history | awk '{print $2;}' | sort | uniq -c | > sort -nr | head -n 10 4924 vim 1326 svn 879 nakedphpunit // it's an alias for phpunit --bootstrap=... 616 sudo 438 cd 266 ls 238 osstest_sqlite 207 phing 135 ./scripts/regenerate 127 grepOf course most of them were only typed the first time and then recalled. From these data you can infer that I use the command line interface a lot, and I've never been more productive. This statistic is a typical leverage of command line tools in a construct that took me less than a minute to write and that I can repeat whenever I want in a few seconds.
<target name="test"> <phpunit bootstrap="tests/bootstrap.php"> <formatter type="summary" usefile="false" /> <batchtest> <fileset dir="tests"> <include name="**/*Test.php"/> </fileset> </batchtest> </phpunit> </target>When you push the big test button on your desktop (from the cli type phing test), this xml configuration will hopefully produce a report while your test suite runs.
<target name="test"> <exec command="phpunit --bootstrap tests/bootstrap.php
--configuration tests/phpunit.xml --colors"
dir="${srcRoot}" passthru="true" /> <exec command="phpunit --bootstrap=example/application/bootstrap.php
--configuration example/application/tests/phpunit.xml --colors"
dir="${srcRoot}" passthru="true" /> </target>$srcRoot is a property that specifies the working directory to run the phpunit command in. passthru makes the task echo the output of the command.
<?php include 'table.php';Include files gained the same scope of the parent script, with no need to look for global variables somewhere.
<?php $user = new User(); $entries = array(...); $showCaption = true; include 'table.php';This programming style is also known as Accumulate and fire, and it exposes a not very good Api. There is no way to learn the variables needed to the template, nor to immediately signal errors in population: if I comment $user assignment or set it to an array, the script would not notice until the variable is used deep in table.php.
Chemical engineers learned long ago that a process that works in the laboratory cannot be implemented in a factory in one step. An intermediate step called the pilot plant is necessary [...] Hence, plan to throw one away; you will, anyhow.I see this as a perfect reverse situation of movies production.
How many times in the last month have you seen a broken screen in the browser? How many times did you have to debug in the browser, by looking at the output, inserting debug statements and breaking redirects? How many times did you perform manual testing, by loading a staging version of your application and tried out different workflows in the browser?
If the answer to these questions is more than very few, it's likely thatThis book is aimed to php developers and features the articles from the Practical php testing series, while the other half of it is composed by new content:
you should give automated testing a chance.
It is a (two-dimension) representation of a pipe. The map is not the territory , but in software engineering terms they are models of it....