
I remember reading Kent Beck’s Test-Driven Development when it first came out in 2002, while on an airplane to a client site. I was already sold on unit testing, and was excited to try this new approach.
Fast-forward almost two decades later, and I still love test-driving when I code – but I find I only do it roughly half of the time I’m programming – maybe even just a third.
It’s not because I’ve gotten so good at programming that I don’t need tests anymore! Or that I’ve found major problems with a test-driven approach. Often when I see criticisms of TDD, the criticisms are in fact just complaints about unit testing in general, and not specific to driving code from tests. Most such criticisms I find poorly considered in any case – often depending on an assumption that developers are being sloppy in some way. Such as: poorly written tests become brittle and a maintenance overhead over time.
The reasons why I shift into and out of a TDD gear have more to do with the type of code that I’m working on.
First, I often do not test-drive when I’m working on code that has little or no behavior. And I find that I often architect systems in such a way that there are entire layers that are insanely boring, at least from a programming logic sense. With an application that has a REST API, for example, almost always I have a layer of endpoint definitions. Each endpoint is a method, and the nature of the endpoint will either be given in annotations (if I’m using a JAX-RS library) or imperatively if I’m using a micro-framework (see example below).
Such methods are library-bound and typically one-liners for me. My goal is always to get out of that layer of library-dependent code, that’s all about HTTP, and into regular old code (POJO, POC#O, whatever), as quickly as possible. So they’re not a place for business behavior. They’re glue code to get me from one place (an HTTP request) to another (code that does interesting stuff) quickly.
// skill operations
app.post("/character/:name/incrementSkill/:skill-name",
ctx -> ctx.json(IncrementSkillCommand.increaseSkill(getCreator(ctx),
ctx.pathParam("skill-name"))));
No behavior to test – just delegation from an API endpoint. IncrementSkillCommand is likely a different story!
Since there is no interesting behavior, and what code is there is all up in the business of some third-party library, I don’t test drive. This layer may have an automated test against it at some later point – perhaps I start a container up and simulate some HTTP requests against the endpoints – but I definitely don’t do unit test-first.
The second primary case where I don’t test drive is a little less defensible than the first: when I’m wresting with technology.
Programmers wear different “hats” when they’re coding, even if they don’t realize it. There’s the “bug hunting” or “why doesn’t this work dangit” hat – if you’re in a debugger, you’re in this mode. There’s the “adding behavior” hat – a good time to be test-driving. There’s the “refactoring” hat – a good time to be relying on tests you wrote previously, but you probably aren’t writing new ones in this mode.
Then there’s the “how the heck do I achieve what I’m trying to achieve with this library or technology” hat. If I’m working on a web application, I’m in this mode very very often. How do I get these two elements to line up? How does a flex area work within a grid? How do I defeat the default browser behavior for this html element? How does the javascript splice method work? How do I interact with browser local storage?
I’m never in Stack Overflow more than when I’m working with HTML, CSS, and client-side javascript!
This means I’m just not capable of test-driving. I’m too distracted by constant learning or re-remembering of how the technology works. This type of work for me is less programming, and more jamming pieces of technology together to get them to behave with one another.
This is combined with the fact that most web application development environments these days “hot refresh” very easily and quickly. So literally looking at the user interface to see if it’s doing what I want now is almost like running my unit test against programming logic. One I’ve lined that thing up, or got my link looking the way I want, that bit of code is highly unlikely to change again. It’s technology, not logic. These aspects keep me from even thinking really about unit testing for this kind of work.
Occasionally, I get bitten. What I thought was going to be a very easy and uninteresting little javascript function to sort a list coming back from a REST API turns out not to be, and if I find myself in a browser debugger stepping through Javascript, I know I’d probably have been better off in a Javascript unit test.
I’m definitely not perfect! I don’t know that my reasons when I’m not test-driving are “correct” – probably not. But these tend to be the sorts of reasons when I walk the programming wire without a unit test net!