Test-Driven PHP Development

phpOK, so I have a little open source project where we agreed to use PHP as the implementation language. I wanted to practice test-driven development with PHP, so I got out PHPUnit and started coding.

It feels like PHPUnit got some things wrong, but they were easily worked around. The PHPUnit documentation spends quite a bit of time talking about how to orchestrate dependencies between tests. They spend the time talking about this early in the documentation, like its some important thing you’re going to need for all your unit tests. I never want tests to depend on each other. Good unit tests stand alone. That PHPUnit provides infrastructure encouraging a bad habit and prominently features that infrastructure early in its documentation isn’t so encouraging. Still, I continued on, ignoring a feature I’m never going to use.

Eventually I ended up with common setup leading to duplicate code that I wanted to eliminate. I know PHPUnit provides a common setup/teardown mechanism as is common in unit testing frameworks, so I thought that would be just the thing. It turns out not to be as useful as you might think. In PHPUnit, you write a class deriving from PHPUnit_Framework_TestCase and name your tests as methods beginning with the word “test”. Simple enough, right? However, the setup/teardown mechanism only runs once per class and not once per test. Argh. In other test frameworks I’ve used (NUnit and Boost.Test), the setup/teardown mechanism runs once for each test. It doesn’t seem like PHPUnit got that one right either. I really want the Fresh Fixture pattern in all my tests and avoid the Shared Fixture pattern. It seems that PHPUnit provides infrastructure for Shared Fixture, but not Fresh Fixture, thereby encouraging use of the more problematic pattern.

Another thing I bumped into was that a PHP fatal error didn’t tell me what test caused the error. I am not sure if PHPUnit could deal with this or not, but if the PHP fatal error was inside a production method, I had to apply deductive reasoning to figure out which test was the culprit. That was slightly annoying. With a static language like C++, compilation and other syntax type errors are found immediately at compile time and I can fix them right there. In C#, NUnit’s test runner would always tell me which test contained the offending call. With PHPUnit it was more of a struggle sometimes.

Despite these problems, I’m thankful for PHPUnit. It was much easier to use an existing test framework than it would have been to bootstrap my own framework. I did have to spend a little time figuring out how to orchestrate all my tests to run as a group by creating my own test suite, but it wasn’t too difficult.

One thing that comes up when testing a PHP application is that you often want to validate the HTML generated by your PHP code. You can always build up a big string and return that string from a method and then use a string compare, but that can get quite tedious with elaborate HTML generation. I ended up using the output buffer mechanism in PHP to capture the output of print and echo into a variable which I could then use in an assertion. The pattern looks like this:

ob_start();
exerciseSUT();
$output = ob_get_contents();
ob_end_clean();
$this->assertEquals($expectedOutput, $output);

If I get more studly in my knowledge of PHP, I might figure out a way to eliminate the repetitious nature of using this pattern in every test, but so far I can live with it. This ended up being very handy because my test essentially captures the result of the PHP output mechanism, but in production code it just writes the output to the page. Of course, if you’re using a templating mechamism like Smarty, you probably wouldn’t care about this technique at all.

In this particular project, I was immitating an existing system, so I started in a rather interesting fashion. I would go to the existing system, perform some operation and snarf the generated HTML from that system. I saved this as my initial PHP file, which at first contains no PHP whatsoever. Then I would perform what amounts to an Extract Method refactoring by replacing a chunk of the HTML with a PHP code snippet.

For instance, suppose we start with the following HTML:

<html><head><title>List of Companies</title></head>
<body>
<h1>List of Companies</h1>
<ul>
<li>Data General</li>
<li>Digital Equipment Corporation</li>
<li>Hewlett-Packard</li>
</ul>
</body>
</html>

That list of companies really comes out of a database somewhere and we want to incrementally refactor this raw HTML into a PHP program that generates the list. So we start by extracting the raw HTML into a piece of PHP code:

<html><head><title>List of Companies</title></head>
<body>
<h1>List of Companies</h1>
<?php
    print "<ul>
<li>Data General</li>
<li>Digital Equipment Corporation</li>
<li>Hewlett-Packard</li>
</ul>\n";
?>
</body>
</html>

When we process the page through PHP, we should get exactly the same output as the raw HTML. Now that we have the output in a PHP statement, we can extract it onto a method on an object:

<html><head><title>List of Companies</title></head>
<body>
<h1>List of Companies</h1>
<?php
    class CompanyLister
    {
        public function renderList()
        {
            print "<ul>
<li>Data General</li>
<li>Digital Equipment Corporation</li>
<li>Hewlett-Packard</li>
</ul>\n";
        }
    }
 
    $lister = new CompanyLister();
    $lister->renderList();
?>
</body>
</html>

Next, we can move CompanyLister into its own PHP file and put a unit test around it to cover its existing behavior before we embark on further refactorings. Eventually we’d refactor CompanyLister to talk to a database to obtain the list of companies and render the formatted output.

I found this to be a rather interesting way to build a web application incrementally. I can easily see this being used in an organization that develops web applications by having a static HTML prototype that is literally refactored to become the web application.

2 Responses to “Test-Driven PHP Development”

  1. Álvaro G. Vicario Says:

    Test-driven development is indeed a great concept and the method you describe looks really good. However, having a fixed and definitive HTML template before starting coding is one of those luxuries you never have. Do you need to redo your tests every time the design or layout is tweaked? And, how do you test pseudo-random output you want to display like current date or time taken by the database query?

    (Nice article anyway—I didn’t mean to criticise it, I’m just curious about how to fit theory into practice.)

    Like

    • legalize Says:

      You have a good eye! Those are all excellent observations and are items that Zhon Johansen and I will be covering in detail next week at Agile Roots 2010 in our session “You Can Test Anything”. I’ll give you the short version now.

      The user interface is one of the “high churn” areas of any program, whether its a web application or a traditional desktop application. The best way to avoid having to constantly change your tests every time a little detail of the UI presentation changes is to decouple the application logic from the presentation. Couple your tests to the application logic and not to the presentation and then changes in presentation won’t break your unit tests—but you’ll still need to test presentation somehow. You could then decide whether or not you want automated tests for your presentation styling and how to write those tests. Ultimately everything needs to be tested, whether by automated or manual means. I like automated testing as much as possible.

      For pseudo-random outputs like current date or time, you decouple your code from those sources of randomness by putting an interface around them. For instance, the date() and time() functions in PHP return the current date or time. Instead of calling those functions directly, call them through an interface:

      <?php
          interface IDateTimeProvider
          {
              public function date($format);
              public function time();
          }
       
          class DateTimeProvider implements IDateTimeProvider
          {
              public function date($format)
              {
                  return date($format);
              }
              public function time()
              {
                  return time();
              }
          }
       
          class FakeDateTimeProvider implements IDateTimeProvider
          {
              public $dateCalled, $dateLastFormat, $dateFakeResult;
              public function date($format)
              {
                  $this->dateCalled = true;
                  $this->dateLastFormat = $format;
                  return $this->dateFakeResult;
              }
       
              public $timeCalled, $timeFakeResult;
              public function time()
              {
                  $this->timeCalled = true;
                  return $this->timeFakeResult;
              }
          }
       
          class DateFormatter
          {
              public function __construct(IDateTimeProvider $dater)
              {
                  $this->_dater = $dater;
              }
              private $_dater;
       
              public function renderTodaysDate()
              {
                  echo "<p>Today's date is ", $this->_dater->date("F j, Y, g:i a"), "</p>\n";
              }
          }
       
          $provider = new DateTimeProvider();
          $formatter = new DateFormatter($provider);
          $formatter->renderTodaysDate();
       
          $fakeProvider = new FakeDateTimeProvider();
          $fakeProvider->dateFakeResult = "March 10, 2001, 5:16 pm";
          $formatter = new DateFormatter($fakeProvider);
          $formatter->renderTodaysDate();
       
          // Gives the following output:
      ?>
      <p>Today's date is June 10, 2010, 10:05 am</p>
      <p>Today's date is March 10, 2001, 5:16 pm</p>

      Like


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: