Continuing last night’s wobbly muse on graceful and awkward collaborators…
In the light of day, I think I see four possible responses to the situation when your new code depends on an awkward collaborator or collaboration:
1) Ignore it (possibly just for now). Write your test anyway and suffer the expense. This is a legitimate judgment, tho one the hardcore among us have to be dragged kicking and screaming towards.
2) Don’t automate a test for it. Again, entirely legitimate, and actually more common than just ignoring it for old hands, tho less so for noobs. Use old-fashioned GAK testing instead.
3) Ee-arrange around it. Change your code so that the part you most want to test no longer needs the awkward collaborator. This is bog-standard hardcore TDD, extremely common.
4) Isolate and fake it. Use one of the several means to “for the purpose of testing”, not use the actual awkward collaboration, but some cheap simulacrum of it. Also extremely common — a lot of old hands might say it’s overused and prefer the third choice.
The third and fourth choices “re-arrange” and “fake” are by far the two choices most TDD’ers prefer. But I don’t know of anyone who never ever responds with “suffer” or “GAK test”. (GAK = geek at keyboard. It’s a geepawism to describe what most non-TDD’ers do to satisfy themselves that the coder and the code say the same thing.)
I want to reach back to the premises to show they relate to this.
The whole conversation is predicated on the money premise. If TDD were about intellectual purity and not shipping more value faster, we would always choose to ignore the awkwardness and suffer the owwie. Many new-to-TDD teams do just that. What then happens, sooner or later, is that the cost of doing the tests eventually outweighs their benefit. the team simply stops doing TDD. The process of stopping is spread out over a long painful time, and it tends to happen more or less insensibly. It begins when we start honoring team agreements around testing “chiefly in the breach”. The chaining premise is invoked by the “isolate and fake” choice and maybe partly by the “rearrange” choice. we are testing part of our system and agreeing to be satisfied that we’ve done enough to be “sure”. Of course, the choice itself has judgment premise written all over it. it takes a human using intuition, experience, technique, theory, the whole vague schmoosh of non-computable consciousness to see and decide on any of these choices.
And finally, re-arrangement suggests the steering premise, and if you’ve done it enough times it leads to pre-arrangement, which is the center of that premise. The reason the steering premise is so important is because it encapsulates and crystallizes so much TDD experience. We can eliminate or mitigate an enormous amount of awkwardness in advance, if only we’re alert to it and willing to address it.
And the morals of the story are . . . 1) Don’t start a muse too late at night. 2) Awkward collaboration is normal in real-world TDD. 3) Notice awkward collaboration and choose to do something about it.
Thanks, and have a pleasant day!
Software Programs can be understood as (potentially huge) orchestras playing in concert.
Depending on your level of abstraction, you might imagine systems, subsystems, layers, packages, objects, or even functions as the individual players.
(Aside: Folks often make major distinctions in these abstractions, but to my touch, they feel all the same thing, just with ever larger labels on ever subsets. and at bottom, 100% of it is Von Neumann architecture code.)
For the purposes of this conversation, I’ll be thinking and using classes and objects, as that is the main level at which daily geekery is conducted in the modern synthesis. But remember, the players could be any of these.
The players collaborate to perform their function. They are collaborators. We often speak of roles or responsibilities – sometimes very formally – and that language is pointing at the same thing. Parts of programs work together: They collaborate. I pretty much always use TDD for anything bigger than the very rare “three hours, ship it, never look at it again” thing. I won’t rehearse the reasons for this, but just offer my official view: It lets me ship more value faster when I work that way.
Anyway, when TDD’ing, all collaborators are decidedly not created equal, and they’re unequal in a peculiar way. Let me throw a strange pair of terms at you: “graceful” and “awkward”. It’s not an all-or-none thing – but one of continuum, with some collaborators being very graceful and some being very awkward, and plenty in between, and some awkward sometimes and not others. TDD works for me because the tests are cheap. We’ve used this word before, and I used the phrase “easy to”. Easy to write, to read, to scan, to run, to change, to collect, to debug.
The idea is really important, “cheapness”, for a very straightforward and human reason: when things aren’t cheap, we don’t do as much of them. If the things have substantial extrinsic value, not doing as much of them is A Bad Thing[tm]. When I’m adding new functionality, my new stuff is usually merrily collaborating away, with my code, her code, library code, framework code, etc. etc. A graceful collaborator is one that makes or keeps my testing of the new functionality cheap. An awkward collaborator is one that prevents my testing from being cheap. (Remember as we go, continuum not binary value.)
The archetypal graceful collaborator in most modern OO languages is the string class. Many TDD’ers write their very first tests on code that manipulates strings. And the tests are chock-a-block with assertions that two strings do or don’t match. Awkward collaborators aren’t always the same from one programming environment to another. In java, the simplest awkward is usually the File class. In other worlds it might be different. A sampling of common awkwards: A ticks-since-1970 clock. A physical file, a relational database. Fronts for external devices, like screens or printers. Fronts for other complex programs, servers or report-writers.
What makes graceful “graceful” and awkward “awkward”? Well. Anything that makes me not want to use a test to validate that my code does what I meant it to contributes to awkwardness. And graceful is the absence of any of those things. Awkwardness can be in runtime, it can be in setup cost, it can be in difficulty obtaining the output data, it can be almost anywhere. In our material at Industrial Logic (cites at end), we just say “if it makes you not want to test it, call it awkward and do something about it.”
Strings are relatively graceful, because they require one-liner setup, one-liner probe, they have no externalities, they run fast, and for all but the noobs, their API is incredibly well-understood.
(Note: Don’t make the mistake of thinking that this is because strings are simple or have no dependencies. try learning every single corner of, say, c++ std::string. pro-tip: bring food, as you won’t be done before lunch — next week.)
A substantial part of doing TDD in the real-world hinges on this awkward/graceful distinction. In fact, the reason so many toy TDD exercises are in fact toys, is because they include few or no awkwards, where real-world TDD is replete with them. How one handles awkward collaboration both potential and pre-made, is at the heart of successfully using TDD to ship more value faster.
There are several different ways to address awkward collaboration, and no single one ring to rule all awkwards. About all I can do is offer some generic advice, for the moment:
1) We struggle mightily to keep the current class — the new functionality — from becoming the newest awkward collaborator in our collection.
2) We can very often divide the work our code does into an awkward part and a graceful part. Sometimes that’s as easy as splitting one method into two. The first method turns awkward into graceful, the second method takes graceful.
3) We nearly always use interfaces when an implementation is inherently awkward. Big girls and boys supply a graceful (if usually incomplete or simplified) variant of an awkward FOR NO OTHER REASON than to keep from passing on the pain.
4) Alternate constructors — one taking an awkward, one taking a graceful, are extremely common.
5) Indirection is your friend in that 95% of your job that has almost no impact on program performance.
6) The most important part of your code is usually not the bringing together of the data, but the processing of it. If we intermingle those modes, we often create permanent awkwardness.
7) Often, the many paths through my code all have a prolog or epilog that’s awkward, with pure grace in the middle. If I tie those into the code in a non-separable way, I’m locking everyone into my awkwardness for all time.
8) The smaller an awkward’s interface, the cheaper it is to fake. (Faking has lots of names, most commonly mocking, a misuse of that term’s origin and intent. It is making an artificial “test only” graceful from some inherently awkward thing.)
Anyway, it’s getting on toward bedtime, and that list is weird and disjoint, so I’m going to leave it for now. Maybe just start here: Learn to recognize the feeling that you don’t want to roll a test because it’s not cheap enough to do so. Think about what could be changed — your code or others — that might ease that pain.
Five Underplayed Premises Of Test-Driven Development (Transcript)
Hey, it’s GeePaw! I’m here to tell you today about five underplayed premises of Test-Driven Development. These premises form the kind of fundament under which almost all TDD proceeds. And when I say that I’m a TDDer, I almost always mean I am operating inside the little ring formed by these five test-driven development premises. Let’s check them out.
We’re In This For The Money
The first premise of TDD is what we call the money premise, and it goes like this. We’re in this for the money. I know, that seems like a strange thing to say, right? Well, what does it mean? We make money in software development through one means exactly, and just the one. And that is shipping more value faster. That’s how we do it. That’s where the money is in software.
We TDD because test-driven development is the best way we’ve devised so far to actually do that. TDD is about more value faster, and it’s not about anything else. Well, as soon as I say that, of course, I have to talk about some of the things it’s not about because they can be really confusing. And there’s a lot of misinformation out there on the internet.
The first thing TDD is not about is this. TDD is not about good citizenship. You are not immoral if you don’t TDD. You’re not not a good looker forwarder or a protector of the future. It’s not about citizenship. TDD isn’t even about raising the quality of your code. Now TDD very often does increase the quality of teams that aren’t doing it to begin with, but that’s actually neither here nor there, because TDD isn’t about that. TDD is about more value faster.
The other thing it’s not about? It’s not about art and it’s not about craftsmanship. It’s not about the excellence of our high standards. The reason we test drive is by test driving, we go faster. We put more value into the field faster than if we didn’t do TDD. And that is the money premise. We’re in this for the money.
We Happily Rely On Individual Judgment
The second premise of TDD is the judgment premise, and it says we rely every day, all the time, on individual human judgment. You know, there’s a lot of folks out there who might try to convince you that test-driven development is a kind of an algorithm for coding. You just follow these steps in this order and you will get TDD.
It’s not an algorithm for coding because there isn’t an algorithm for coding. All of those steps and pieces and parts of TDD, in order to choose which one to apply when, I am constantly making individual human judgments. And just for the record, we’re pretty happy that there isn’t an algorithm for turning human words into code. That’s what we do for a living, right? We turn human words into actual running programs.
The day after there becomes an algorithm for doing that, me and you and everybody in this trade is going to be looking for jobs on the night shift at 7-Eleven, and a week after that we’re all going to be hiding in the rubble from Skynet.
So it’s actually a good thing that there’s no algorithm for code. But this premise tries to highlight the extent to which you are going to be required– if you’re doing TDD– you’re going to be required to make active, individualized judgments. The judgment premise says we are absolutely, routinely, every day, all the time happily reliant on individual humans using their individual best judgment to guide us through the process.
Internal Quality And Productivity Correlate Directly
Next up we have the correlation premise. The correlation premise says that internal quality and productivity are correlated. They go up together and they go down together. They have a direct relationship. Now to understand that, you need to know a little bit about what I mean when I say internal quality. Internal versus external.
External quality is anything a user of your system could tell you about that system. Is it slow or is it fast? Does it handle all the cases correctly or only some of them? Does it stay up and running the whole time no matter what? These are all characteristics of external quality.
Internal quality, on the other hand, is things that you could only tell by studying the code. Internal quality is stuff like is the code scannable, readable, the factors that make it easy to change. Is it well-factored, broken into chunks that we can manage and change independently of each other? Is it well-tested? These are the sorts of things that go into making internal quality.
To circle back around again, you can trade external quality for productivity. In other words, if you don’t care that the screen runs kind of slow in this particular part of the program, then I can get done faster, which means I can produce more because I can spend more time on other things. And so on and so forth.
Internal quality doesn’t work that way. Why? Because the third most important factor in my daily output– given that the first two factors are one, how skilled am I, and two, how hard is the domain– the third most important factor, where do I start? And where do I start incorporates all those things we call internal quality.
So the correlation premise is very clear. It says that internal quality and productivity are correlated. They go up together and, sadly, they go down together. And that is the premise.
Test A Chain By Testing Its Links
The fourth premise is the premise we call the chaining premise, and it goes like this. The way to test a chain is to test each individual link in that chain. How does that work? Well, the idea is this. Our programs are always built out of smaller pieces and mid-sized pieces and then larger pieces. And there’s a chain. We call it a dependency chain.
Well, the chain premise is telling us that we write tests such that mostly what they concentrate on is individual connections in that dependency chain. So if I have a bunch of things, A, B, C, D, and E, where A calls B, B calls C, and so on, what I focus on first is how to write tests that can prove that A works assuming B works, and B works assuming C works, and so on and so forth until I’ve covered the entire chain. When I do that, I get the cheapest test that will cover the most ground for me. And that is the chain premise.
Tests & Testability Help Steer Design
Last but by no means least, we have the steering premise. The steering premise says that when we steer the development of our project all the way through, it tests and testability are first class participants in that process. So when we build new software, there are lots of different factors that we’re constantly balancing as we build it. On the one hand, you have things like the market and our expectations about where the market is going to go. And on the other hand, you have things about the actual technical capabilities of the particular platform that we’re running on.
| the middle we have things like that the capabilities of our geeks, things like the capability of our interpreters of the market to actually express those needs to us effectively, and so on and so forth. A lot of factors in play when we build new software.
The steerability principle says we use tests and testability as one of those factors all the way through development, just as we do the other factors. In other words, we are constantly considering questions of how am I going to test this, and how have I tested it so far? All the way through, from the very first line of code we write to the very last line of code that we write. And that is the steerability premise.
So we have these five premises, right? The money premise, the judgment premise, the correlation premise, the chaining premise, and the steering premise. Why did I call them underplayed premises at the beginning? Well, there’s a reason for that. It’s because when you’re outside TDD, those premises are arguable, debatable, wranglable at length. And in fact, we all can do it.
But inside TDD, they’re almost invisible to us. They’re the air we breathe. So when you go out there on the internet and you start studying TDD, you know, you’re studying people who have already stood inside those five premises. As a result, they hardly see them anymore. That means they don’t pay a lot of attention to explaining to them.
So when you get out there and you start looking into TDD, yes, by all means, pay attention to the techniques used by the people who are inside the ring of those premises. But remember, the premises are really what are binding us into this test-driven approach in the first place.
So now you’ve heard the five underplayed premises of test driven development. And I hope as you go out there on the net, you’ll bear them in mind.
I’m GeePaw, And I’m done. Thanks.