Thursday 2 September 2010

Patterns and Anti-Patterns: Like Matter and Anti-Matter

Well, that's a few hours I'd like to have over again.

As both my regular readers know, I've long been a proponent of agile software development, particularly with respect to my current focus on Web development using PHP.

One tool that I, and frankly any PHP developer worth their salt, use is PHPUnit for unit testing, a central practice in what's called test-driven development or TDD. Essentially, TDD is just a bit of discipline that requires you to determine how you can prove that each new bit of code you write works properly — before writing that new code. By running the test before you write the new code, you can prove that the test fails... so that, when you write only the new code you intend, a passing test indicates that you've (quite likely) got it right.

At one point, I had a class I was writing (called Table) and its associated test class (TableTest). Once I got started, I could see that I would be writing a rather large series of tests in TableTest. If they remained joined in a single class, they would quickly grow quite long and repetitive, as several tests would verify small but crucial variations on common themes. So, I decided to do the "proper" thing and decompose the test class into smaller, more focused pieces, and have a common "parent" class manage all the things that were shared or common between them. Again, as anyone who's developed software knows, this has been a standard practice for several decades; it's ordinarily the matter of a few minutes' thought about how to go about it, and then a relatively uneventful series of test/code/revise iterations to make it happen.

What happened this afternoon was not "ordinary." I made an initial rewrite of the existing test class, making a base (or "parent") class which did most of the housekeeping detail and left the new subclass (or "child" class) with just the tests that had already been written and proven to work. (That's the key point here; I knew the tests passed when I'd been using a single test class, and no changes whatever were made to the code being tested. It couldn't have found new ways to fail.)

Every single test produced an error. "OK," I thought, "let's make the simplest possible two-class test code and poke around with that." Ten minutes later, a simplified parent class and a child class with a single test were producing the same error.

The simplified parent class can be seen on this page, and the simplified child class here. Anybody who knows PHP will likely look at the code and ask, "what's so hard about that?" The answer is, nothing — as far as the code itself goes.

What's happening, as the updated comments on pastebin make clear, is that there is a name collision between the ''data'' item declared as part of my TableTest class and an item of the same name declared as part of the parent of that class, PHPUnit's PHPUnit_Framework_TestCase.

In many programming languages, conflicts like this are detected and at least warned about by the interpreter or compiler (the program responsible for turning your source code into something the computer can understand). PHP doesn't do this, at least not as of the current version. There are occasions when being able to "clobber" existing data is a desirable thing; the PHPUnit manual even documents instances where that behaviour is necessary to test certain types of code. (I'd seen that in the manual before; but the significance didn't immediately strike me today.)

This has inspired me to write up a standard issue-resolution procedure to add to my own personal Wiki documenting such things. It will probably make it into the book I'm writing, too. Basically, whenever I run into a problem like this with PHPUnit or any other similar interpreted-PHP tool, I'll write tests which do nothing more than define, write to and read from any data items that I define in the code that has problems. Had I done that in the beginning today, I would have saved myself quite a lot of time.

Namely, the three hours it did take me to solve the problem, and the hour I've spent here venting about it.

Thanks for your patience. I'll have another, more intelligent, post along shortly. (That "more intelligent" part shouldn't be too difficult now, should it?)

3 comments:

Jerng said...

Test driven development, is just a reformulation of critical-path management. :) Just playing with the language.

Unknown said...

I'd tend to disagree; yes, the motive and opportunity were the same, but method and philosophy differ more than sufficiently for them to be treated as separate techniques.

CPM, as I was taught it anyway, is an attempt to ensure through regular reviews and benchmarking that the project is making the most efficient use of resources practicable. CPM does not itself prefer a particular development process per se; in fact, every project I've seen that used CPM (back in the late '70s to late '80s) used what even for the time were considered very heavy, front-loaded processes that emphasised estimation and projection over actual data. This was one of the process misfeatures that gave rise to the Agile Manifesto.

TDD, like agile development in general, emphasises efficiency in that it is much harder to hide project failure in a test-driven project than in a waterfall one; there is less process to get in the way of measurable, deliverable work. By contrast, waterfall project failure can be hidden behind a blizzard of memos and buried under a mountain of milestones that tick boxes on a chart, but deliver little or no real value to the project customer.

And "value to the customer" delivered early, often and increasingly, is a defining characteristic of a successful agile project. Nobody is seriously suggesting that the mountain of paperwork done before deliverable artifacts can be completed will be eliminated entirely. Rather, it's transformed into several manageable molehills. More importantly, the means by which that information is collected, shared and used among the various stakeholders in an agile, TDD project is more likely to be timely, accurate and effective than in all but the most serendipitously fortunate of waterfall (including CPM) efforts.

What have I missed?

Anonymous said...

My answer to that last question would be me.

Hehehehe

Totally irrelevant. I know. Sorry. Just dropping by.