A function assigns a dependant variable (the range) to an independent variable (the domain). Alternatively stated, a function will assign a result to an argument. And by that definition, we eliminate “void” methods and properties since these have neither range, nor domain. It is accepted though that these constructs can still alter the state of an object, but the focus of this post revolves on Functions and TDD.
This definition is useful since it also helps to describe bug resolution as a function, or more accurately, as an inverse function. An inverse function is determining the inputs based on the outputs. So when we’re resolving bugs, we’re inverting a particular function. So let’s look at releasing functionality in software…
We implement a function, with both range and domain. At some point in time, that function starts to behave “unexpectedly”, ie. it’s buggy. Functionally, either the range or the domain of the function has contradicted expectations. And the more granular the function, the greater the likelihood is that the domain introduced the bug and that the range is symptomatic. Of course, this can change as the implemented function increases in cyclomatic complexity. But back to TDD…
TDD recognized the domain-range behaviour of a function and attempted to introduce a mechanism whereby the domain-range relationship could be documented in an automated fashion. In doing so, a programmer could efficiently determine where the system changed expected behaviour as the domain and range of the various functions increased through integration. The programmer was then empowered to make empirical-based decisions, quickly, and in doing so progress (hopefully more rapidly) towards completing the various functions. Or at least, that’s the way i always understood it….
Then a school of thought emerges asserting that TDD is more about design, and not testing. And while it is acclaimed (and true, imho) that TDD will bring about a better design, it is still more about testing than it is about design. TDD documents what the expected inputs and outputs of any given function are, and the relationship between the range and domain. When it starts to focus on design, or is utilised with design as the primary focus, it’s no longer TDD, but should be rephrased: Perspective Driven Design, or User (as in caller) Driven Design. In which case, xUnit frameworks, tools and processes are inadequate. Why indeed would statements like Assert.AreEqual() be required to check wether the _design_ works? It’s not, and as such, TDD fundamentally remains, and should always remain, functionally oriented.
Along with TDD come some well understood concepts like defensive programming, as an example. DP has nothing to do with design, but everything to do with functionality. Of course, DP can come without TDD too 😉 But it still remains that TDD is about testing the function, not the form. And wether the form evolves into a good design or not, depends on the programmer. TDD won’t guarantee you a good design. You can still hack it. And it also won’t guarantee you good function either- you can easily write pretty bad tests.
Stick to proper design principles outside and beyond TDD and intelligently utilise a combination of experience, technology and common-sense to get it looking right. And use TDD, primarily, to get it acting right But try not confuse the two…