In case you haven't heard David Heinemerier Hansson wrote an article entitled 'TDD is dead. Long Live Testing'. There has been a lot of fallout over this, including some very good articles and an ongoing series of conversations between DHH, Kent Beck and Martin Fowler entitled Is TDD Dead?.
For me the latter has been disappointing thus far, mainly because the discussion seems to continually circle the same topics without ever properly defining terms or going into detailed examples. But it has reminded me of the weight we pile onto particular words and how their official, ‘industry standard’ definitions are always softened by the realities of day to day development.
Three terms in particular seem to stand out:
- Unit (as in Unit Test)
- Responsibility (as in SRP)
- Isolation (as in DI and Mocking)
Lets briefly look at each of them
Whats in a Unit?
Unit testing is frequently spoken of as testing at the lowest level of granularity, which is taken to mean a class in OO and a function in procedural languages. But take a look at the following:
public class Person {
//Constructors and methods...
private String name;
private Date dob;
}
char* func(int p1, int p2) {
char * buffer = new char[MAX];
convert(&p1);
convert(&p2);
sprintf(buffer, "Results are %d and %d", p1, p2);
return buffer;
}
Do we want to say that Person
cannot be unit tested because of its dependencies on java.lang.String
and java.util.Date
? Is func
unit testable simply by virtue of being a function? What about its dependencies on sprintf
and convert
? What about the ever mysterious MAX
?
In practice Unit Testing means ‘testing at the lowest level provided by the language without worrying too much about built in functions, core libraries, generated code and anything else we inherently trust’. Which is a fluid concept to put it mildly :-)
What are you Responsible for?
The Single Responsibility Principle is taken to mean that a class ‘should do one thing well’ or ‘have only one reason to change’. However the public methods of a class (its ‘interface’) are understood as its responsibilities (note the plural). Furthermore during refactoring we are encouraged to apply the SRP principle to individual methods. So a well written class simultaneously has one and many responsibilities.
The problem is that ‘responsibility’ is a highly divisible term. I am a responsible adult, and as such am responsible for locking up the house. This includes being responsible for closing windows, locking doors and checking the cooker is off. In OO terms it might look like this:
public class Adult {
public void lockUpHouse() {
closeWindows();
checkCooker();
lockDoors();
}
private void closeWindows() { ... }
private void checkCooker() { ... }
private void lockDoors() { ... }
}
At present we lack the typology to describe different layers and types of responsibility. MVC and its derivatives are one attempt but its only a beginning…
How Isolated Should You Be?
The debate over isolation (and hence Dependency Injection and Mocking) is a well worn one, and popped up again in the second episode of ‘Is TDD Dead?’. As is usually the case it was in the context of being ‘isolated from the GUI’ or ‘isolated from the Database’ – but these are very coarse examples of being isolated. Other alternatives might be:
- Isolation from an ‘Enterprise Architecture Team’ that keeps re-engineering the system every 18 months
- Isolation from the proliferation and every changing set of client-side JavaScript frameworks
- Isolation from the portion of the system the customer cant make up their mind about
- Isolation from the reinvention of GUI libraries (e.g. WinForms to WPF to Windows 8)
- Isolation from different NoSQL products and document repositories
- Isolation of provably thread-safe or secure code from the rest of the system
Isolation feeds back into the meta-meta-principle of ‘keep stuff that uses each other and changes at the same rate in the same place’ – which may be the only thing that is safe to say in the long run :-) Like determining responsibility it is a skill, which when done well leads to better design and more testable code but when done badly leads to code bloat, nested mocks and a proliferation of unnecessary layers. The excellent blog post by Gary Bernhardt makes this point well.
Conclusion
I think Inigo Montoya had it right, most of the time we don’t know the meaning of the words we use. Of course there is nothing wrong with an individual starting with a surface interpretation and then deepening their understanding – this is how we learn. But as a profession if we don’t get a handle on our terminology then we will keep chasing our tails without progressing. This may be why detailed debates on language design and OOP increasingly sound more like a linguistics conference than computer science…