Book Review: “The Grumpy Programmer’s Guide To Building Testable Applications”


When most developers think about books on testing, they think about books that highlight things like “test driven development” or “how to build a test for X.” There are lots and lots of testing books out there that supposedly teach us how to write tests and reach milestones like “100% test coverage.” But at the risk of sounding risque, testing is very similar to sex: more people claim to do it than actually do, most people claim more experience with it than they actually have, and more people have advice on the subject than probably should.

This is not true of Chris Hartjes’ book, “The Grumpy Programmer’s Guide To Building Testable PHP Applications”. When I asked Chris if I could review his book, I expected a step-by-step guide to writing tests. What I got was a step-by-step guide to building an application that COULD be tested. There’s a big difference, and it’s important to understand the distinction.

Continue reading

Peer Review: Testable Code And Architecture

This entry is part of an ongoing series involving the review of a code sample and it’s refactoring. For the original code sample, see here.

Now that we’ve worked out the abstraction issues and the logic questions, we should take a moment to focus our attention on a few of the issues relating to the architecture and testability of the class we’ve worked out.

A couple of big architecture issues raise their heads early on. The first one is this set of code:

< ?php
// class_Twitter.php - NPC TWITTER AUTO-FEED

if (count(get_included_files()) < 2) {
    header("HTTP/1.1 301 Moved Permanently"); header("Location: /"); exit;

This raises a number of architectural issues that we should address. My recommendation is that we remove this code altogether. The first line, about error reporting, really should be set at the application level, rather than the script level. As for the redirect, this is intended to prevent the script from being called directly. However, it's a good idea to place classes like this outside the document root anyway, meaning that they would never be called directly. When including this file, there's the potential that you'll not have included enough files and you'll inadvertently redirect your user. So let's drop these lines altogether. This will improve reusability and improve the architecture.

There's another line of code that is particularly troubling:

&#91;sourcecode language="php"&#93;
$this->http->setHost($this->baseHost . "statuses/update.json?status=".urlencode(stripslashes( urldecode($message))));

It may not look obvious at first, but this code relies on the assumption that the magic_quotes_gpc directive is set to on.

For those who don’t know, magic_quotes_gpc automatically adds slashes to all GET, POST and COOKIE variables that come into an application. This deprecated feature represents a poor programming practice, and its use is discouraged. Though it remains on by default in PHP installations, it should be turned off altogether if at all possible. It’s slow, and potentially dangerous. Additionally, it will be removed in future versions, meaning that code relying upon magic_quotes_gpc will break in the future.

Now, oftentimes programmers don’t have direct access to the magic_quotes_gpc directive in php.ini, but it can be set on the htaccess level, if your hosting provider allows you to access PHP variables in this way.

I’m going to make the assumption that we’ve disabled magic_quotes_gpc, as is recommended by the PHP manual.

Another problem that we need to address is the fact that this object gets thrown away after it’s been used. We have the property $done, which gets set when a tweet is successful. Unless there’s a good reason for it, objects should never be designed this way. We’ll remove this code in the final draft (see below).

Moving on to testing, again we have to ask ourselves about abstraction. Where testing is concerned, it would be very easy to abstract the testing out of this object. Making use of things like the HTTP_Request2_Adapter_Mock class will help us to “test” the Twitter interface without actually posting a tweet. Unit testing software like PHPUnit will give us reports on code coverage.

If we opt to leave the test code in the object itself, it should be abstracted into its own method. But I don’t recommend this course at all, because testing should be conducted at the application level. Moving testing out of the class also allows us to remove references to the $done property, and remove the $you property as well as the test() method, the $test property, and a large bit of code from the tweet() method. All of this code will then not be loaded on each request of our class. Not a bad improvement.

Something else that we’ll do in order to improve testability is we will inject (rather than create) the HTTPRequest object into the class. This allows us to both inject an object that is a mock object, and it also allows us greater control over the environment when we do our testing.

It was pointed out by the blog PHP In Action that one of the things that we should have done first is write unit tests. I think that this is good advice for most applications. Obviously, the small size of this class make unit testing less critical, but it is a good practice to write unit tests first, and then refactor.

A good example of this is the work being done by web2project with regards to bug fixes. According to Keith Casey, one of the leads on the project, their rule now is that any time a bug is fixed, a unit test identifying the bug must be submitted along with the bug fix. This has allowed them to significantly improve their testing system, and it’s a great idea for existing projects that don’t have unit tests but want to implement them.

Testing is not to be underestimated. Testing allows you to refactor methods and the internal workings of a class, and still know that the results are right. It allows you to work on a very small part of an application without necessarily understanding the whole application, because you can run a unit test suite and see if your code works properly. And writing tests first, even on bug fixes, forces you to think about how you expect your code to work, and face the assumptions you’ve made about that process, as well.

We’ve made a good number of changes to the code, and we’ve made a vast number of improvements. Let’s take a look at what we’ve got:
Continue reading