Archive for May, 2006

The Unit Testing Experiment

Unit Testing is one of those topics which in PHP circles has not built up huge momentum. Of the experienced PHP developers I know, very few practice it even though many know about the basics whether from a book they read, or an online article they discovered. Once I believed this apparent lack of adoption was a logical state - Unit Testing was time consuming afterall and added more work to development. Yes, a few people thought it was the greatest thing since Prometheus stole Fire from the Gods, but they were few in number (even if they were big people who wrote the books on topics like Design Patterns and Refactoring). I was completely wrong. Totally. I feel almost ashamed at my early dismissal of the practice. So here I apologise for my ignorance and layout my personal thoughts on Unit Testing, and its extensive use in Test Driven Design.

So what is Unit Testing? How does it work? Should I utilise it? These are the questions that went through my head back last Autumn when I had time to read up on the growing phenomena called Test Driven Design (or Development) abbreviated to TDD, often association with Agile Development. But more on my observations of TDD later. First - Unit Testing basics.

In simple terms, Unit Testing is a method for testing classes (whether they be Java, PHP or other). A Unit Test is usually a class itself with test methods. These methods (Test Cases) ensure the class is behaving as expected, i.e. it’s outputs are correct, it’s inputs are properly used, all the class variables are set as expected, etc. In short, a Unit Test ensures the class works in accordance with your expectations, i.e. it does what you expect it to do.

An example: I have an Addition class with a method for cumulatively adding numbers to a total.

class Addition {

        private $total = 0;

        function add($number){
                $this->total += $number
        }

        function getTotal(){
                return$this->total;
        }

}

Now, to test this class I write a Unit Test which will test its expected behaviour (using SimpleTest assertions).

class TestAddition extends UnitTestCase {

        public function __construct(){
                // set title of test
                $this->UnitTestCase(‘Test Addition’);
        }

        public function testAdd(){
                $addition = new Addition();
                $addition->add(1000);
                // check new total = 1000 (0 + 1000)
                $this->assertEqual($addition->getTotal(), 1000);
        }

}

I grew pretty alarmed at a similar example last Autumn. The Unit Test is as large as the actual class being tested! My biggest piece of advice at this stage: Unit Testing looks pretty dull at first glance. Don’t let those early impressions fool you. I let them fool me for months and now I feel like kicking myself when I look at previous code I wrote without the benefits of Unit Tests. If it makes you feel a little better - most tests will be far smaller than the classes they test. Continuing…

This example simply tests if the total is the amount we expected (1000) by using the Addition::add() method to add 1000 to the total in a new class instance. If the test passes, then the class is working as expected. If it fails we need to fix it - a failure might be a bug, an exception or a PHP error of some type. It’s also possible the test itself is flawed.

The interesting part is what happens three months down the line if someone changes the addition operator += to -= by mistake. Now ordinarily this bug would need to be hunted down once our application stopped working properly, a process which could take hours or even days in some extreme cases. However, since this Unit Test now exists, and since we should (must) insist all developers regularly run these tests when they make changes, we would be able to locate the bug quickly. In fact, the developer who made the mistake should know immediately he did something wrong and, since the change was recent, be able to locate it rapidly.

This is an interesting idea - writing a Unit Test takes extra time when developing, but can save far more time later in the application’s life when maintaining the app, as well as directly promoting quality. Now this is Unit Testing 101 - regular testing is akin to the early bird, which has a preference for bugs over worms, but the metaphor holds. However it is immensely difficult to wrap one’s head around the idea that Unit Testing saves time - especially when during early development it obviously takes extra time to implement tests. Extra time when we could be adding new features, extra time that can slow down the time taken to a finished product. But hold on, take a step back. Is it not reasonable to consider that by catching bugs quickly we will make adding features easier? As well as leaving more time for such features to be developed since the maintenance time cost will be reduced?

When I reached this point I was nodding happily, but I was just a few steps up the Unit Testing learning curve. There is more to Unit Testing than just testing.

In theory, a class is not always a static piece of source code that will never change. Quite the opposite - in many cases we will alter classes to add new functionality, reorganise classes into separate units of responsibility or perhaps simply clean them up. One thing I tend to do with new code is try to restructure it so it’s easier to work with and understand. A key idea behind such changes (this is a form of Refactoring) is that it does not alter the overall behaviour of the class or application (well, ideally).

Now, if we are going to make such changes we find we have a highly valuable tool. Unit Testing. Let’s say I find a large 200 line method in a class. I don’t like large methods - they’re often intolerably difficult to read and understand. To solve the problem, I might break it down into several discrete blocks - each of which will be separate methods with a nice descriptive name. Sounds easy enough - it’s the simplest possible change - extracting discrete functionality from one method into several new private methods.

Here’s the usefulness of Unit Testing. After I’m done with each change I should immediately re-run my Unit Tests (assuming I wrote them!). Did the change break something? Is the class behaviour unaltered? If the tests pass I have succeeded in making the class more readable, and with smaller methods I also increase potential reuse. Plus, I can rest assured the risk of my adding a new bug is minimised since my tests run clean.

More happy thoughts floated through my brain at this point. I began thinking of Unit Tests as being a safety net allowing me to safely manage change and reduce the risks of such changes. Unit Testing was looking very nice - even the extra time to write tests looked reasonable. Ah, but the learning curve was still beckoning…

It turned out that I was still in the cycle of Plan -> Code -> Test. I planned a class, wrote the source code, wrote the tests, and ran them. In the process of this cycle I found myself cursing the tests at times. After writing a large class, my tests would sometimes become difficult to write (not incredibly so - but it was a little tiresome, though still worth the effort for future maintenance). At this point I revisited the theory of “Test Driven Design” or TDD. This practice advised writing tests BEFORE writing the source code. Ridiculous, I thought to myself. How can I test what doesn’t yet exist? How will this help with those test writing issues?

The answer I found was that tests, besides making for easier efficient maintenance and allowing me to manage change with little risk, also documented a class. Now, I don’t mean HOW the class does something, I mean WHAT it does, that is, how it behaves when being used. If you took my Unit Test for the Addition class, the test simply states that calling add(1000) should add 1000 to the total, which is then accessible from getTotal(). It’s not source code, it’s not a word document, but it is documentation - in a weird way I never considered before.

class TestAddition extends UnitTestCase {

        public function __construct(){
                // set title of test
                $this->UnitTestCase(‘Test Addition’);
        }

        // the test "documents" how Addition:add() should behave!
        public function testAdd(){
                $addition = new Addition();
                $addition->add(1000);
                $this->assertEqual($addition->getTotal(), 1000);
        }

}

Now if a Unit Test is documentation that describes what a class does, then it can indeed be used to design a class. Simply write a test for a proposed class, each method testing a specific piece of behaviour, and once the test describes what you want, fire up your IDE/Editor and write enough source code to pass that test. If it passes, then your class is in agreement with your “documentation”, the Unit Test. Who needs a design document that took 20 minutes to write? We have a Unit Test!

In our example’s case, you would have written the Addition test, then wrote the class itself to pass that test. Hey presto, welcome to Test Driven Design - the practice of writing tests first which you can utilise to hone your design - all before writing any source code to pass the tests.

What’s remarkable about TDD is that once you get past the “Hey, this is adding more time to development!” reaction (I’ve been there!) you begin to see a few changes in your programming approach. Its very subtle, you may not even notice it at first. You see tests highlight behaviour. If you don’t like the resulting behaviour (as documented by the test) - well, modify the test. If you see where behaviour is not specific enough to the class - maybe you have a Logger which writes to a file, and you realise it should be optional to write to a database - then remove that from the tests, and start a new test case for Database and File writers, with maybe a Writer Factory to select one of these options.

If that makes little sense, consider this. At this point you HAVE NOT WRITTEN source code - just tests. Since you have not written source code - you have not spent much time writing source code which will need to be discarded, or altered, or refactored into something else (a time-expensive process heavily prone to error without tests to catch problems). In fact, you will most likely have saved part of that wasted time by letting the Unit Tests guide you to a more acceptable solution, i.e. the tests have driven your design for the class. And the design is more easily tested.

I’ll cut off this blog entry with a final paragraph.

My first experience of Unit Testing was uninspiring. I found it tedious, time consuming, of limited utility, and did I mention tedious? I agreed with all the sentiments that testing, although nice, was too time-costly when development resources were limited. Today, I realise I was completely wrong. If you treat Unit Tests as just tests - they are tedious. If you get a feel for using them to test, manage change, save maintenance time, and increase your ability to design well early then Unit Testing is all consuming. It takes practice and experience, but I found it to be incredibly powerful as a design tool. I highly recommend people not dismiss it after a few days of use - stick with it, and most importantly read the theory in advance of using it. Otherwise it may feel like picking up a new power tool and forgetting to read the Instruction Manual…

I need to rest my fingers for a while now…:-).

Well, it was bound to happen sooner or later…Refactoring Partholan

Edit: Since the original post I have continued some unit testing driven designing - the near final version is posted to the QS forums HERE. A few amendments are likely - for example we currently search locations via PHP checks - it would be far easier to simply add the locations to our includes directory list. You thoughts on the topic are welcome, and many thanks to the Devnetwork members who contributed ideas and feedback.

I’m slowly back on the case with Partholan and Quantum Star SE. After spending a few hours looking through the source code again I’ve been putting together a small list of needed improvements. The first improvement is the most obvious, and the most damaging. A lot of classes need to make calls either to grab singletons, or grab instances from the current ApplicationHelper. In practice it works, but it’s messy. I could leave it alone, but future changes would start getting bogged down eventually. In short the classes have too many dependencies - and I need to shift these out to a single access point.

The suggested solution is to implement a ServiceLocator. A ServiceLocator is similar to a Registry (storing object instances for future use) but it also adds some additional responsibilty. For example, it is also responsible for instantiating objects - not simply storing them though it does that once its instantiated something. That’s not the only interpretation - many ServiceLocator classes I’ve found in Java and PHP are really just Registry classes under a different name. Anyway, the new Partholan class has been coded for the most part, although I have yet to implement object instantiation via Factory classes (as is needed for Database connections and such). The basic method names used are:

Public:

- registerService()
- registerFactory()
- addSearchLocations()
- removeService()
- removeFactory() [not implemented]
- getService()
- hasService()
- hasFactory()
- hasSearchLocations()

Private:

- getServiceFromCache() [not implemented]
- getServiceFromClass() [not implemented]
- getServiceFromFile() [not implemented]
- getServiceFromFactory() [not implemented]

The 4 private methods are likely to result from refactoring (Extract Method) in the public getService() where the 4 possible avenues to locating and returing an instantiated object are determined by a conditional if…else block. Haven’t bothered with the refactoring yet since it’s not worth the effort but adding factories will turn that getService() method into a 100 line or more smelly monster.

Anyways, the second difference in this round of Partholan updating is that I have committed to bringing out the big guns. I’ll be writing unit tests for all the changes I make, especially since refactoring without testing of some kind is a sure way to introduce small annoying bugs. I’ve already posted this to Devnetworks for some feedback but here’s the new ServiceLocator class with unit tests…

class Partholan_ServiceLocator /* implements Partholan_iServiceLocator */{

        private static$instance = false;

        private $instances = array();

        private $factories = array();

        private $searchLocations = array();

        public function __construct(){// allow direct instances

        }

        staticfunction getInstance(){
                if(!self::$instance)
                {
                        self::$instance = new Partholan_ServiceLocator();
                }
                return self::$instance;
        }
     
        public function registerService($serviceName, $serviceInstance){
                if(is_object($serviceInstance)&& strlen($serviceName))
                {
                        $this->instances[$serviceName] = $serviceInstance;
                        returntrue;
                }
                trigger_error(‘Service could not be registered. Requires a valid Service name and object.’);
        }

        public function removeService($serviceName){
                unset($this->instances[$serviceName]);
        }

        public function getService($serviceName){
                if(isset($this->instances[$serviceName]))
                {
                        return$this->instances[$serviceName];
                }
                elseif(is_class($serviceName))
                {
                        $this->instances[$serviceName] = new$serviceName();
                        return$this->instances[$serviceName];
                }
                else
                {
                        foreach($this->searchLocationsas$location)
                        {
                                if(file_exists($location . DIRECTORY_SEPARATOR . $serviceName . ‘.php’))
                                {
                                        require_once($location . DIRECTORY_SEPARATOR . $serviceName . ‘.php’);
                                        $this->instances[$serviceName] = new$serviceName();
                                        return$this->instances[$serviceName];
                                }
                        }
                }
                // factory lookups to be added later
                trigger_error(‘Specified Service could not be located.’, E_USER_ERROR);
        }

        public function registerFactory($factoryName, $serviceFactory){
                if(is_object($serviceFactory)&& strlen($factoryName))
                {
                        $this->factories[$factoryName] = $serviceFactory;
                        returntrue;
                }
                trigger_error(‘Factory could not be registered. Requires a valid Factory name and object.’);
        }

        public function addSearchLocations(){
                foreach(func_get_args()as$dirPath)
                {
                        $this->searchLocations[] = $dirPath;
                }
        }

        public function hasService($serviceName){
                if($this->instances[$serviceName])
                {
                        returntrue;
                }
                returnfalse;
        }

        public function hasFactory($factoryName){
                if($this->factories[$factoryName])
                {
                        returntrue;
                }
                returnfalse;
        }

        public function hasSearchLocation($dirPath){
                if(in_array($dirPath, $this->searchLocations))
                {
                        returntrue;
                }
                returnfalse;
        }

}

My simple empty object (used in testing object presences)…

class EmptyObject {

        public function __construct(){}

}

The unit tests…

class Partholan_ServiceLocator_TestCase extends UnitTestCase {

        private $data = array();

        public function __construct(){
                $this->UnitTestCase(‘ServiceLocator Test’);
        }

        public function setUp(){}

        public function tearDown(){}

        public function testRegisteringService(){
                $sl = new Partholan_ServiceLocator();
                $sl->registerService(‘EmptyObject’, new EmptyObject);
                $this->assertTrue($sl->hasService(‘EmptyObject’));
                $this->assertIsA($sl->getService(‘EmptyObject’), ‘EmptyObject’);
        }

        public function testRemovingService(){
                $sl = new Partholan_ServiceLocator();
                $sl->registerService(‘EmptyObject’, new EmptyObject);
                $sl->removeService(‘EmptyObject’);
                $this->assertFalse($sl->hasService(‘EmptyObject’));
        }

        public function testAddingSearchLocations {
                $sl = new Partholan_ServiceLocator();
                $sl->addSearchLocations(‘/tmp’, ‘/home’);
                $this->assertTrue($sl->hasSearchLocation(‘/tmp’));
                $this->assertTrue($sl->hasSearchLocation(‘/home’));
        }

        // test: no class, no file included - get file and instantiate object
        public function testGettingServiceInstanceFromFile(){
                $sl = new Partholan_ServiceLocator();
                $sl->setSearchLocations(dirname(__FILE__)); // or location of EmptyObject.php if other
                $mk = $sl->getService(‘EmptyObject’);
                $this->assertIsA($mk, ‘EmptyObject’);
                $this->assertTrue($sl->hasService(‘EmptyObject’));
        }

        // test: is_class method where file pre-included()
        public function testGettingServiceInstance(){
                $sl = new Partholan_ServiceLocator();
                $mk = $sl->getService(‘EmptyObject’);
                $this->assertIsA($mk, ‘EmptyObject’);
                $this->assertTrue($sl->hasService(‘EmptyObject’));
        }

        public function testRegisteringFactory(){
                $sl = new Partholan_ServiceLocator();
                $sl->registerFactory(‘EmptyObject’, new EmptyObject);
                $this->assertTrue($sl->hasFactory(‘EmptyObject’));
                $this->assertIsA($sl->getFactory(‘EmptyObject’), ‘EmptyObject’);
        }

}

Anyone with comments, I’d love to hear from you. Especially if you’ve used something similar in the past and have an opinion on its use.