Similar to reframing agile, I encounter a (perhaps surprising) number of experienced developers who outright deny that approaches like TDD or BDD have any value and it seems like we need a reframing here. But, in fact, I’ve found it’s more a case of getting people to uncomplicate the ideas.
Regarding this denial of any sort of value to TDD, I see this a lot with development managers, in particular. I’ve likewise seen testers, and developers, who dismiss out of hand any notion that BDD may have value. Or, at the very least, the value seen is considered not worth ultimate costs of implementing these practices. We’re even getting psychological about this, such as when it’s asked if TDD is a form of OCD.
Let’s Start with the Simplest Thing
When a lot of people hear “TDD” they just think: “Oh, first create a failing test.” And many of those same people say: “What a waste of time!” After all, they know the test is going to fail. So why not just write the code?
But notice a key implication here that often isn’t stated: being able to write a failing test implies that you know what failure looks like in the particular situation you’re dealing with. And not just “failure” in a singular sense, but failure modes based on the likely sensitivities of whatever you are dealing with. So whether you actually write the test or not, TDD as a practice is asking you to question whether you understand what “good” and “bad” look like in the context of your immediate work.
How is that not useful? Well, of course it is — when you frame it that way.
Let’s Go To Green
Okay, let’s go to the next step. After you have established a failing test, you can start working to “get it green,” as they say. Meaning you now write code that passes the test. Do you do this test-first? Well, as I talked about before, test-driven is not (necessarily) test-first. But, again, there’s an important point that often gets overlooked, which is the level of granularity.
If you start with a very high-level test — even if only a conceptual one — you may find that it helps to conceptually break that higher-level test up into multiple failing tests that deal with lower-level concerns. And that is what TDD should be speaking to: lower levels of abstraction, closer to the code.
“Okay, okay,” you might say, “I see where you’re going with this. But I can just think through the tests without actually ‘being driven’ by them or ‘writing them first’ or whatever. That’s just as much doing TDD as what you’re talking about.”
Is it? Well, maybe. But here’s the problem as I see it.
Expression Level Matters
If you are like most people on the planet, what you write is different from what you might verbally say. And what you say (and write) may be very different from what you think. And you think many thoughts as you go through from design to implementation. So just thinking it through is not the same thing as talking through. And neither are the same as writing it down.
Writing Intent Frames Pressure on Design
By writing the intent — key word there! — of our code before we write our code, it applies a pressure to the software design that prevents us from writing “just in case” code. And by this I mean code that we write just because we aren’t sure if there will be a problem or because we’re pretty sure we’re going to need something later, so we may as well write it now.
There is a lot of code that gets added in the name of “defensive programming” that simply doesn’t need to be there. This malformed defensive code is actually “offensive code”, in the sense that’s an offensive push to get stuff in there that we might need for a later time that may never come for features we may never need.
Focus on Intended Behavioral Change
If we reframe TDD and treat it as an approach, rather than a prescriptive technique, we can do something helpful. We can think of a test condition, we can prove that the condition isn’t supported currently, and then we construct the context so that the condition is supported. There’s a key point to this that, again, often gets overlooked: if we can’t think of a condition, then we then don’t add code. That’s one of the keys things about TDD.
Any sort of test-driven aspect should be there to put pressure on design. Such test-focused code is the writing of code to detect an intended behavioral change. If there is no intended behavior change, why are you writing new code? Keep in mind this is not about refactoring existing code; this is about writing new, untested code. Even more importantly this is about being able to look for unintended behavioral change. Do you see why?
When you approach it all this way, it means that failing tests are showing you behavior that was not intended. You will want to find out why that behavior was put in place.
BDD Is An Abstraction of TDD
So here’s how I see it. The key value of TDD is that at each step of the way, you have demonstrably relevant working software as well as an itemized set of what we can call “executable specifications” that illustrate aspects of behavior. And we do this at the appropriate level of abstraction. Which takes us to BDD.
BDD is really just the addition of business concerns to the technical concerns that we deal with in TDD. BDD wasn’t a reaction to TDD, as is often stated. BDD was simply an approach that let us move up the abstraction chain as people became more comfortable with TDD.
Simply put, BDD is about writing those conditions we talked about in the context of scenarios such that they will tell us the kind of behavior change they affect. A good barometer for adding a scenario might be asking if the scenario you are writing would be worth explaining to a business stakeholder. And you can frame that by asking what value it provides them. What aspect of the overall user experience is being captured in that scenario?
Broaden the Idea of Conversations
But here’s really the key reframing that I think needs to happen. TDD and BDD need to be seen as conversations. They are both conversations, in a sense, between yourself and the system. Here the “system” is what you are building and the environment you are building it in. The longer you go without “talking” to the system, the more likely you are to stray from the path of getting things working as simply as possible.
And note that “talking to the system” means you have to have a system built up enough that you can talk to it. That, folks, is really the key aspect of reframing Agile from so-called Waterfall. It’s a means of being able to have more conversations, sooner, so that we can make better decisions as quickly as we figure out new information about what value we want to provide and how we can best provide it.
That’s it! That’s all I like to talk about when I talk with folks about “being agile”, “shifting left”, “adopting TDD” “being a BDD shop”, whatever.
I firmly believe that we are still, as I wrote about in 2011, defocusing our development and testing practices. Let’s stop that.
Tests, in both a TDD and BDD context, are the first consumers of our software. They put pressure on our design thinking. And it is our design thinking, at the appropriate levels of abstraction, that ultimately, become the software we provide that gives our users value-adding experiences.
Let’s not make it any more complicated than that.