Scott Bellware [MVP]

Sponsors

The Lounge

Syndication

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Write Naive Test Code

Finding the design sweet spot in software is an essential part of Test-Driven Development.

If you're new to TDD, it might not be immediately clear why frameworks like NSpec point the way to more appropriate domain-specific languages for TDD than frameworks like NUnit.  The notion that Test-Driven Development is principally about designing software rather than about testing software probably doesn't quite make sense yet either.  Either way, continuing to practice will inevitably clear the fog and lead to breakthroughs.  I don;t know a single TDD practitioner for whom this hasn't been the case.

NUnit was - as was JUnit before it - a readily-available and low-tech solution to creating executable specifications.  Agilists tend to have great appreciations for low-tech solutions since "low-tech" typically implies "uncomplicated", or "simple".  Given a choice between two solutions that work equally well, the simpler of the two is often overwhelmingly the better choice.  This is a kind of Occam's Razor for software development.

Simple is the goal.  We want to arrive at simple solutions to complex problems rather than make complex problems even more complex by slathering them with complex code.  Quoting Ron Jeffries, "Simplicity is more complicated than you think. But it’s well worth it."  Similarly, Kent Beck said , "Clean code that works is out of reach of even the best programmers some of the time, and out of reach of most programmers most of the time."

Pursuing and achieving simplicity are at the heart of agile development's mission.

The strongest force acting in opposition to simplicity is a software developers' own tendency to arrive at a conclusion about how to implement a solution, and then proceeding to implement the solution without challenging any of these conclusion first.

Experienced agile practitioners use TDD to temper a psychological mechanism that - increasingly reinforced, unchallenged, and justified since youth - can keep us from seeing the simple solution that lays obscured beyond the obstruction of our assumptions.  TDD is a fail-safe mechanism that helps us insulate the software we develop from the detrimental effects of our own human egos.

You might even say that Ego-Driven Development is the polar antithesis of Test-Driven Development.  I'm not saying this simply to malign developers for having egos.  The ego is an important component of human psychology and an essential enabler of our ability to discriminate and ultimately to ensure survival.  But the unquestioned assumptions that it leads us to are far too often the basis of very smelly software, not to mention all kinds of social and spiritual mishaps.

Learning to make effective use of TDD means a whole heck of a lot more than merely forcing yourself to write test code before writing functional code.  It also means learning to write to right kind of test code - simple test code.  It means being constantly engaged in self-observation when making implementation decisions, and throwing a monkey wrench into your most natural inclination to believe in your own assertions about design as fed by your years of experience, your respected position in your community, and the adroit choices you've made in populating your software book shelf.

Here is one of the most important bits of TDD guidance that I constantly endeavor to put into practice: write naive test code.  Or better: write naive specification code, since in TDD, tests are specifications.  Said yet another way: write ideal specifications.

Imagine that you have the freedom to create the simplest, most uncomplicated, and easiest library to use.  Write some client code that shows how that simple library would be used in an ideal scenario.  If you write that client code in an NUnit (or other) test method, you've written naive test code.  And since you've written this specification before you've written your functional library code, you've also just done some Test-Driven Development.

Naive test code is a starting point in a process that leads almost invariantly to simple solutions.  By not starting at naive and simple specifications, you are very likely going to be led by the very nature of basic human intellectual devices to overly-complex conclusions - also known as accidental complexity.  Naive test code written before functional code in a test-first manner will typically force simple designs and clean code to emerge from the process of evolving the functional code to meet the simplicity of the client code that specifies it.

We always want to be asking ourselves, "is this the simplest way to solve this problem?"  You can't really know whether it is until you approach the solution micro-iteratively, adding micro-increments until the problem at hand has a solution.  Often you will find that the solution you end up with isn't one that you had any idea of when you started.

By using naive test code whose intension it is to cleanly and literately express a single design intension, we tame our you-know-what-would-be-cool predispositions, and retrain our intellects to pursue and discover simplicity.

TDD asks you to put your money where you mouth is in regards to your design assumptions, and provides you with a simple, low-tech approach that is guaranteed to surface simpler, cleaner designs while also providing you with a host of opportunities to learn about about the appropriate use of design patterns and to simply choose the best tools for the right jobs.


Posted Sat, Aug 26 2006 3:27 AM by ScottBellware

[Advertisement]