Jeff Carouth's blog Ramblings of a Web Application Developer

18Nov/092

Stubbing an Interface

One of the topics I talk about frequently is coding to an interface. There is an abundance of evidence why one might choose to do so but today I'd like to talk about one benefit in terms of unit testing—another topic I love to talk about. To illustrate the benefit I will use an example of having the data source coded against the interface thereby allowing the consumer of the data source to ignore the specifics behind how data is stored, e.g., in a relational database or in a key-value store, and concentrate solely on requesting the appropriate data. Before I get to the code, however, I think it's important that I define some of the scope of this post.

Goals of a Unit Test Suite

Other than exercising a substantial portion of your code a unit test suite must be capable of running quickly lest it be a burden on development. Having a burdensome, slow-running test suite will only encourage developers to ignore it and only run it when absolutely necessary. This, in turn, will make maintenance of the test suite more difficult and probably result in fragile or poor tests which ultimately will leave a bad taste in the developer's mouth about unit testing, and we wouldn't want that, now would we?

One sure-fire way to decrease the speed of a test suite is to make it rely on real-world data sources such as your relational database or even a web service. Imagine a test suite for an application that relies on Twitter's availability. I can assure you that I would not want to run that test suite. The fail whale is annoying enough on the web. But I digress. Being able to emulate a data storage source is paramount to the speed of a unit test. Stubbing is one technique employed to do such a thing.

A Data Source Interface

A basic data source needs the implement the standard CRUD methods and can be defined as follows:

interface ICrudDataSource
{
    public function create(array $data);

    public function read($id);

    public function update($id, array $data);

    public function delete($id);
}

If this were a real interface there would be PHPDoc blocks to indicate the behavior of the methods and parameter types, but for the sake of the example I omit them. Now we can implement this interface in our database table class and define the behavior for each of the methods in terms of how our chosen rdbms will understand them, e.g., create() is an INSERT statement, read() is a SELECT statement, so on and so forth.

We can then use this class in our unit tests and it will actually test that the database vendor correctly implemented the INSERT, SELECT, UPDATE, and DELETE statements and that we are calling them appropriately. While I do think the latter should be tested when we look at the database abstraction implementation, the former is not necessary and will only slow down tests that use the data source code but don't directly depend on it being a database, for example. In such a case a simple array-based storage mechanism will work.

Array-based Stub for Unit Testing

Thus, we implement the ICrudDataSource interface in a stub that is used for our unit tests that need to interact with the data storage but necessarily need to interact with the specific data storage used in production.

class UserDataStorageStub implements ICrudDataSource
{
    protected $_store;

    public function __construct()
    {
        $this->_store = array();
    }

    public function create(array $data)
    {
        if (!$this->exists($data['id'])) {
            $this->_store[$data['id']] = $data;
            return true;
        }

        return false;
    }

    public function read($id)
    {
        if ($this->exists($id)) {
            return $this->_store[$id];
        }

        return false;
    }

    public function update($id, array $data)
    {
        if (!$this->exists($id)) {
            return false;
        }

        $this->_store[$id] = $data;
    }

    public function delete($id)
    {
        unset($this->_store[$id]);
    }

    private function exists($id)
    {
        return array_key_exists($id, $this->_store);
    }
}

With this array-based implementation the unit tests will be fast regardless of the availability of the actual data source or the load on that data source. This stub, or a stub that extends this one, can also be easily pre-populated with test data to allow for testing with fixed data (although this can lead to very brittle tests if done lackadaisically).

by
5Nov/090

Maintainable Unit Tests with PHPUnit Revised

One Step Back

In my previous post I chose an example that did not illustrate my point. In this post I will address the issues with previous post, specifically those brought to light by Matthew Weier O'Phinney in his comment.

His first point is that changing the method signature on a constructor should and will break any client code, thus modifications will be necessary to the tests. I agree with this wholeheartedly and my choice of using a constructor was obviously flawed. Let's just chalk this one up to not having had my coffee as I typed that post…

His second point deals with using the tools available to you within the framework. Specifically he mentions the use of the setUp() method which executes prior to each test case and is normally used to establish required state, set up fixtures, etc. I already committed to the constructor example so I carried it through this segment of the code. It was, again, a bad decision.

So this post will serve as a replacement for the old post to better illustrate my point.

Maintainable Tests

Great unit testing is a learned skill. Unfortunately the motivation developers have for dabbling in writing unit tests for an application usually comes from someone else, e.g., his or her boss, a fellow team member, etc. This leads to writing tests just to have tests which, in my opinion at least, is no better than not having tests in the first place.

A non-maintainable code base is know to be a problem. The corollary, a non-maintainable test suite is a huge problem, is also true. If you cannot maintain your test suite, what is the point? Any time a unit test reaches beyond a single or violates the DRY principle a development team could find that the test suite is verging on a non-maintainable state.

Testing Units More Than Once

If a requirement or business rule is tested more than once in the test suite, as is the case in the following example, any changes become difficult to manage. In this example the business requirement that changed is that all cars start with five gallons of gas before they arrive at your showroom. Thus a brand new car, i.e., the default constructed car, will not have an empty gas tank and should be able to start—unless something else is wrong, but I am a programmer not a mechanic.

class CarTest extends PHPUnit_Framework_TestCase
{
    protected $_car;

    public function setUp()
    {
        $this->_car = new Car();
    }

    public function testGasolinePresentBeforeStarting()
    {
        $this->setExpectedException('GasTankEmptyException');
        $this->_car->start();
    }

    public function testCarStartsAfterFillup()
    {
        $fuelVessel = $this->getMock(
            'GasolineContainer',
            array('dispense')
        );
        //set expectation on fuelContainer to give 10
        //gallons of fuel when dispense is called

        try {
            $this->_car->start();
            $this->fail(
                'Empty gas tank but no exception caught'
            );
        } catch(GasTankEmptyException $e) {
            $this->_car->addGasoline($fuelVessel);
        }

        $this->_car->start();
        $this->assertTrue($this->_car->isRunning());
    }
}

This change is not necessarily a regression because code that used the Car object before should still work as intended the cars will just start from scratch with some gasoline. Since we changed this requirement both tests in the above code will fail. The first will fail because we assumed a brand new car object would have no gasoline in the tank. The same for the second test. The code repeats itself, and so does my explanation.

Avoiding the duplication in the second test by immediately filling up the car with gasoline solves the problem of having to dig through the unit test and makes the second test case a true unit test. As it stands above, it tests two units: 1) that a car with an empty tank throws a specific exception and 2) that after adding gasoline to the car it will start.

Using the Tools Correctly

I knew about setUp() and tearDown() but I was trying too hard to come up with a fancy solution to my problem. The problem, as it should have been explained previously, occurs when a test case has groups of tests that require different configurations of the UUT. For example, sticking with our Car class, we have several tests that require a stock instance of Car while a group of others needs a car with a special engine. We can accomplish this as follows:

class CarTest extends PHPUnit_Framework_TestCase
{
    protected $_car;

    public function setUp()
    {
        $this->_car = new Car();
    }

    public function testSomethingWithAStockCar()
    {
        //work
        $this->assert(…);
    }

    public function testSomethingElseWithStockCar()
    {
        //same as above
    }

    public function testCarWithDifferentEngine()
    {
        //mock/stub of engine: $engine
        $this->_car->setEngine($engine);
    }

    public function testCarWithDifferentEngineAgain()
    {
        //mock/stub of engine: $engine
        $this->_car->setEngine($engine);
    }
}

Assuming I abstract the creation of the mock engine into a helper method, I still have a lot of duplicated code and I still have to call the setEngine() method in each test. That's absurd. One solution is to create a new test case for the different configuration; I'll call it CarWithEngineBTest.

class CarWithEngineBTest extends PHPUnit_Framework_TestCase
{
    protected $_car;

    public function setUp()
    {
        $engine = $this->getMock('EngineB');
        //…configure mock object…
        $this->_car = new Car();
        $this->_car->setEngine($engine);
    }

    //test cases that need this configuration
}

And remove these specific tests from the previous test case. This probably isn't the only way to solve this issue, but it does seem better than my previous factory method "solution".

The example still isn't perfect, but I couldn't come up with a better one. You are more than welcomed to weigh in.

by
5Nov/09Off

Maintainable Unit Tests with PHPUnit

Better version of this post coming. Thanks to Matthew Weier O'Phinney's comment I see that this post did not convey the point in the manner I wanted, and I thought of a better solution to the problem. Thus I am closing comments on this post pending the new version. Read the modified version.

Great unit testing is a learned skill. Unfortunately the motivation developers have for dabbling in writing unit tests for an application usually comes from someone else, e.g., his or her boss, a fellow team member, etc. This leads to writing tests just to have tests which, in my opinion at least, is no better than not having tests in the first place.

A non-maintainable code base is know to be a problem. The corollary, a non-maintainable test suite is a huge problem, is also true. If you cannot maintain your test suite, what is the point?

Looking at an example, I frequently see test cases that have the UUT's constructor called at the beginning of each test. This works fine up until the requirements change and the constructor now requires an instance of IDataSource or an array instead of a string. Now each test must be touched, i.e., development time devoted to each test function, to implement this change.

class CarTest extends PHPUnit_Framework_TestCase
{
    public function testStartFailsIfGasTankEmpty()
    {
        $car = new Car();
        $this->setExpectedException('GasTankEmptyException');
        $car->start();
    }

    public function testSomethingElse()
    {
        $car = new Car();
        //assert something
    }

    //so on and so forth
}

Instead, if a developer uses a factory method within the test suite to return a default instance of the UUT, the change to the tests is only necessary in cases where the initialized state of the instance must change via the constructor, as follows.

class CarTest extends PHPUnit_Framework_TestCase
{
    public function testStartFailsIfGasTankEmpty()
    {
        $car = $this->Factory_Car();
        //…snip…
    }

    //…snip…

    private function Factory_Car()
    {
        return new Car();
    }
}

Okay, that works for a while. But what if in ninety-five percent of the tests you need to use the "default constructor"—in quotes because PHP classes can only have one constructor—but the other five percent need to pass in some configuration options. You have two choices:

  1. create a new factory method for each group of, e.g., Factory_Car and Factory_SuperFastCar, or
  2. allow the existing factory method to take arguments and pass them on to the constructor.

Which you choose depends on the unique situation of your test case. For this example I'll assume there are ten tests. Seven of the tests use a default Car object while the other three each need a unique Car instance, thus each passes a configuration argument to the constructor. To handle this situation the factory method will make use of the PHP functions func_get_args() and call_user_func_array().

public function Factory_Car()
{

    $car = new Car();
    if (func_num_args() > 0) {
        $args = func_get_args();
        call_user_func_array(array($car, '__construct'), $args);
    }

    return $car;
}

This factory method allows each test to retrieve a properly-configured instance—where configuration is done via the constructor—of the Car class with which to work without sacrificing maintainability. One caveat: the three test methods in the example will need to be modified if the constructor's signature changes, but, last time I checked, three is less than ten.

by
20Oct/097

The Modern PHP Workflow

This article was originally published in November 2008 by php|architect.

Over time we have all seen projects that become great success stories and our fair share of projects that become epic failures. Some of this can be blamed on the idea behind the project but I challenge you to consider that part of failure can be attributed to a lack of constant supervision of the project. This is not to say that any one person is in charge of ensuring a project is on target with the original goals, or even that a project manager is responsible for testing the project throughout development. Rather, I'd like you to consider the work flow used on those failed projects and compare it with a more modern approach to development. While this won't guarantee the success of every project, using proven tools and methods such as unit testing with PHPUnit, an automated build system such as Phing, and a continuous integration server such as Xinc will greatly improve the quality and timeliness of your future projects.

Preparations

In order to follow along with this article you will need to equip yourself with the proper tools. Luckily between pear and the pecl obtaining these tools is the matter of a few characters typed into the command line. Note that thorough installation instructions can be found on the websites for each of the tools which are http://www.phpunit.de for PHPUnit,  http://xinc.googlecode.com for Xinc, and http://phing.info for Phing.

The Foundation: PHPUnit

Personally before I began my journey into the world of “Agile” development practices, specifically unit testing, I was already familiar with the basic concepts behind why it is a good idea but I was a little intimidated by the seemingly steep learning curve. If you're anything like me you have thought about the amount of additional time your projects will take because of the added burden of writing tests, you think that testing is something reserved for the extra weeks you tacked on to the schedule for this project and comes in the flavor of “Try out this application and see if you can break it.” or worse yet you think that your code is so impressively impeccable that you could not possibly benefit from writing these silly little tests. I am here to tell you that I was wrong in all of those assumptions. It took a while for me to realize the benefit and I will not try to convince you that it took no time at all to adapt my thought process to testing, but I can tell you that as a result I am more confident in my code, my projects are easier to maintain, and my clients and employers are much more satisfied with my work.

The toughest part of getting started with unit testing is deciding where to being your journey of learning how to test your applications. To help you along we are going to work together to develop an application to track the whereabouts of various items needed for school as a collegiate student. For this purpose we are going to assume that our student needs to track his notebooks, his textbooks, his planner, and his laptop which can be in his desk or in his backpack. Over the course of this example I may introduce a few bugs--some purposely, some otherwise--if there is any doubt in your mind about the code, assume it is for example sake.

Coupled with unit testing the technique of test-driven development is thrown into the mix and we're going drive the development of our application with testing. The principle behind test-driven development (TDD) is that we first write tests that describe how our code functions, more specifically how it is used, and then we write the code that allows the tests to pass. This might seem counter-intuitive at first, but we write failing tests first then make the code conform to our tests thus making our tests the contract to which we design our code. Coding the opposite direction generally leads to bending test code to fit the workings of your code base which in turn will lead to massive refactoring and potential code hernias in the future. If you aren't familiar with the term “code hernia” it is essentially a fragment of code that is in place that may be difficult to understand, most likely causes awkward use of the code base or API, and is only explained by lengthy comment blocks. The maintenance nightmare associated with such awkward code is exactly the type of problem we hope to solve by unit testing our code.

by

Recent Comments

Categories

Recently Authored

Archives

Blogroll