Make it Testable…No Matter how Painful it is


testLately I have been working on a rather large application; you know…the kind that replaces 15 year old software written in a language deader than Atakapa with a code structure that is strikingly similar to the Wednesday Night Special at Notini’s Italian Eatery (oh I miss that place sometimes).  To top it all off, the system has to be replaced in one fell swoop…running parallel or replacing pieces at a time are not even an option.

Anyway, I look at the situation we have gotten into by having this problem, and frankly, it is like the tech version of ‘Scared Straight’.  The paranoia of such a far reaching application within the enterprise that absolutely has to deploy successfully and also be very flexible to meet the needs of the future have driven me to the point of near madness in trying to make sure this thing is bulletproof, enter Test Driven Development.

Side Note: I would be remiss if I didn’t give a shout out to Chris Hartjes for being such an outspoken advocate of unit testing.  It has been very helpful and insightful.  

Today, I found myself in that all too familiar situation.  While working on a method that used three different objects plus two more dependency objects that were injected into the constructor, I inadvertently took the easy way out….and it looked basically like this:

The above is a highly simplified representation, but the point is that without really thinking about unit testing while in the heat of battle on this particular method, I had created a nightmare of dependencies and tightly coupled code.  Even with that, I was able to play around and hack together some testing code to get it tested.  So all is well that ends well, right?  NO!  The problem came when trying to write tests for another related class, and then the tight coupling came back to haunt me.  My eternal bliss of having gotten this method working lasted all of about 2 hours.  Basically the wheels fell off when I wrote some mock classes, and then kept getting “cannot Redeclare class” errors.  So it was either hack around it…again…or tear it apart and do it better:

So, the code does exactly the same thing, but some of the lines have essentially been moved out of the method.  Why?  The short answer is, by doing it this way, I can isolate the method completely, use mock objects for the constructor dependencies as well as   ObjectA  and ObjectB , and then testing becomes very easy.  That is the key point to this post: what you may often find is that as you focus on making code testable, portions of your code become simpler, and isolating potential problems becomes much easier.

What’s the downside?  Time, if you really can consider that a downside.  Until you are fully in the mindset of writing code that is testable, and then the next level, code that is easily testable, you are going to write perfectly useful code that you have to refactor…or maybe even trash and start over.  I already know what you are saying:  ”In the real world there are deadlines, and I do not have time or the budget to write tests, much less redo working code just because it isn’t testable”.   I get it, and I have been under those deadlines too.  Now, when someone pressures me with a deadline that doesn’t allow time for testing, I just say: “Remember project ABC, the one we ended up spending a month debugging and then screwing around with for another 2 months after deployment during which time we lost the productivity of 25 very frustrated people?”  Oddly enough, the conversation always ends right there, and I am right back to writing testable code.

For me, writing custom apps in an enterprise environment is not about rapid deployment and looking like a hero.  It is about deploying software with a design life of 7-10 years, because the change management involved in deployment is not something you want be be doing over and over again.  Testable code with 100% coverage of unit tests, well developed integration testing, and prolific use of tools like PHPUnit & Selenium are part of the development culture because while speed is important, durability is even more critical to business.  We have to be able to make changes as needs evolve, and we need to be able to do that without fear of blowing up the existing application.  We simply choose to invest our time in the beginning of the project rather than the entire lifespan of its existence.

All the best -

-Andy

, , , ,

  1. #1 by blacksonic on April 5, 2013 - 5:43 am

    Am I right that at first You just started refactoring before writing a single test, and after started to tested the code examples at the beginning of the article?

    • #2 by Andy (@phpAndy) on April 5, 2013 - 7:48 am

      Actually, the method in the first example was fully tested. The problem came up with what I had to do to write the test…which was very hacky to begin with. Then when I moved on to the next part of the app, which used some of the same objects, my hacky testing strategy caught up with me and started causing the test suite to fail in other places because of collisions with namespacing, etc. Basically I had set up some mocks which were actual classes, and it just created a cascading mess. Rather than keep hacking around it, I decided to drop back and clean up the root mess, which would make all of the unit testing a whole lot easier.

Comments are closed.