We’ve all heard the meme by now: The Karen. Usually a blonde woman with an eccentric hairstyle that demands to see the manager when things do not go as they want them to. Personally I think the term is a bit sexist as I’ve met plenty of men who act this way too. Anyway, when you’re stuck behind such a person in line at a retail establishment, you suffer while they selfishly demand things until someone relents, and you have to wait until they get their way.
I don’t much care for those people. But when it comes to TDD, I want the tests I write to be just like that. I want them to be demanding, picky, and downright relentless. Let me explain why.
Let’s say you are not a TDD shop. You are tasked with this requirement: Every night at midnight, the transactions that have been recorded throughout the day (let’s say, they are financial in nature) must be committed to a service. Assuming we know what it means to “commit a transaction” this seems pretty simple, right? You’d create a job running on a timer, and when midnight comes you’d activate the code that commits the collection of transactions. Most developers could write that pretty quickly and move on. Let’s say you did that.
The next day you come in to find the business in an uproar. The transactions did not go through, and your customers have sustained significant financial damage as a result. There is liability, potential loss of market share, and damaged reputation. When you ask why this happened, they tell you that the commit process was “too late” and that the transactions were rejected as a result.
The problem is the notion of “happening at midnight”. That seems like a perfectly clear, simple requirement with little room for misinterpretation. Everyone knows what midnight it.
But nothing can happen “at midnight”. Midnight is an infinitely short period of time… the moment it becomes midnight, it is immediately “after midnight”. So what did the client actually want? Did they mean “by midnight”? If so, then you should have started the commit process prior to midnight so that it will compete before the deadline?
But how much earlier should you start? Is that based on the speed of the computer performing the action? Is there a time that is “too early” in terms of regulations or business rules? You now know there is a “too late”, so that seems believable.
Is it really “at” midnight, or “by” midnight, or what?
Now imagine you are a TDD team, and you received that same requirement in the same language. The first thing you must do is convert it into some kind of executable test, probably a unit test if you are a developer. When such a test is written, it must express three things:
- The condition of the system before the behavior is called for
- The event or action that triggers the behavior
- The way we can determine the success or failure of the behavior
TDD says “until you have these three things accomplished in a test, and have run that test and observed its failure, you may not begin the development work.” It will not budge, or yield, or get out of line until you do what it wants. It’s a Karen, or whatever the non-sexist term might be. TDD is a discipline, and it only works if you follow it diligently.
How will you create the environment that the behavior applies to? Do you really want to create a bunch of bogus transactions every time you need to run the test? How can you activate the commitment if it’s based on the system clock? Will you have to come in every night at midnight to conduct your test? How will you know if it worked? What might make it not work? What does “it worked” even mean?
In TDD, the team must know how to do all these things. Some of it involves technique (dependency injection, mocking, endo-testing, etc…), some of it requires that we ask questions that we might otherwise neglect to ask (like “how will I know if it worked?”) and some of it will challenge the design of the system we are planning. Should the “trigger at midnight” code be intertwined with the “commit the transactions” code? Probably not, but that would be an easy mistake to make. TDD won’t let you, because it will be too painful and arduous to write a test of such a thing.
Bad designs are hard to test. Is your design any good? If not, don’t you want to know?
It’s hard if not impossible to write a test for something you don’t understand. Do you really know enough to build this feature? Are there questions you should be asking?
TDD works because, among other things, it is demanding, picky, and downright relentless. It is that annoying customer that won’t go away until you address them, and thus it holds your feet to the fire in a way that makes you better at your job, and more valuable to your clients.
To do this, the team must be effectively trained. Just knowing “how to write a unit test” won’t do it, not even close. If you do not know how to control dependencies (like “time” in the example) or how to separate behaviors in your system without over-complicating its design, or what it means to completely express a requirement as a test, then you’ll struggle and probably give up. But if the team is empowered with this knowledge they can move swiftly, confidently, and aggressively to ensure that the organization they serve remains competitive in an ever-changing world. TDD makes you do it right, because doing it wrong simply won’t work.
And isn’t that great?