May 17, 2013

...Learn TDD with Codemanship

Straw Man TDD

A lot of the criticisms of Test-driven Develoment I hear are really attacks on a mythical version of TDD that no right-minded advocate ever put forward.

Nevertheless, being a TDD trainer and coach, I do still devote time to answering these straw man criticisms and objections. I thought it would be useful to collect some of the most common misconceptions in one place that I can point people to when I'm just too tired and/or drunk to answer them any more.

1. TDD means not doing any up-front thinking about design

Nobody has ever suggested this. It would be madness. Read books like Extreme Programming Explained again. You'll see sketches. You'll see CRC cards. You'll even see UML. (Gasp!)

The question really is about how much up-front design is sufficient. And the somewhat glib answer is "just enough". I tend to qualify that as "just enough to know what tests you need to pass". So, if your approach is focused on roles, responsibilities and interactions, then I'd want to have a high-level idea of what those are before diving in to code. If it's more an algorithmic focus, I'd want to have a test list that can act as a roadmap for key examples that - taken together - explain the algorithm. And so on.

I'd stop at the point where I'm asking questions that are best answered in code (e.g., is this an interface? Should this method be exposed? etc) Code is for details.

2. TDD takes significantly longer because you write twice as much code

Once you've got the hang of TDD - and that can take months of practice - we find it doesn't take significantly longer. Mostly because the bulk of our time isn't spent typing, it's spent thinking and, when we don't take care, fixing problems. Fixing problems, we find, generally takes more time than avoiding them. So much so, in fact, that working in the very short feedback loops of TDD and testing thoroughly as we go can turn out to be a way of saving time.

Most developers and teams who report a loss of productivity when they try TDD are actually reporting the learning curve. Which can be steep. This is why it can make good commercial sense to seek help in those early stages from someone who's been there, done that and got the t-shirt.

3. TDD leads to mountains of test code that make it harder to change your source code

There are three key steps in TDD, but most developers miss out or skimp on the third one - refactoring. So, when they report that they tried TDD for a few months, but found after a while that they couldn't change their source code without breaking loads of unit tests, I'm inclined to believe that this is what's really happened.

Test code is source code. If the test code is difficult to change, your code is difficult to change. So we must apply as much effort to the maintainability of test code as to the code it's testing. It must be easy to read and understand. It must be as simple as we can make it. It must be low in duplication. And, very importantly, it must be loosely coupled to the interfaces of the objects it's testing.

Think of UI testing. Maybe we wrote thousands of lines of scripts that click buttons and populate text boxes and all that sort of thing, binding our UI tests very closely to the implementation of the UI itself. So if we want to change the UI design - and we will - a whole bunch of dependent tests break.

Better to refactor our UI test scripts so that interactions with the concrete UI are encapsulated in one place and invoked through meaningfully-named helper functions. so we can write tests scripts in the abstract (e.g., submitMortgageApplication() instead of submitButton.click() )

The same applies to unit tests. If we repeatedly invoke the same methods on an object in our tests, better to encapsulate those interactions behind abstract and meaningful interfaces so it all happens in one place only.

4. TDD does not guarantee bug-free code

This isn't a straw man, per se. But to say that "we don't bother doing X because X is not completely perfect" isn't much of an argument against doing X when no approach guarantees perfection. When people throw this one at me, I'm naturally keen to see their bug-free code.

Let's face it, the vast majority of teams who don't do TDD would benefit from doing something like TDD. They'd benefit from working towards more explicit, testable outcomes. They'd benefit from shorter and less subjective feedback loops. They'd benefit from continuous refactoring. They'd benefit from fast, cheap regression testing. Their software would be more reliable and easier to maintain, and - once they've worked their way up the learning curve - it won't cost them more to achieve those better results. There are, of course, other approaches than TDD that can achieve these things. But, by Jiminy, they don't half feel like TDD when you're doing them (which I have).

UPDATE

5. you are not designing domain abstractions, you are designing tests.

This is a new addition to the fold, courtesy of some chap on That Twitter who obviously thinks I don't know one end of a domain model from a horse's backside.

Now, I've spent a fair chunk of my career modeling businesses - back in the good old days of "enterprise architecture", when that was where the big bucks were. So I do know a thing or two about this.

What I know is that those domain abstractions have to come from somewhere. How do we know we need a customer and that customer might have both a billing address and a shipping address, which my be the same address, and that customer may be a person or a company?

We know it because we see examples that require it to be so. If we don't see examples on which these generalisations are based, then our domain model is pure conjecture based on what we think the world our systems are modeling might look like (probably). I design software to be used, and it has been considered a good idea to drive the design from examples of usage for longer than I've been alive. Even when we're not designing software, but simply modeling the domain in order to understand it - perhaps to improve the way our business works - it workes best when we explore with examples and generalise as we go. In TDD, we call this "triangulation".

I will very often sketch out the concepts that play a part in a collection of scenarios - or examples - and create a generalised model that satisfies them all as a basis for the tests I'm about to write. (See Straw Man #1, of which this is just another example.)

When we generalise without exploring examples, we tend to find our domain models suffer from a smell we call "Speculative Generality". We can end up with unnecessarily complex models that often turn out not to be what's needed to satisfy the needs of end users.

Good user-centred software design is a process of discovery. We don't magic these abstractions and generalisations out of thin air. We discover the need for them. At it's very essence, that's what TDD is. I can't think of a single mainstream software development method of the last few decades that wasn't driven by usage scenarios or examples. There's a very good reason for that. To just go off and "model the domain" is a fool's errand. Model for a purpose, and that purpose comes first.

If you practice TDD, but don't think about the domain and the design up-front, then you're doing TDD wrong. It's highly recommended you think ahead. Just as long as you don't code ahead.

UPDATE #2

6. TDD doesn't work for the User Interface

Let's backtrack a little. Remember those good old days, about 10 minutes ago, when I told you that you should decouple your test code from the interfaces that it tests?

Those were the days. David Cameron was Prime Minister, and you could buy a pint of beer for under £4.

Anyhoo, it turns out - as if by magic - that it's not such a bad idea to decouple the logic of user interactions from the specific UI implementation in the architecture of your software. That is to say, you knobs and widgets in the UI should do - to use the scientific parlance - "f**k all" as regards the logic of your application.

The workflow of user interactions exists independent of whether that workflow is through a Java dekstop application or an iOS smartphone app.

A tiny slither of code is needed to glue the logical user experience to the physical user experience. If more than 5% of you code is dependent on the UI framework you're using, you're very probably doing it wrong.

And for that last 5%... well, you'd be surprised at how testable it really is. It may take some ingenuity, but it's often more do-able than you think.

Take web apps: all it takes is a fake HTTP context, and we've got ourselves 100% coverage. (Whatever that means.) Java Swing is equally get-at-able. As are .NET desktop GUIs. You just have to know where to stick your wotsit.

UPDATE #3

7. TDD Only Works On Simple, Toy Examples

I hear this surprisingly often. People say "It's all fine and dandy if you're doing FizzBuzz, but can you name a single real-world application of any appreciable size that has been written using TDD?"

I can think of a few dozen I've been directly involved with, plus hundreds more I've coached developers on. The FREESAT version of the BBC's iPlayer was originally written using TDD, for example. Significant parts of the Eclipse platform have been written using TDD. A fair amount of the new Visual Studio was written using TDD (it may surprise you to learn.) They use it at Facebook. They use it at Google. They use it at Amazon. Not across the board, of course. But when did any large software organisation apply any practice consistently?

I've personally used it on everything from slimline desktop gazelles to major server-side mammoths.

I've seen many clients successfully apply TDD on real projects of all shapes and sizes, from a few hundred lines of code to a few million.

To be honest, I'm not sure why - after all these years and all the war stories - people still think that TDD only exists in the laboratory. For sure, it's been documented many times in the wild. It may relate, in some way, to the first myth - that up-front thinking about design is not allowed in TDD. There's an accusation that TDD is somehow "anti-architecture". And, for certain, if you do no thinking up-front - and as you go along - about the design then that will be the case.

Also, too many developers have weak refactoring muscles. As the need for generalisations and abstractions emerges with each new test case, developers may lack the refactoring skills to reshape the design to be optimal for the code at that time. Instead, they let entropy do its worst.

Yes; it turns out that refactoring is really rather important after all.

Chris Pitts (@thirstybear) points out that TDD-ing FizzBuzz can reveal that there's more to a simple example like that than some programmers may have realised. I completely agree. Even an algorithm that simple can have several things go wrong in the implementation.

This is another important issue: TDD, done well, can help us to focus in on those "minor details" that we tend to skim over, or miss completely, when we test after the fact. be honest now, if you wrote the FizzBuzz code and then thought about writing some tests for it, would you have been as rigorous? Would you have picked out that many potential failing test cases? I've measured teams on this, and testing after the fact has a very noticeable tendency to miss more test cases.

When we discipline ourselves to only write production code when we have a failing test that requires it, we tend to end up with every inch of our code being meaningfully tested. Hence, TDD'd code tends to have much higher assurance, and be much more reliable. It's not the reason for doing TDD, but it's one heck of a fringe benefit.

UPDATE #4

8. I'm A Special Case. TDD Won't Work For Me

Got reminded of this one today from a Twitter debate I seemed to have got caught in the crossfire of where someone claimed "I'd do TDD, but I work on Android", or words to that effect.

The basic form of the argument goes "TDD might work for you, but my case is special, so it won't work for me."

It's either that it's not technically feasible in your "special case", or there would be no benefit to doing it in your "special case"

Let's face it, we're all special cases, right? We could all dismiss everything that seems to work for other developers by simply playing that card. "Oh, I'd use source control but I'm in Belgium", "I'd get everyone around a whiteboard and do a bit of brainstorming about the architecture, but we're at a high altitude here" or "TDD's fine if you're not working on Android like I am"

It's a given that not every practice is consistently applied, and not every practice makes sense in all situations. But some are very widely applicable. Name me a project or product you've worked on in your career where it would not have been a good idea to version-control the code?

TDD can work in a wide range of situations, if you know how. And the vast majority of teams would benefit from doing it, if they can.

That's not to say that TDD is the only way. There are other ways of producing clean, elegant and reliable code that's easier to change.

But I don't hear teams saying "We'd do TDD, but we're already practicing Design By Contract, so we don't really need to" or "We're running model checkers on our code, so TDD probably wouldn't add much value". (And, before anyone dares to say it, if you're doing BDD, you're doing a kind of TDD.)

Most teams would benefit from doing something like TDD in most situations. Undoubtedly there are special cases, like when the software is safety-critical, when TDD might not be sufficient (although there are those from that background who are thinking perhaps it might be, with some tweaks and an extra dollop of discipline), or when it might be overkill (though I've not come across many situations where even some loosely applied TDD wouldn;t have helped, even on spikes.)

I suspect there's an element of Special Pleading in many of these instances, and also people extrapolating from the Straw Men of TDD (e.g., "I've heard that it takes longer, and we haven't got the time", "I've heard that it's very difficult to do GUIs, and we have lots of GUIs" etc)


If you'd like to see a few other TDD myths debunked, while getting some hands-on practice in an intensive and fun workshop, join us in London on July 13th.






Posted 4 years, 8 months ago on May 17, 2013