#NoTDD

Like the other #no<x> assertions – NoEstimates, NoBugs – I'm really not saying that you shouldn't do TDD. Well, maybe I am…

I was an early TDD advocate, as I really liked the way it helped me organize my thoughts, and I respected some of the other people who were early advocates – people like Ron Jeffries.

But looking back on the 15 years since I started with TDD, I have to say that it really did not live up to my expectations.

What I've seen is a consistent pattern of TDD working in a laboratory setting – developers are quick to pick up the workflow and can create working code and tests during classes/exercises/katas – and then failing in the real world.

My hypothesis for this is very simple. When you look at the TDD evangelists, all of them share something: they are all very good – probably even great – at design and refactoring. They see issues in existing code and they know how to transform the code so it doesn't have those issues, and specifically, they know how to separate concerns and reduce coupling.

If you have those skills, TDD works great; you see what the issues are as the arise and fix them incrementally, and your simple tests prove that your design has low coupling. And, you have the tests to lean on in the future.

A hypothetical

Let's take the TDD workflow and remove the third step – the refactoring step. What would we expect to happen?

Well, we would expect to end up with classes that have multiple concerns in them – because we didn't split them apart – and they would be inconvenient to test. We would need to write a lot of test code, and would need a lot of help to write the code, either creating many hand-written mocks or using mock libraries.

Which I submit is precisely the result that most developers get using TDD; the resulting code looks exactly like what we would expect if developers are skipping the third step. Which isn't really very surprising, given that the tests in most non-TDD code look the same way, and we know that most developers do not have great design/refactoring skills.

At this point we should ask ourselves, "are these developers getting a net positive benefit from using TDD?" Let's list the pros/cons:

Pros

We end up with tests that verify the behavior of the code and help prevent regressions

Cons

It takes us longer to write code using TDD

The tests get in the way. Because my design does not have low coupling, I end up with tests that also do not have low coupling. This means that if I change the behavior of how class <x> works, I often have to fix tests for other classes.

Because I don't have low coupling, I need to use mocks or other tests doubles often. Tests are good to the extent that the tests use the code in precisely the same way the real system uses the code. As soon as I introduce mocks, I now have a test that only works as long as that mock faithfully matches the behavior of the real system. If I have lots of mocks – and since I don't have low coupling, I need lots of mocks – then I'm going to have cases where the behavior does not match. This will either show up as a broken test, or a missed regression.

Design on the fly is a learned skill. If you don't have the refactoring skills to drive it, it is possible that the design you reach through TDD is going to be worse than if you spent 15 minutes doing up-front design.

I think that's a good summary of why many people have said, "TDD doesn't work"; if you don't have a design with low-coupling, you will end up with a bunch of tests that are expensive to maintain and may not guard against regression very well.

And to be fair, the level of design and refactoring skills required is relative to the state of the codebase you're working in. There are a lot of legacy codebases with truly monumental amounts of coupling in them and figuring out how to do anything safely is very difficult and complex.

TDD—, Refactoring++

Instead of spending time teaching people TDD, we should instead be spending time teaching them more about design and especially more about refactoring, because that is the important core skill. The ability to write and refactor code to a state with low coupling, well-separated concerns, and great names will be applicable in all the codebases that we work in.

That is not to say that I don't dust off Ncrunch now and then to either create a new set of functionality with great tests or to create tests around a concern as I do a refactoring, because I still do that often. I just don't think it's the right place to start from a educational or evangelical perspective.

Nice point. In my view I would prefer having TDD in place as a learning tool. Blindly using it without understanding the architectural benefits it provides would be unwise. But it puts some nice checks in place when we are learning while working on commercial/production code bases.

I respectfully disagree with the implication that unit testing your code is not worth the frustration. Maybe with a caveat that 100% unit test coverage is often overly restrictive. If you’re frustrated with unit tests, you’ve probably done something in a way that could be improved.

As far as mockathons… My approach is to mock just enough to get to atomic, isolated tests, and write multiple tests so that each one isolates every individual bit of logic. Mocks aren’t bad. *Lots* of mocks aren’t even bad, as long as you’re not mocking a positive assertion, and you’ve tested every bit of logic in isolation. 🙂

I had a discussion with the Arlo Belshee about testing production code, and we agreed that that there is code that is untestable and there is code that is aggressively untestable. I am not the best designer around, but I have worked with developers who spent 2 hours trying to break dependencies in a meaningful way (ie not “sprout method”) to make things testable, and the situation would have taken me a morning to work on and I only gave myself a 70/30 chance of coming up with a good way to make it testable.

At that point, I don’t think the effort is worth it; my experience is that the code is worse after the changes to make it testable when the changes are made by most developers.

“Maybe with a caveat that 100% unit test coverage is often overly restrictive.”

Many of the best who push for Unit testing or even TDD consider 100% coverage a code smell. Every acronym for a development model is an ideal that is not practical unless used in moderation. TDD is a great learning tool and forces you to think about things you never had to before, but it is not very practical. Use it, learn from it, move on.

TDD is just a tool. Not only should you use the correct tool for the job, but you may need to partially use many tools for different aspects.

These are exactly my thoughts!!! I know it is kind of blasphemy to say it. These days you get insulted if you say something like this. I’ve been in some projects that did not work well with TDD. The answer from the TDD-disciples were “you are not doing it right”. Ok, with this kind of argumentation you are always right 🙂 .

One of our TDD-followers wanted to show the others “the right way”. He created a small program using TDD and about 300 different tests. The program went to our quality assurance and they found 90 bugs in just some days. They were the typical UI bugs (pressing a “Run” button twice starting a process that should not work in parallell, users doing “unexpected” things, …). Fixing the first two bugs took three days because many tests had to be rewritten. As you can imagine we had to stop this nightmare.

Please, don’t answer with “he did it wrong”. He was one of the TDD “fans” that would show the others how to do it right. As Eric says it is more important to learn how to design and some people think that TDD will solve their design problems (“if I do TDD I am designing it right”).

I do understand the benefit of doing unit testing, but I think that sometimes people are too religious about axioms. “You must do SOLID”, “you must do TDD”, “Clean Code”, … There are enough valid points in all of them, but many developers these days make a war out of it (like many extreme religious people).

I agree that TDD without having a good design foundation can be like spinning your wheels in mud. However I think the answer is when you teach or mentor a jr engineer in the value of TDD you should also be mentoring in the design as well. It’s not a “instead of” but “along with”. In my experience the value of TDD and unit tests in general (if done correctly) is that it validates the design, pointing out the flaws and giving you the opportunity to correct the design before it’s too late.

I’ve had decent luck with that approach when I was the lead and I was able to instruct/mandate on an ongoing basis *and* I could keep the schedule pressure off. I’ve had very limited luck otherwise. Only the pair/TDD curious are willing to work with me that way, so my leverage is limited, and when I see the results when those people try to do TDD themselves, I’m not very excited. Assuming they can figure out how to write tests at all in our codebase. I’ve had better luck pairing with people on refactoring and working through different design approaches.

Pairing as much as possible is my first choice. I like the idea of katas – and paired katas are better – but I think you are limited by the total knowledge of the pair; if they aren’t very skilled their ability to find insight will be somewhat limited. I facilitated a code retreat last fall, and that was definitely a pattern.

I’ve been piloting a sort of guided instruction with a small group of engaged people at work, where we evaluate and discuss a specific part of code and a specific issue. I’ve seen some good results but the approach still needs a lot of tuning.

But, refactoring takes longer without tests.
Tests should have influenced the design.
No one ever said TDD precluded up front design or that it was even “Design on the fly.”
You get even higher coupling by not using TDD. TDD has driven me to think more about how I can loosely couple my code. That was one of the intentions of TDD to begin with.
If it takes you longer to write tests and they get in the way, your design is wrong.

No, the problem isn’t TDD. The problem is that all we seem to be able to teach in the college system these days is syntax for a particular language rather than design patterns. That’s probably because most of the guys teaching don’t know any better and syntax is easier to teach and grade. This is where I think you and I agree. If we had people who knew what a good designed looked like, we could move on to tools that helped us think about design up front.

I disagree with you on what people say about TDD and up-front design; that is the whole point of “do the simplest thing that could possibly work” part of TDD. Here’s an example from James Shore: http://www.jamesshore.com/Agile-Book/incremental_design.html. Now there is the part of deciding what to work on first, which has a little design to it. And that works if you are as good as James Shore is.

I also disagree that you get higher coupling without TDD. What I’ve seen is that the TDD approach creates little design pressure for most developers, so you tend to get the same sort of designs either way. But TDD with high coupling gives you the bad tests.

I don’t mean to imply that I think this is universal; I still like TDD. I just don’t think it works for less-skilled developers.

I agree with you strongly about the kind of skills that new developers get coming out of college, but I don’t think it was really any different when I was in school back in the 1980s; I got algorithms but very limited instruction in design. I *do* think that we have lost the focus on craft that was part of the profession in earlier days, and these days with short ship cycles, management seems ultra-focused on tactical concerns, and that means there is no time to learn and no time to refactor, but plenty of time to get slower and slower.

As I’ve thought about this more, I think the problem with TDD and, for that matter, Agile, Extreme Programming and other “methods” we try to apply to our programming is that they can not be enforced. Our code will “run” regardless of what method we used to write the code. And since it isn’t a necessary part of programming, it doesn’t get used and never gets learned. If having a good architecture and knowing how to properly use TDD were as fundamental to being able to run our code has being able to use the syntax, many of these problems would go away.

In light of this, I think maybe we are trying to solve a problem with hacks when what we may need is a new paradigm for programming that makes architecture and testing part of what defines the language while still leaving enough choice to be flexible.

No, Dave’s right about higher coupling without TDD. I’m arguably still an amateur TDDer, and have been for about eight years (i.e. I do TDD as often as I can, but have never worked in an environment that embraces it). When I talk to my team about TDD and just writing unit tests in general, the point I always try to hammer home hardest is that you can write your code in such a way that it’s difficult to test. By writing the test first, TDD guides you toward a design that is easier to test, and that design will almost always have lower coupling than a codebase which has no unit tests at all.

That’s a great point about missing the third step in the red-green-refactor cycle. There have only been a handful of times I’ve done TDD where I’ve embraced that third step and refactored frequently, and it has always made an improvement in the design. Resharper makes that third step easier by reducing the refactoring friction. I suspect the one thing that would make it easiest is combining TDD and pair programming, so you have a second set of eyes looking at the big picture and seeing where the design needs to pivot.

Excellent post. I attended TDD course 18 months ago tried to do it and gave it up. However I then attended clean code course with uncle bob about 4 months ago and what I learned on that course really helped. Before TDD you do need to have a good grasp of clean code, decoupling etc.

I think your argument rests on a false dichotomy. The code that is hard to test is the perfect feedback loop for the developer that signifies a need for better design and refactoring. The value of this feedback is immeasurable, and to me it’s one of the fundamental forces for people to learn refactoring and design. If we view TDD as Test Driven Design, then TDD is a gateway for newcomers to learn refactoring and design, and I fail to see why you claim we should choose to teach one and not the other.

Even for a skilled developer TDD questions the domain model and helps fleshing out some details that may have been overlooked.

When I am doing TDD, I find the feedback very useful to guide my design. But when I’ve been working with other developers, they are generally not sensitive to the feedback, or if they are, they don’t know how to go from “this test is hard to write” to “do to make the code better”. Further, I see a number of cases where heavy use of mock libraries mutes the design feedback.

I tend to agree with Sam’s comment in it’s fullest.
This article creates a set-in-stone connection between TDD with good design skills, while this connection may exist, it does not have to.
That is like saying ORMs are bad because a lot of people do not know how to use them efficiently or even when to use them.

By the time I left college in 2000, I had also read Writing Solid Code and Code Complete. The rest I picked up along the way. What do people recommend today as good contemporary design and refactoring books?

I think this is a well written post and I appreciate you are taking a stand on this! However, I don’t think it is fair anyone to say categorically that TDD does or doesn’t work. I’m not saying the OP did except for the clickbait. 🙂 The is no silver bullet technique or process that is going to give us perfect code and TDD is like that. It has some good properties, but won’t guarantee anything. The is only one thing that can “guarantee” good code (it it is attainable) and that is time, effort, skill and training. I think the issue that you are running into when TDD fails is that the user assumed the because they were using TDD, everything would come out right. Or, more realistically, they worked in a company that assumed that TDD would “solve the problem” and then pushed the developer into schedules that don’t allow them enough time to refactor, grow and evolve their code. I found @Dave’s comment interesting where he defended a lot of the qualities of TDD. TDD promotes these behaviors (thinking about design ahead of time, having a working baseline, etc.) but doesn’t guarantee these. In particular he said “TDD has driven me…” and that is the sign of a good technique. One that forces us to think differently about how we code. But, we can’t let that be the end. But we also see that in Agile and many other methods. These “tenets” (e.g. deliver early and often, or pair programming, etc.) don’t guarantee a good product. They are there to promote customer interaction, team interaction, discovery through working code, etc.
But overall I think that the fundamental failure is that we don’t spend enough time really working on our craft as developers, and spend too much time trying to “manufacture” code. The organizations that value design thinking, reflecting, refactoring and mastering the craft again and again seem to product better code.

I think there’s a strong balance to be struck between tests and static analysis. I see a lot of tests that a decent static analyzer should be doing for you automatically without the need to maintain the test.

My biggest complaint with TDD is testing frameworks add additional obscurities and nuances to learn. All new names “toBeTruthy()” “toBeFalsey()” sounds stupid. I have NEVER gotten a forms framework to work with the testing suite. E.g. “form.isValid” never works. Highly nuanced configurations. Mocking is BS, at least in .Net mocks can be generated automatically at runtime, I shouldn’t have to add more boilerplate/scaffolding just to get tests to work. Angular hides the “Testing” section in the documentation. If we’re expected to use it, they should have more examples. How to mock a router? How to mock a form? How to mock authentication guards? Frameworks should be self verifiable.

I don’t see this as an either-or proposition. As Kent Beck says in XP Explained, “Quality is not a control variable.”

TDD doesn’t automatically give you a good design. “Listening” to your tests (or feeling their Design Pressure) is a skill that takes time to hone, and one that I continue hone daily.

Rather than drive toward good design, TDD drives code away from bad design. If the tests are difficult to write, it’s time to refactor. Removing that crucial step causes TDD not to be complete, much like removing the dough from a pizza recipe would no longer yield a pizza. When teaching folks, I always try to show “Look how this test becomes difficult to write/setup/etc., and notice how that exposes a design flaw here. Let’s do a quick refactor.”

If practicing TDD made all code great, we’d all be doing it.

Knowing when to turn the different dials of TDD is an art that takes time to learn, but I don’t think we should advocate for eliminating TDD entirely. Very rarely do I look at a piece of code and wish there were less tests.

Write a new failing test, make that test pass, refactor to a better design.

I was taught that TDD is a feedback mechanism to help you improve your design. The fact that you’re experiencing pain while maintaining your tests is a symptom of poor design. TDD encourages decoupled code, and decoupled code tends to be easier to test. If changing one piece of code breaks 20 tests that should be unrelated, that tells you that there’s too much coupling and you have to refactor.

And they were constantly mentioning that TDD is about design, and if attention is paid to some of the subtle and critical things you can experience with TDD (rather than many of the distractions of TDD mimicry, like Test-First Programming, and the distortion of TDD that originally appeared in MSDN’s knowledge base), you too can learn to design well.

I learned to design from TDD. And I still use TDD to do what Mark Miller of DevExpress called, “Consume-First Design”.

TDD’s most important lessons aren’t the superfluous ones, and the on-the-surface mechanics. TDD’s most important lessons are still the most subtle, and because of that they’re some of the most powerful.

TDD does indeed depend on good design. And many good designers depend on TDD to challenge their pre-conceptions, and validate their design presumptions against the reality of a consume-first exercise.

Maybe I could have learned to design without TDD – it’s hard to second guess now. But being fluent in both even to this day, I’m comfortable crediting TDD not only with requiring good design, but in teaching how to produce it by providing proof in the awkwardness of a design by experiencing it first-hand.

You know your design is awkward if the effort to set up a test requires dealing with many interactors and more than two layers. Putting that down in code is a way to see it first-hand; to get clear evidence that the design is going to cause setbacks, and that the habits that produced the design are going to cause complications and multiplications of setbacks.

Or, you might just be going through the motions of test-first programming, and never learn the design lessons that can be observed through TDD.

And while this next comment always draws the kind of fire that suggests logical fallacy, I’ve never seen someone who’s declared that they’ve given up on TDD actually demonstrate the depth of grasp of TDD that suggests that there was something in-hand to give up to begin with.

Did you really, really “get” TDD? All of the items you listed in the cons section of your writeup suggest that you ended up not doing TDD, but maybe test-first programming.

This is the most difficult and abrasive part of getting to the bottom of these kinds of issues. It requires us to ask ourselves if we really achieved that which we’ve already given ourselves credit for. And that usually draws out a lot of ire. It’s the question we want to avoid, and the question we want to have a right to de-legitimize. But it’s the most salient issue we can face when addressing subtle knowledge that’s not-so-obvious and easy to miss like TDD.

TDD will make the initial cut of some code more expensive to produce. It will also make less expensive to own over its lifetime.

Refactoring is indeed a damned good skill to have – but not refactoring because you got the design wrong to begin with. Refactoring is valuable as a way to adapt to new constraints that could not be known or addressed at the outset. Refactoring isn’t a desirable skill when its about fixing mistakes that were knowable and avoidable from the start.

I’m admittedly not very good at dealing with code that should have never been the design horror show that it was allowed to be. I’m good at not making the obvious mistakes that lead to the kind of fragility that you described in your cons section.

By learning to design well through proving exercisability, I didn’t learn to fix obvious mistakes. I learned to not make them. And that’s why TDD is valuable.

The techniques and approaches for getting cancerous software healthy again do indeed include the more crude mechanics that you enumerated. But the problems that TDD is intended for and the problems you outlined are indeed quite different, and beget necessarily different approaches. TDD can still be valuable in those contexts, but it’s a different tool belt to put on to address a different class of problems.

So this blog post is fiction, then? Having never flown with China Eastern, from my observations of their missing planes, I deem #NoChinaEastern… except it was Malaysian Air, but whatever, their logos look the same.

I’m not quite sure what you are trying to say with your comment. My experience is based on working in a number of different teams experimenting with TDD and teaching TDD to others. If you look at the other comments, you will find others whose experience aligns with mine.

I have got a lot of value from TDD, but its really easy to take it too far and end up with brittle tests that slow down development. I use it only on interfaces/abstract classes and with real objects; and staying away from mocks entirely. TDD has been a real valuable design step for me, and lets me sleep at night knowing the build is in good shape.
I used to put user behaviour in unit tests, but have learned that stuff belongs in BDD tests. These tests actually use a persona and their interaction the the product. BDD first, then filling in the architectural gaps with TDD tests. Outside-in.

Reducing complexity is the real goal in the end, so don’t let your tests get in the way, and don’t be afraid to delete dead tests (or dead code of any flavor). I’ll never go back to not using BDD/TDD tests as it has been a real benefit to me.

It sounds like your saying, “If they don’t do TDD well, it doesn’t work”. Refactoring is, in my opinion, an essential part of TDD. I’m not stuck on TDD but it’s my favorite way to develop still. I was hoping you were going to introduce us to something new that was the next evolutionary step. Got any of that?

Good point. What’s your opinion on the claims that test should be written in the very beginning because:
1. Force you think in certain aspects and help in design.
Personally I don’t feel this advantage. There are lots of things need to consider in design phrase, and writing tests need too much efforts and gain relative little. I’d rather write some specifications and claims, even it cannot be executed like tests. Especially when I need to iterate rapidly.

2. Write a simple even dumb test, fail it, then make it pass to get some sense of achievement.
I don’t feel I did something worth my time for fail and pass a simple test.

For me, I like doing TDD, because the incremental approach works well with me and I like doing things that make me get better at refactoring. And it gives me code that is easily testable, and therefore loosely coupled.

But what I’ve found is that I can now write code that is loosely coupled – or refactor my way to that code – without having the tests to guide me, so sometimes I do just that.

I do find that through TDD I can focus on one thing and know that I won’t miss things as I build up the class I want.

As for the second things, when I am doing TDD I often write a first test that is something like Assert.IsNotNull(new MyClass());

It seems pointless, but it verifies that my tool chain is up and working properly and reminds me that I’m doing TDD, and it takes me about 30 seconds.

I totally disagree.
1) It takes longer, no it’s faster for most things. It seems longer but the problem when you test last is you write code that’s hard to test. So what happens, you write a bunch of code and then do a half ass job of testing it. TDD is the fastest way to write tested code.

2) Tests get in the way, no! That crap design would be hell to refactor with out tests in place. Ever worked on a large code base with no unit tests? Regression hell.

3) This is true but certain tools can help like Python’s autospec Mock. But it’s still better to have mocks than to have nothing.

4) I think this comes with experience, after testing and refactoring for a while you start seeing pitfalls ahead of time and don’t paint yourself into a corner as much.

So basically is the argument that units tests are bad or that TDD is bad. Because if you think unit tests are good then TDD is the only realistic way you will get 100% test coverage. If you don’t believe in unit tests then I feel sorry for you.

I’ve always found that you can’t apply things like TDD, OOP and things like it religiously. It works great on toy problems, “a vehicle has an engine, wheels and so on and a car is a type of vehicle”, but things in real life seldom break down into tiny perfect pieces.

What I do is design/think about my API or what not beforehand, which I should not do according to TDD, then I apply TDD. That has served me well for 10 years or so. Also, I don’t go for maximum code coverage, some things does not need tests, but for some code I find it almost crucial to have them.

As with all things, use what works for you and apply the needed amount of it.

Here’s an alternative hypothesis: it works well in the lab because the problems are small and well-defined, so that while the subjects’ performance seems to be a result of TDD, it is actually because they have a fairly clear mental picture of the entire problem and their intended solutions, so the first pass does not need a whole lot of refactoring, and the changes are incremental. Without that guiding vision, development becomes blind trial-and-error, and inconsistencies develop between the various components – inconsistencies that do not emerge until late in the game. Refactoring becomes a nightmare where each change has unintended consequences, and the roots of the problems extend deep into the architecture, and that is why refactoring can’t save the situation.

1. You don’t sell tests, you sell working apps. TDD doesn’t generate profit directly.
2. Refactoring in the for-profit world is a pipe dream. It rarely happens, and when it does, it’s usually a result of an individual taking extra time to make things better (i.e. not during regular work hours). More likely, you get to add twisty logic to existing code to handle new stuff.
3. TDD is ultimately about quality. Agile is ultimately about delivering as little as possible, as soon as possible. These are in direct competition, therefore a balance will need to be found.
4. It makes more sense (at least to me) to get some code working first, work out the unit tests in a fresh frame of mind (yes, I’ve found code errors while writing follow-up unit tests), and then cleaning up the code on a second pass. For me that’s the fastest path to getting something working, working well, and tested, with the knowledge that I’ll rarely have the time to come back and look at things later. I strive for 95% coverage, knowing that there will be a couple of places that are difficult to test well without making the code unnecessarily hard to read. Clarity sometimes trumps loosely-coupled.

You argue that TDD without frequent refactoring and design skills does not work. Do you think that programming in general, without refactoring and design skills works? If you don’t know how to design software and you don’t refactor, you will end up with a big ball of mud. This is not relevant to TDD. Maybe you mean that a junior developer should focus more on these skills first and then move to TDD.

Another problem highlighted in the article, fragile tests, is irrelevant to TDD. You can create very fragile tests, even if you write them after your production code. Tests are clients of production code and should not depend on the implementation details, just like any other external component. If you want to have a good suite of automated tests (by using TDD or any other technique), you need to keep your tests clean and decoupled.

My experience is that, for the majority of developers, writing code the way that they are most familiar with – which is typically what I would call “think about it a little, maybe talk to somebody, and then write it” – results in better code than if those same developers try to do the same task with TDD. I think this is because they are better at doing this little up-front design than they are at taking the code that comes out of TDD and evolving it into a decent design.

So I don’t think we should advocate that those developers use TDD, at least in most cases. We should instead try to improve their design and refactoring skills. If they *want* to do TDD, then sure.

I explicitly did not say “junior developer” because I have known many senior developers with poor design skills.

I think it really depends on how you evaluate a `senior developer` – how would that be possible that a senior developer has very low design skills? Likely you’re referring to some domain expert that are good at actual business logic or domain knowledge but poor software programming skills.

I agree with your point that even senior developer may need some sort of adequate up-front design, which should be based on actual business scenario analysis and responsibilities split – good design skill (and refactoring skills) should be essential for nowadays software engineers in my opinion.

1) Most production code is not well designed; it has high coupling, poor separation of concerns, unnecessary abstraction, etc.
2) The design of the production code is mostly guided by senior engineers as they have the most influence on how things get done in the group.
3) Therefore, senior engineers are generally not good at design.

This clearly isn’t the only factor; the environment in which developers operate can put a lot of constraint on good design.

I think this is an excellent point. For me, TDD helped me improve my refactoring skills. It also helped me think about how to create simple solutions for simple problems and avoid over-engineering. This stuff isn’t easy. Trying to learn TDD by applying it everywhere in production code is going to be like learning to fly airplane by starting out using 747. It’s just going to result in frustration and probably cause more problems than it solves. However, I think it is valuable. It is good to think about how to apply it, and try to apply it at an appropriate level based on your skills. I believe thinking about how to you can make your code easier to test helps push you toward better designs, but don’t try to go too far beyond your depth.

For me TDD is mainly three things.
Firstly it’s about testing myself so I’ve understood the task properly before writing any code at all. In this step I also can tell if my code is doing too much and therefore can tell if my method or function conforms to the “single responsibility” rule.
Secondly it’s about maintainability and logical reason. A codebase where I don’t know what it’s supposed to do, or forgot about it, I can always rely on the tests to skip the parts that’s not interesting making me to move faster.
Thirdly it’s about the ability to refactor and therefore evolving design. Even if this is a step in TDD you will always need to refactor since requirements changes over time. The solution you had is not the optimal solution anymore and therefore you must refactor anyway. Evolving design is a strength where you continuously strengthen your code while getting work done faster and faster since you can offload the reasoning on the tests, covering your back.

AFAIK TDD is actually the only way to produce code in a systematic way. When reading TDD driven code I can make certain assumptions which I cannot do with randomly produced code (there’s no system to the code). TDD code is developed automatically with tests in mind and are always a lot easier to test when you need to do it, and you always need to do it. (I would argue that there is possible code which is not testable as is, unless you refactor and then you don’t know what the code is doing the same thing as it did before).

If your tests get coupled with the code, I’d say it’s because either your method/function is doing to much or it’s a language problem, not giving you the necessary tools to ignore implementation details, which mocks usually are an indication of.

Since TDD is a systematic way of producing code (at least more systematic than not doing it), code which isn’t produced with TDD will not play well with TDD produced code since it won’t follow the same conventions, designs and possibilities.

TDD doesn’t automatically make the code less bug free, but I don’t believe that it won’t cause more bugs just because you use TDD.

If programmers cannot learn or deal with TDD, you have a different problem on your hands.

I think a lot of people confuse “No TDD” with “no testing”, which isn’t the case at all. But if you happen to be a TDD advocate I’d love to hear your defence of how “Uncle Bob” solves the bowling game kata, as mentioned in my article.

In my opinion, TDD is flawed scaffolding that actively constrains good design. The holistic nature of a well-architected solution can’t be achieved by iteratively chipping away at the red-green-refactor coalface.

“The holistic nature of a well-architected solution can’t be achieved by iteratively chipping away at the red-green-refactor coalface.”

Strictly speaking, this feels like hyperbole to me, because through refactoring one can go from one implementation of a solution to any other.

At least theoretically. Practically, this doesn’t happen.

There’s a tension between a “design up front” approach and a TDD approach. If you do design up front, you will likely get to whatever design you choose up front. Is that the best design? Are there other alternatives? That question generally doesn’t get asked. TDD is a tool that you can use to bring those questions more to the forefront, so that you get the opportunity to say something like, “hey, there’s a really a chain of responsibility hiding under here”.

Of course, that assumes that you have the skills and time to see those things, and one of my main points was that you don’t typically see that in TDD in production scenarios. And I agree strongly that in those cases, you are better off if the developers do design up front because their skills are a better match to that approach.

“Strictly speaking, this feels like hyperbole to me, because through refactoring one can go from one implementation of a solution to any other.”

I’ve never seen a good example of that though, and I’ve always been left with the impression that the red-green-refactor cycle was supposed to be about making small changes very quickly, and didn’t permit long breaks to introspect about design. But even if it did, refactoring bad code into good would require many small changes that keep the tests green (and introduce new tests), in code-compile-test cycles of only a couple of minutes long. I’d love to see a good example of how a well-designed system evolves from scratch under these constraints. My claim is that you either have a good idea of what the final code will look like (and slow implementation down by following TDD) or end up with a mess (that you’ll want to rewrite).

In the bowling game kata, “Uncle Bob” writes a scoring routing that returns 0, just to make a test green. He *knows* it’s wrong when he writes it. He *knows* the final program will score a game of bowling differently. But he writes it anyway, because that’s the process. What is the utility of doing that? There is none. The process results in poor code and tests that don’t even cover the requirements he explicitly states at the beginning. I think it’s the perfect anti-example of TDD.

My first is that the goal of the refactor step is to take the existing implementation and make it better. That might be a small rename, or that might be extracting a few classes, or it might be choosing a new organizational approach for the code. If you do this incrementally and you have decent design skills, then I don’t think updating the tests is too much overhead, though it obviously depends on what the current architecture is and how far you need to change it; if you have crappy code, it’s going to take work to get it into a good state and you are sometimes better off abandoning the current tests and writing new ones that better match the goal.

WRT the “do the simplest thing that could possibly work” step. The point of this step is incrementalism. To take the scoring routine as an example, with the TDD flow you might get something like:

1) Add a test for zero
2) Add a test for one
3) Add a test for normal scores
4) Add a test for spares
5) Add a test for strikes
6) Add a test for end-of-game spares
7) Add a test for end-of-game strikes

Without the TDD approach, I would expect implementations that look like this:

What I notice is that people tend to bite off far more code at once than they should, and that means they spend more time writing code and are more likely to miss test cases. My experience is that big steps take much much longer than a series of small steps and the first “stupid” test takes on the order of 15-30 seconds to write, so I’m in favor of doing it.

Indeed – so many projects I’m on, I am discouraged to re factor: I do it anyway, since removing cruft is the only way I can understand what is going on and move forward.

Usually it is legacy projects; but I have had pressure to leave things as they are on my own projects too. In this case, refactoring is a risk, since your manager has already said not to – I need to make those changes without saying I am as well :/

Having lots of tests and 90% code coverage is great, but does not imply TDD. I fully support comprehensive unit testing, but I’ve never clicked with the idea of writing tests first to “drive”development.

I think “test-driven development” could also be named “mistake-driven testing”, because examples often show code being written that is just enough to get existing tests to pass but it’s still known to be wrong a priori (to “drive” the creation of a new, failing test).

Because you only get to choose two of the three characteristics of FAST, GOOD, and CHEAP; TDD may not be as effective in industry, where low-cost high-productivity is the goal. But, in education, where the goals are critical thinking and skills development, TDD is immensely valuable. It forces the students to think (and even think ahead) rather than just plunging into code. Writing test cases and entire input/output pairs ensures understanding. Manual TDD even shows up in the standard SE curriculum where the students build a requirements document with user-stories, then progress to architectural design, coding, validation and finally deployment. Those user-stories (which help identify actors, actions, scenarios, etc) can be _executed_ as group role-plays to _test_ whether requirements will be met or architectures will function.

My experience – and it would be great if it was not typical – is that many teams have codebases that are very hard to test in, so while they have a mandate to write unit tests, they don’t really write that many of them, and many of the ones they do write are more integration tests than unit tests.

I sometimes explain this by saying that unit testing in a lot of a production codebases is a graduate-level research project and most developers only have 200-level refactoring/design/testing skills.

Tests are only a by product of TDD but not design. Tests will prove your design works. It can be used as a discovery tool to help you tease design/architecture or behavior. It is just a tool. Every problem can not be solved by one tool. If you are not great at design and refactoring you should not be coding.

Unit tests are the seat belt we all need before we do any refactoring, or believe me, you’ll crash, and it hurts. When you write TDD, the code you produce is decoupled, and your tests simple.
When you have code without unit tests, you have legacy code. And just like any legacy code, working it this out is hard, there is lot of coupling, and the code is hard to test.
Nobody wants to be there, without a seat belt, and if you are, you have to do the cleaning , or you will always have a mess.

I would have bought into this, if I were not a programmer, programming for around 16 years now in industry, and for average blue collar programmers like me, testing/writing test case during development (TDD) has made be better at refactoring and keeping coupling down. I hate to refactor, I am too lazy , but since I have to write the damn test, I am forced to, so that I don’t have to work too much on the test cases; and this is in a variety of programming languages, C++, Python, Java, JavaScript .. in different domains, right from enterprise software to sw that interact with small devices; and I can only freeze hearing the ticking time bomb, for those who don’t do testing During development, and either desert the project or put it on somebody else shoulder to fix the horror…real coding horror; and if you think this is of no value, either your sw is too simple or you are too awesome a coder

TDD tends to work of people that can already do a good design without it Yet those people usually don’t get any comfort from doing TDD.

TDD tends to create terrible code in the hands of juniors or less experienced people, yet in still somehow helps weed out the mess that is in their unstructured thinking, due to lack of practical experience.

Now what are the problems of TDD:

First problem: It promotes writing code, and not thinking about and abstracting the problem domain. The best designs come come out of beautiful abstractions, and abstractions do not emerge out of simply writing code. Simply writing code most of the time leads to tight coupling or glue code. There’s nothing in the practice that would steer someone away from writing glue code and call it a day, even the refactor step at the end can’t prevent this, as it’s centered around micro refactorings like method size, etc, not holistic design choices.

Second problem: It kind of makes you write tests that test the code you’re about to write, tests that test functionality. If you’d start with a functional test then you’ll usually end up implementing almost the whole design anyway, and TDD does not promote or allow this. And this leads to naturally lots of unfunctional tests and lack of encapsulation, lot’s of mocking and testing the wrong thing like “method x was called” or exposing internal state or partial results.

IMHO TDD can be useful as a tool to do brainstorming or prototyping, but that’s it. Other than this and usefulness starts to be inversely proportional to the project’s complexity.

There is a tension between an evolutionary design and an up-front design. I am personally not a fan of up-front design because it is generally quite hard to know ahead of time what you need, and I’ve seen countless cases of – to abuse your phrase – “beautiful abstraction” that were designed up front and either are not well-suited in the end, or end up being redundant.

Since removing abstractions – and especially bad ones – is one of the more costly and risky operations to perform in code, I am firmly in the YAGNI camp.

I’m not sure I understand what you are saying about the second problem.

It’s a brilliant place to start education though. Consistency. Think about the basics we teach kids all the time. Math with simplified trigonometry, because teaching fermat to a group of teens is going to be problematic. We teach English without attempting to fix the language because it’s a horrid pig-sty of a language and hundreds of years of patching hasn’t yet come up with a perfect solution for the language gods.

I’d argue that if I learned TDD I’d have learned a heap more than having to retro-fit it into my practises. I may have caught errors that I didn’t think of before, or abandoned lines of interest because if you cannot express in a test (which means you likely don’t understand what you are doing enough to be making code around it).

I won’t lie, I don’t TDD half as much as I should; I prefer to get others to write tests and specification I then test my code against. I’m my own boss, 75-80% of my code never leaves my offices, so I get to make rash, stupid decisions that will haunt me later. I think that is a better point to make about TDD. If you bought a car and Ford didn’t test it. How mad would you be? What if you were involved in an accident with said car? How mad are people that Musk is testing cars on roads, with other users that have not consented to participating in his experiment?

We don’t TDD because it makes us feel good, or makes us individually APEX, the same as language doesn’t help us internally communicate easier. We TDD when it’s required, because as well as freedom, we have responsibility to those using our code.

What I’d like to see attacked more with TDD is the cargo-cult mentality that every test must be a unit-test, that it’s infallible, a hammer to hit all nails with, and that it makes individuals faster or better coders.

“If you have those skills, TDD works great; you see what the issues are as the arise and fix them incrementally, and your simple tests prove that your design has low coupling. And, you have the tests to lean on in the future.”

For me this is the crux of the matter. TDD without up-front design skills is indeed pointless. Developers need to learn the design skills! They need to master them – or at least, strive for mastery, because I don’t think anyone can truly claim mastery in any of this 🙂
Designing loosely coupled code is a skill and any developer who refuses to learn this skill can’t complain when their code ends up in a big tightly coupled mess that cannot easily be modified or tested.
But what could be taken from this article is”people don’t have the design skills, so those people should avoid TDD until they learn these skills”.

But I have personally found that TDD is a great way to help people to learn and apply those skills in practice; because it’s a positive feedback loop.
So if you don’t take for e.g. the SOLID principles into account when designing your system, then you will have a harder time writing tests for your code; therefore that *should* prompt you to question your design and ask yourself “How can I make this better, how can I test this more easily?”.
I don’t believe it is a happy accident that following the clean code guidelines and the SOLID principles of OOD lead to easily testable code – I think they complement each other *by design* and this this was that Uncle Bob had in mind all along, I seem to recall him writing something along those lines before.

So for example, when I talk to people about the Dependency Inversion Principle, and people ask “yeah but *why* is this important, it sounds like just some theoretical dogma” then it’s easy to immediately demonstrate one advantage of adhering to the DIP – it makes the code more testable. That’s a tangible, in-your-face-you-cannot-deny benefit that as a learner you can grasp. So now you have started to bridge the gap…we all know that this is not the *only* reason to apply the principle, but it is one easy reason to understand. Later on you can start to understand the other less tangible benefits of it in terms of overall system decoupling.

For me that’s the real point of the feedback loop that TDD enables.
So whilst I agree with the sentiment behind this:
“Instead of spending time teaching people TDD, we should instead be spending time teaching them more about design and especially more about refactoring”, I disagree strongly that TDD is not one of the most useful tools by which we can teach those design skills.

Can TDD be used as a method to help developers expand their skill set?

Definitely. But my experience – and if your experience is different I’d love to know what was successful for you – is that it takes the right scenario, the right mindset, and the right codebase to make that possible.

I’ve been playing around with different ways of teaching design. Pairing works great, but has low leverage and cultural issues in many places. Group study kindof works, if you have the right environment and somebody who knows how to teach that way (I’ve had some success but am not very good at it (yet)). Do-it-yourself techniques (of which TDD is one) can work for some people, but I’ve rarely seen it do much.

I had a way longer reply for this but it ended up as big as your original blog so this is the abridged version 🙂

You’re right that it does require a confluence of things in order to click fully; but for me the best way is to make sure that you know the various design patterns and principles very well, and can talk about their pros and cons with some level of mastery; then ask people to start doing their code via TDD. Invariably they will have a problem with this because it’s causing them grief; at that point you kind of have to take each situation as it comes.

Person A has a problem because they are trying to test-drive horrible inner-platformy-lasagne code and they have to write so much setup code per test it’s killing them; Person B has a problem because they’ve never heard of Dependency Injection so they can’t figure out how to isolate their SUTs, Person C is sick of having to mock this 40 member interface because they never heard of the ISP, and so on.
And you just have to explain, and show, and most importantly,lead by example by taking the keyboard and showing them how they can do it.

This helps to avoid a lot of the resistance to adoption of TDD because you can show developers that it’s a supporting cast member, a tool and only a tool, that helps to keep their design – the one they started in their head as the simplest possible thing, scratched out on paper and are now trying to encode in their IDE – keep that on the rails and make it all the way through from design to implementation without being waylaid by artificial complexity. And really that’s all TDD is there for 🙂

I feel this post is saying “TDD is great, do TDD if you’re really good. But to require TDD for developers who do not have a strong design foundation, it will cause more frustration than it does benefit. In those, cases, it’s more important to train them up to the standard when TDD becomes a useful workflow rather than a hindrance.”

Unit tests let you refactor with confidence that you’re not breaking things. Refactoring makes it easier to write unit tests.

It’s a chicken-and-egg situation. Unless you’re starting a new project from scratch (and how often does that happen to us?) each side of the equation essentially requires that the other already be done.

I value adding lightweight unit tests *after* a new feature is completed to guard against regressions. But writing the tests first? On anything other than a trivially small codebase? It would be pretty to think so.

This article would have been so much easier to read and more professional if the author had written out the full expression, followed by the abbreviation in parentheses (TDD) the first time it was used, rather than leaving the reader to scour various abbreviation lists on the Web to try to guess what he meant. I found no fewer than 31 definitions for “TDD” at Abbreviations.com.

I’ve been exposed to TDD for over 12 years now and am all in favour of #NoTDD.

The mantra is Red->Green->Refactor. But in my experience only the most trivial level of refactoring is possible before we have to start re-writing our tests. So the real world situation is actually Green->Refactor->Red->Refactor Tests->Green.

I’ve been doing this a long time and I’ve seen countless man hours wasted in this frustrating cycle on many different code bases. And the benefit we get in return, that refactoring is now safe because the tests are a safety net, I don’t see it actually happen very often.

Teach them refactoring he says. However, he forgets that the tests are the safety mechanism that enables refactoring to safely occur. The tests are the mechanism that allows the architecture a decision the design to become flexible variables.

How can non refactoring experts attain progress in the art of refactoring without that safety net?

> How can non refactoring experts attain progress in the art of refactoring without that safety net?

1) Work with tools that do refactorings, and know them well enough to learn which ones are safe and which ones aren’t. You can do an amazing amount with tools but few developers are willing to learn much beyond the basics (extract method & rename).

2) Practice on non-production code, both with automated and manual refactoring. Ideally do this in a group setting.

3) Pair with people who know how to do this.

4) Read extensively.

Those are the best ways that I know. Doing TDD is a decent part of #2.

My experience with unit tests and refactoring is not great. Unit tests can cover you when changing how a single class works, but those are only the simplest refactorings; many refactorings involve extracting classes or changing how classes work together. Unit tests are of less help there.

Teach them refactoring he says. However, he forgets that the tests are the safety mechanism that enables refactoring to safely occur. The tests are the mechanism that allows the architecture and the design to become flexible variables.

How can non refactoring experts attain progress in the art of refactoring without that safety net?

And for the record, mocks are certainly a class of code smell. I encourage a default position of using mocks only when absolutely necessary. I’ve never had a need to use any mocking framework (in the ten years I’ve been doing TDD), even though I’ve heard of several mock frameworks that are supposedly very good, and I wouldn’t let me team members use one either.

The only thing I can see where a mock framework might be required is for easy and rapid low effort mocking of third party components. Since I don’t use mocking frameworks, I’m hand building simple mocks as facades. Coupled with IOC, that DIY approach has always been sufficient, although somewhat painful. The pain of manual mocking is not something I want to alleviate though: if it isn’t painful, myself or my team might start doing it more frequently.

I agree with you on mock frameworks; they make it far too easy to patch around very bad coupling issues.

I go beyond that, however, and try to avoid mocks in general. There are a few places where I find them very useful – port/adapter/simulator is a mocking pattern that I really like – but in general, I like to create code that can be tested without mocks. #NoMocks

I get very good results with TDD in my own designs. But that’s because I take the time to refactor. Not every boss is sympathetic to spending time on refactoring, so not every developer is able to get or maintain this habit. After a few years of getting beaten up for taking too long, developers give up on refactoring. Then they come to a shop that wants to do TDD, and they choke. Their reflexes say, “Just get it done.”

I don’t think this is TDD’s fault. I think it’s the fault of the consistently poor management in software developement. Now there’s a contraversial position.

100% agree. I’ve heard so many people say “yay!TDD” or “you must know embrace TDD” to find no one really knows what they are doing. Another buzz word band wagon to jump on. Patterns and decoupled code are the real benefit of driving towards testable code. The benefits of decoupled code serve themselves

In my experience, TDD without refactoring to a better design is meaningless, but fefactoring without tests is unnecessary risk. About 8 months ago, I was assigned to a project with about 17 years worth of badly designed legacy code with even worse implementation. While some of the developers on the project were reasonably astute in the design department, most were not. None of the team were proficient in refactoring or unit testing. Shortly after I started, one of the better developers attempted to refactor a fairly significant portion of the code without any unit testing at all. We ended up rolling it back twice, and the company is now short one developer and one manager.

Since then, I have taken on the task of improving the skills of the team in refactoring and in developer written testing (unit & integration), which naturally includes teaching design principles. I make use of developer led training, confluence articles, code reviews and one-on-one mentoring. Our rollback rate has diminished, our code and design quality have improved, and our management and product owners are much happier than was the case six months ago.

TDD is only one tool in what should be a full toolbox. As with any tool, its use is only as good as the skill of the developer using it.

Sounds like you are taking a wonderful approach and we would get along well…

I do want to comment a bit on one thing you said…

> refactoring without tests is unnecessary risk.

I would qualify this by saying, “It depends on what you mean by refactoring”, which can mean anything from a simple extract method to a full rearchitecture. I have certainly seen too many refactoring fiascos in my days. Far to many.

Part of what I like to teach is how to do them automatically (ie tool-assisted) in a way that is safe without tests, how to approach refactoring without tests in an organized manner (and perhaps what tests to write before the refactoring), how to refactor while pairing, and – most importantly – how to do refactorings as a series of incremental changes rather that a rewrite approach.

I have been at TDD ‘evangelist) for 18 years and I absolutely agree with what you are saying here. When I started I did not fully understand the importance of Refactor after every passing test look at code look at tests refactor them both. Then next test. When the refactor step is not skipped TDD takes everything to a new level. My teams and I code faster produce solutions easier and have fewer bugs ….. I have seen and coached teams who did not Refactor continually and I have seen how teams can slow down. It seems that refactoring, naming variables, methods etc is an absolutely essential skill for any programmer no matter where when or what. I have found that teaching TDD right encourages developers to refactor because they feel safer doing it. I believe that another option is that the focus on TDD shifts to the importance of TDD for continually refactoring and producing clean code. As always TDD was unfortunately named and focusing on the tests is a common failure. Yes to Test Driven Refactoring.

Thanks a lot. I’m a TDD fan, so you won’t have me on not using TDD. I get your points, but I just don’t suffer from these. I might have at some point but I found that as I learned more TDD, they disappeared. Ex :

– I’m so used to it that I don’t feel TDD slows me down
– I use almost no mocks, which makes the whole thing more manageable, implies less coupling. I replace mocks by immutable value objects, test data builders, customer assertions, reusable fake in-memory implementations or large parts of the system and proxy mocks
– I found that TDD actually allows me to redesign my code and keep it clean

At the same time, other people might also have great results without using TDD. I don’t want to enforce it on other, I don’t think there is a silver bullet. For me though, it made my whole life easier as a programmer.

Meanwhile in the real world TDD/Unit Testing has not caught a single bug or issue that has happened on my own personal website this year. Far more likely to happen in the real world are web hosting issues, configuration issues and weird Microsoft stuff.