95

Test-driven development (TDD) is big these days. I often see it recommended as a solution for a wide range of problems here in Programmers SE and other venues. I wonder why it works.

From an engineering point of view, it puzzles me for two reasons:

  1. The "write test + refactor till pass" approach looks incredibly anti-engineering. If civil engineers used that approach for bridge construction, or car designers for their cars, for example, they would be reshaping their bridges or cars at very high cost, and the result would be a patched-up mess with no well thought-out architecture. The "refactor till pass" guideline is often taken as a mandate to forget architectural design and do whatever is necessary to comply with the test; in other words, the test, rather than the user, sets the requirement. In this situation, how can we guarantee good "ilities" in the outcomes, i.e. a final result that is not only correct but also extensible, robust, easy to use, reliable, safe, secure, etc.? This is what architecture usually does.
  2. Testing cannot guarantee that a system works; it can only show that it doesn't. In other words, testing may show you that a system contains defects if it fails a test, but a system that passes all tests is not safer than a system that fails them. Test coverage, test quality and other factors are crucial here. The false safe feelings that an "all green" outcomes produces to many people has been reported in civil and aerospace industries as extremely dangerous, because it may be interepreted as "the system is fine", when it really means "the system is as good as our testing strategy". Often, the testing strategy is not checked. Or, who tests the tests?

In summary, I am more concerned about the "driven" bit in TDD than about the "test" bit. Testing is perfectly OK; what I don't get is driving the design by doing it.

I would like to see answers containing reasons why TDD in software engineering is a good practice, and why the issues that I have explained above are not relevant (or not relevant enough) in the case of software. Thank you.

11
  • 55
    Bridges, cars, and other physical designs are nowhere near as malleable as software. This is an important distinction, and means that comparions between software and real engineering isn't always relevant. What works for bridges might not work for software, and vice versa. Commented Jan 30, 2011 at 13:29
  • 9
    I somewhat agree with your doubts. I've for example to confess I got the impression that having a test suite can have as a side-effect a somehow "softened" attention when writing code. Of course tests are a good thing (a mandatory one if you want to have the possibility to refactor), but only if they supplement the attention to details, border cases, efficiency or extensibility and not if they replace it.
    – 6502
    Commented Jan 30, 2011 at 13:56
  • 2
    @6502: By all means! TDD is not a silver bullet and will not solve all issues that arise during software development. It is however a useful method of organising workflow. You can for example impose a requirement that all border cases are covered by tests. You still need to know what these border cases are, but now you also have a tool to check if your code deals with them correctly.
    – Mchl
    Commented Jan 30, 2011 at 14:01
  • 2
    @CesarGon, you might also be interesting in this question I asked on SO some time ago... Not quite TDD, but related... Some very enlightening answers there.
    – AviD
    Commented Jan 30, 2011 at 23:31
  • 6
    How amazing that a civil-engineering/software-development analog doesn't hold up. Along the same lines, I've often noticed that I can't cook pancakes the same way that I mow my lawn.
    – user29776
    Commented Oct 21, 2011 at 13:52

19 Answers 19

68

I think there is one misconception here. In software design, the design is very close to the product. In civil engineering, architecture, the design is decoupled from the actual product: there are blueprints that hold the design, that are then materialized into the finished product, and those are seperated by huge amounts of time and effort.

TDD is testing the design. But every car design and building design is also tested. Construction techniques are first calculated, then tested in smaller scale, then tested in larger scale, before put out in a real building. When they invented H-beams and the load for example, rest asured that this has been tried and tried again before it they actually build the first bridge with it.

Designs of cars are also tested, by designing prototypes, and yes, certainly by adjusting things that are not exactly right, until it lives up to the expectations. Part of this process though is slower, because as you said, you can't mess around much with the product. But every redesign of a car draws on experiences learned from former ones, and every building has about a thousend years of fundamentals behind it about the importance of space, light, insulation, strength, etc. Details are changed and improved, both in the buildings and in redesigns for newer ones.

Also, parts are tested. Perhaps not exactly in the same style as software, but mechanical parts (wheels, igniters, cables) are usually measured and put under stress to know the sizes are correct, no abnormalities are to be seen, etc. They might be xrayed or laser-measured, they tap bricks to spot broken ones, they might be actually tested in some configuration or other, or they draw a limited representation of a large group to really put it to the test.

Those are all things you can put in place with TDD.

And indeed, testing is no guarantee. Programs crash, cars break down, and buildings start doing funny things when the wind blows. But... 'safety' is not a boolean question. Even when you can't ever include everything, being able to cover - say - 99% of the eventualities is better than covering only 50%. Not testing and then finding out the steel hasn't settled well and is brittle and breaks at the first smack of a hammer when you just put up your main structure is a plain waste of money. That there are other concerns that might still hurt the building do not make it any less stupid to allow an easily preventable flaw bring down your design.

As to the practice of TDD, that is a matter of balancing. The cost of doing it one way (for example, not testing, and then picking up the pieces later), versus the cost of doing it another way. It is always a balance. But do not think that other design processes do not have testing and TDD in place.

16
  • 8
    +1 for talking about where testing occurs in manufacturing. Great point.
    – Adam Lear
    Commented Jan 30, 2011 at 15:20
  • 11
    You say "parts are tested". Sure, but not test-driven designed. An aircraft part is not designed in a test-driven fashion, but in an architectural, big-design-upfront manner. The similarities with TDD are non-existent here.
    – CesarGon
    Commented Jan 30, 2011 at 16:43
  • 4
    Adding to that: TDD is, in my opinion, mainly about ways to make sure you can check parts rather than a big 'all or nothing' at the end. But the adagium of TDD, 'build a test first' is not meant to be 'make a test before you think about what you want to accomplish'. Because thinking of a test IS part of designing. Specifying what you want that exact part to do, is designing. Before you ever start to type, you have already done some designing. (In that way I think the term 'test driven design' is misleadingly implying a oneway path, where it is really a feedback loop).
    – Inca
    Commented Jan 30, 2011 at 17:55
  • 3
    @CesarGon: No, TDD is driving the DEVELOPMENT by testing. That's different to driving the design. The design dictates how you would use the system, and thus which tests you'd need to implement to replicate that behaviour. Implementing those tests often helps you refine the design, though.
    – deworde
    Commented Oct 21, 2011 at 9:39
  • 3
    Possibly, I've misunderstood, but the way it was explained to me, is that you design your system before writing the code, laying out the main components, how they will interlink, how each components public interface will interact. Then you write tests that conform to this behaviour for each component. Then, once you have the tests, you begin writing real code that will support the API's used within the tests. You don't just sit down, and write a bunch of tests with no clear idea of how the design will fit together to produce real functionality.
    – deworde
    Commented Oct 21, 2011 at 16:37
26

IMO, most of the success stories for TDD are fake and just for the marketing purpose. There may be very few success with it, but only for small applications. I am working on a big silverlight application where TDD principles are used. The application has got hundreds of tests but it is still not stable. Several parts of the application are not testable because of the complex user interactions. Resulting tests with lot of mocks and hard to understand code.

Initially when we tried TDD, it all seems good. I was able to write lot of tests and mock out the parts that are tough for a unit test. Once you have a fair amount of code and an interface change is required, your are screwed. A lot of tests needs to be fixed and you will rewrite more tests than the actual change in the code.

Peter Norvig Explains his view on TDD in the Coders At Work book.

Seibel: What about the idea of using tests to drive design?

Norvig: I see tests more as a way of correcting errors rather than as a way of design. This extreme approach of saying, “Well, the first thing you do is write a test that says I get the right answer at the end,” and then you run it and see that it fails, and then you say, “What do I need next?”—that doesn’t seem like the right way to design something to me. It seems like only if it was so simple that the solution was preordained would that make sense. I think you have to think about it first. You have to say, “What are the pieces? How can I write tests for pieces until I know what some of them are?” And then, once you’ve done that, then it is good discipline to have tests for each of those pieces and to understand well how they interact with each other and the boundary cases and so on. Those should all have tests. But I don’t think you drive the whole design by saying, “This test has failed.”

6
  • 7
    Now, if you tell these facts to TDD folks and consultants, the answer you get would be, well, you haven't done TDD right! Commented Jan 31, 2011 at 6:29
  • 12
    And they'd be right. We're doing BDD / TDD on a very high volume system, and its been working well. The tests are there to tell you that you broke the expected behavior. If you're going and changing this later "breaking" the tests, you are in fact doing it wrong. The tests should be changed first to solidfy the NEW behavior of the system, then you change it. And yes, if you're doing it right, you write your tests starting with "what does this thing need to do" and the process of writing the test helps you think "what does IT need to do its job." Oh, and no consultants were ever used...
    – Andy
    Commented Oct 21, 2011 at 1:55
  • 4
    Making a lot of tests does not exempt you from creating a proper design. A highly coupled design regardless of how many tests are built around it is and will always be fragile. embedding tests in this design can very well make the whole thing even worst.
    – Newtopian
    Commented Oct 21, 2011 at 2:32
  • 3
    It is not a question of doing it wrong or being a highly coupled design. The fact is that interfaces change. That means all tests that use that interface must change. On big systems keeping tests in sync with required changes can begin to overwhelm implementation. This becomes an even bigger problem if you are doing agile development since the odds of interface changes are far more likely. It is funny how when methodologies don't work the methodology's proponents insist you are doing it wrong. It is more likely that the methodology is not suited for all problem domains.
    – Dunk
    Commented Oct 21, 2011 at 14:42
  • 2
    In my experience doing TDD works for small applications or modules. When I have to work on something complex TDD slows me down because it forces me to write a detailed (runnable) specification before I have gotten the overall picture clear in my mind: so I get lost in details too early, and often I have to throw away a whole bunch of tests if I find out that I do not need certain classes (I am still playing with the design). In such cases I prefer to get a reasonable overall design first, and then flesh out the implementation details (possibly using TDD).
    – Giorgio
    Commented May 6, 2014 at 10:00
25

Test Driven Design works for me for the following reasons:

It is a runnable form of the specification.

This mean that you can see from the test cases:

  1. THAT the code being called full-fills the specification as the results expected are right there in the test cases. Visual inspection (which expects the test cases to pass) can say immediately "oh, this test checks that calling invoiceCompany given this situation, should have THAT result".
  2. HOW the code should be called. The actual steps needed to do the tests are specified directly without any external scaffolding (databases are mocked out etc).

You write the view from the outside first.

Frequently code is written in a way where you first solve the problem, and then you think of how the code you just wrote is to be called. This frequently give an awkward interface because it is frequently easier to "just add a flag" etc. By thinking the "we need to do THIS so the testcases will look like THAT" up front you turn this around. This will give better modularity, as the code will be written according to the calling interface, not the other way around.

This will usually result in cleaner code too which require less explanatory documentation.

You get done faster

Since you have the specification on runnable form, you are done when the full test suite passes. You may add more tests as you clarify things on a more detailed level, but as a basic principle you have a very clear and visible indicator of progress and when you are done.

This means that you can tell when work is necessary or not (does it help pass a test) you end up needing to do less.

For those pondering on it may be useful to them, I encourage you to use TDD for your next library routine. Slowly set up a runnable specification, and make the code pass the tests. When done, the runnable specification is available to everyone who needs to see how to invoke the library.

Recent Study

"The results of the case studies indicate that the pre-release defect density of the four products decreased between 40% and 90% relative to similar projects that did not use the TDD practice. Subjectively, the teams experienced a 15 to 35% increase in initial development time after adopting TDD." ~ Results and Experiences of 4 Industrial Teams

2
  • 5
    I would add to this that you actually HAVE a somewhat reasonable and clear guideline about when you are done. Without some clear procedure to objectively verify that you've completed the task at hand, its difficult to know. My own experience includes many hours and days wasted "negotiating" whether a task has been completed and a continued, constant moving of the line. This impacts all levels of project management including scheduling, for how can you possibly schedule such a task? Clearer targets with quicker turnaround increase throughput AND communication. Commented Jan 30, 2011 at 20:56
  • This should be the accepted answer.
    – NingW
    Commented Apr 25, 2019 at 8:35
19

In my mind TDD works because

  • It forces you define what you want the unit to do before decide on the implementation at a level of precision not generally covered by any spec or requirements doc
  • It makes your code inherently reusable, because you have to use it in both tests and production scenarios
  • It encourages you to write code in smaller eaiser to test chunks which tends to lead to better designs

Specifically on the points you raise

  • Code is more malleable than brick or steel, so it is cheaper to modify. It is cheaper still if you have tests to ensure the behaviour is unchanged
  • TDD is not an excuse not to do design - a high level architecture is general still advised, just not with too much detail. Big Up Front Design is discouraged, but doing just enough design is encouraged
  • TDD can't guarantee a system works, but it prevents lots of small mistakes slipping through which otherwise would be missed. Also because it generally encourages better factored code it is often easier to understand so less likely to be buggy
1
  • 3
    You should also add that as defects are discovered you can ensure that they won't be repeated as you'll add another test.
    – Andy
    Commented Oct 21, 2011 at 1:56
19

The process of creating software is not the process of writing the code. No software project should start without a 'broad scope' plan first. Just like a project of bridging two shores of a river needs such a plan first.

The TDD approach relates (mostly) to unit testing - at least that's how people tend to think about it - that is creating the most low-level bits of software code. When all features and behaviours have already been defined and we do actually know what we want to achieve.

In structural engineering it looks a bit like this:

'We have these two pieces of metal connected together, and the connection needs to sustain shear forces in the order of x. Let's test which connection method is the best one to do this'

For testing if software works as a whole, we design other kinds of tests like usability tests, integration tests and acceptance tests. These too should be defined before the actual work on writing the code starts, and are performed after the unit tests are green.

See V-Model: http://en.wikipedia.org/wiki/V-Model_%28software_development%29

Let's see how it would work for a bridge:

  1. A local government says to a bridge-building company: "We need a bridge to connect these two points. The bridge needs to be able to allow n amount of traffic per hour and be ready fo Dec 21st 2012' - this is a definition of acceptance test. The company will not get full amount of (or any) money, if they can't pass that test.

  2. Company's managment decides on project schedule. They set up working teams and set up goals for each team. If the teams will not meet these goals - the bridge will not be built on time. However - there's some level of flexibility here. If one of the teams has some problems, the company can offset that by changing the requirements, changing subcontractors, hiring more people etc. so that the entire project still meets the goal set up in point #1.

  3. Within a team responsible for designing particular bridge components it looks like in the example I've given above. Sometimes the solution is obvious, because we have a large body of knowledge regarding building bridges (it's like using a well tested library in software development - you just assume it works as advertised). Sometimes you need to create several designs and test them to choose the best one. Still, the criteria upon which the component is tested are known beforehand.

9
  • If I understand you correctly, you are saying that TDD is OK as long as (a) it is used for unit testing only, and (b) it is accompanied by other testing approaches as well. If this is the case, it may address point number 2 in the OP. How would you address point number 1?
    – CesarGon
    Commented Jan 30, 2011 at 13:29
  • @CesarGon: TDD also works great for integration tests. Commented Jan 30, 2011 at 13:37
  • Point 1 boils down to the act, that before the final project for a car or a bridge gets accepted, it goes through many reiterations during which all it's details are reviewed and tested against the requirements imposed by the 'broad scope plan'. It's done mostly on paper (or in computer memory), because that's cheaper in this case, but notice that there are often physical prototypes being build of both entire construction (not in bridge case perhaps) as well as of it's components.
    – Mchl
    Commented Jan 30, 2011 at 13:39
  • @Karpie: And for acceptance tests too! You should know beforehand what is required for your work to be accepted by the client.
    – Mchl
    Commented Jan 30, 2011 at 13:41
  • 1
    Ok then. Pretty much first team to start the work is a team of architects who are told to design a bridge capable of meeting clients criteria, while being also cheap and possibly looking good and not going down at the first stronger gust of the wind. The team might propose a few rough designs more or less meeting these criteria, then select one and work on it in more details, repeat, repeat, repeat until the design is ready (i.e. it meets the criteria given, and is detailed enough so that another stages of project can start)
    – Mchl
    Commented Jan 30, 2011 at 16:15
16

TL;DR

Programming is still a design activity, it's not construction. Writing unit tests after the fact only confirms that the code does what it does, not that it does something useful. Test failures are the real value because they let you catch mistakes early.

Code Is Design

In Chapter 7 of PPP "Uncle Bob" talks about this issue directly. Very early in the chapter, he references an excellent article by Jack Reeves in which he proposes that code is design (the link goes to a page collecting all three of his articles on the topic).

What's interesting about this argument is that he points out, unlike other engineering disciplines where construction is a very expensive activity, the construction of software is relatively free (hit compile in your IDE and you have your built software). If you view writing code as a design activity instead of a construction activity, then the red-green-refactor cycle is basically an exercise in design. Your design evolves as you write tests, the code to satisfy them, and refactor to integrate the new code into the existing system.

TDD as Specification

The unit tests that you write for TDD are a direct translation of the specification as you understand them. By writing code that minimally satisfies your specification (makes your tests turn green), all of the code you have written is there for a specific purpose. Whether or not that purpose has been met is validated by a repeatable test.

Write Tests to the Functionality

A common mistake in unit testing happens when you write the tests after the code, you end up testing that the code does what it does. In other words you'll see tests like this

public class PersonTest:Test
{
   [Test]
   TestNameProperty()
   {
      var person=new Person();
      person.Name="John Doe";
      Assert.AreEqual("John Doe", person.Name);
   }
}

While I guess this code could be useful (make sure that someone hasn't done something obscene with a simple property). It doesn't serve to validate a specification. And as you said, writing these kinds of tests only takes you so far.

While Green is Good the Value Lies in Red I had my first true "aha" moment in TDD when I got an unexpected test failure. I had a suite of tests that I had for a framework I was building. Adding in a new feature, I wrote a test for it. Then wrote the code to make the test pass. Compile, test...got a green on the new test. But also got a red on another test that I didn't expect to go red.

Looking at the failure, I breath a sigh of relief because I doubt I would have caught that bug for quite some time had I not had that test in place. And it was a VERY nasty bug to have. Fortunately, I had the test, and it told me exactly what I needed to do to fix the bug. Without the test, I would have kept building my system (with the bug infecting other modules that depended on that code) and by the time the bug was discovered, it would have been a major task to properly fix it.

The true benefit of TDD is that it allows us to make changes with reckless abandon. It's like a safety net for programming. Think of what would happen if a trapeze artist makes a mistake and falls. With the net, it's an embarassing mistake. Without, it's a tragedy. In the same regard, TDD saves you from turning boneheaded mistakes into project killing disasters.

4
  • 4
    The value of red tests catching bugs is an attribute of Unit Testing in general, not of TDD specifically. Commented Jan 31, 2011 at 23:27
  • 2
    You're right on that point. But the likelihood that I would have had that specific bug covered with post-hoc unit testing is lower. Commented Feb 1, 2011 at 1:06
  • 1
    Can you support that claim with some evidence, data or solid analysis?
    – CesarGon
    Commented Feb 20, 2011 at 18:18
  • 1
    @CesarGon this study, while of programmers working on a small project, suggests that developers using TDD produce code with better test coverage than those testing after-the-fact (92%-98% vs 80%-90%) and consequently catch more defects during development (18% fewer defects found in the code produced using TDD).
    – Jules
    Commented Nov 10, 2014 at 23:09
11

You'll not find anyone who advocates Test Driven Development, or even Test Driven Design (they're different), that says tests prove applications. So lets just call that a straw man and be done.

You'll not find anyone who dislikes or is not impressed by TDD that says Tests are a waste of time and effort. Although tests do not prove applications, they are quite helpful in finding errors.

With those two things said, neither side is doing anything different with regard to actually performing tests upon the software. Both are doing testing. Both RELY on testing to find as many bugs as possible, and both use tests to verify that a software program is working as well as can be discovered at the time. Nobody with half a clue sells software without testing and nobody with half a clue expects that testing is going to render the code that they sell completely bugless.

So, the difference between TDD and not-TDD isn't that tests are being done. The difference is in when the tests are written. In TDD tests are written BEFORE the software. In not-TDD tests are written after or in concert with the software.

The issue I've seen with regard to the latter is that testing then tends to target the software being written more than the desired outcome or specification. Even with the testing team being separate from the development team, the testing team tends to look at the software, play with it, and write tests that target it.

One thing that has been noticed time and time again by those who study project success, is how often a customer will lay out what they want, the development people run off and write something, and when they come back to the customer saying "done" it turns out to be utterly and completely NOT what the customer asked for. "But it passes all the tests..."

The goal of TDD is to break this "circular argument" and provide basis for the tests testing the software that isn't the software itself. The tests are written to target the behavior that the "customer" wants. The software is then written to pass those tests.

TDD is part of the solution meant to address this problem though. It's not the only step you make. Other things you need to do is make sure there's more customer feedback and more often.

In my experience though, TDD is a very difficult thing to successfully implement. It's hard to get tests written before there's a product because a lot of automated testing requires having something to play with in order to get the automation software to work right. It's also hard to get developers who are not used to unit testing to do it. Time and time again I've told people on my team to write the tests FIRST. I've never actually gotten one to do it. In the end, time constraints and politics destroyed all efforts so that we don't even do unit tests at all anymore. This of course lead, inevitably, to the design being accidentally and severely coupled so that even if we wanted to, it would now be prohibitively costly to implement. Avoiding THAT is what TDD ultimately provides for developers.

4
  • +1 Thanks for the comprehensive answer, Noah. I agree that the major difference between TDD and not-TDD is in when the tests are written. However, I also think that the first "D" in TDD stands for "driven", meaning that, in TDD, the whole development is driven by the testing. That is what I find most puzzling. I have no problems with writing tests before actually constructing what's going to be tested. But letting tests drive? How is that different from a green light to do anything as long as the superficial (i.e. outcome) is fine?
    – CesarGon
    Commented Jan 30, 2011 at 22:38
  • Well, Cesar, what would you propose as a better, objective criteria for deciding when a development task is finished? If, as it is in TDD, the test is the spec that a developer targets, then the developer has done their job when the test passes, no? Yes, there can be flaws in the test just like there can be flaws in any specification. That's not the developers' task to resolve though. If the test is flawed then it gets fixed, then development targets the new target, and when it's all green they're done. It works because there's ALWAYS a test to pass...no extra, undocumented fluff. Commented Jan 31, 2011 at 1:28
  • 3
    Maybe I didn't express myself clearly. Tests may be a good way to determine when you are done. But I don't think they are a good way to decide what you must build. And, in TDD, I find that people are using tests to decide what they are supposed to build. Is that your experience as well?
    – CesarGon
    Commented Jan 31, 2011 at 14:09
  • No. Our builds are automated. They're triggered by changes. Like I said, TDD is only part of the solution. Commented Jan 31, 2011 at 18:00
9

Design first

TDD is not an excuse to skip design. I've seen many jump in the "agile" bandwagon because they though they could start coding immediately. True agile will get you to stat coding much faster than the (other field) engineering good practices that inspired the waterfall process.

But test early

When one say that the test are driving the design it simply means that one can use tests very early in the design phase, long before it is complete. Doing this tests will strongly influence your design by challenging the grey areas and pitting it against the real world long before the product is completed. forcing you often to go back to design and adjust it to take this into account.

Test and design... one and the same

In my opinion TDD simply brings test to be an integral part of design instead of something done at the end to validate it. As you start using TDD more and more you get in the mindset of how to destroy/break your system as you design it. Personally I do not always do my tests first. Sure I do the obvious (unit) tests on an interface but the real gains come from the integration and specification tests that I create when I think of a new and creative way this design can break. As soon as I think of a way, I code a test for it and see what happens. Sometimes I can live with the consequence, in this case I move the test in a separate project that is not part of the main build (since it will continue to fail).

Then who drive the show ?

In TDD the driven here simply means that your tests influence so strongly your design that one can feel they are actually driving it. However it one stops at that, and here I understand your concerns, it is a bit scary... who drives the show ?

YOU are driving, not the tests. The tests are there so that as you move along you gain a good level of confidence in what you have created thus allowing you to build further knowing it rests on solid grounds.

solid so long as the tests are solid

Exactly, hence the driven in the TDD. It is not so much the tests are driving the whole thing, but they will have such deep influence over how you do things, on how you design and think your system that you will delegate a large chunk of your thought process to tests and in return they will have a deep influence on your design.

yeah but if I do that with my bridge th....

stop right there... software engineering is VERY different from any other engineering practices out there. In fact software engineering has actually a lot more in common with literature. One can take a finished book, rip a 4 chapters from it ans write two new chapters to replace them stick them back in the book and you still have a good book. With good tests and software you can rip any part of your system and replace it with another and the cost of doing so is not much higher that it was creating it in the first place. In fact, if you did your tests and allowed them to influence your design enough it may very well be cheaper than creating it in the first place since you will have a certain level of confidence that this replacement will not break what the tests are covering.

If its sooo good how come it does not always work ?

Because testing requires a VERY different mindset than building does. Not every one is able to switch back and from, in fact some people will not be able to build proper tests simply because they cannot set their mind to destroy their creation. This will yield projects with too few tests or tests just enough to reach a target metrics (code coverage comes to mind). They will happy path tests and exception tests but will forget about the corner cases and boundary conditions.

Others will just rely on tests forgoing design partly or altogether. Each member doing it's thing then integrating with one-another. Design is first and foremost a communication tool, stakes we set in the ground to say this is where I will be, sketches that say this is where the doors and windows will be. Without this your software is doomed regardless of how many tests you put in the thing. Integration and merges will always be painful and they will lack tests at the highest levels of abstractions.

Tor these teams TDD may not be the way to go.

7

With TDD you tend not to write code that isn't easy or quick to test. This may seem like a small thing, but it can have a profound affect on a project as it impacts how easy it is to refactor, test, reproduce bugs with tests and verify fixes.

It's also easier for a new developer on the project to get up to speed when you have better factored code supported by tests.

5
  • 2
    I like this - it emphasises the point that its not so much TDD that creates the benefits (though having unit tests clearly has a huge amount of value) as the kind of code that it produces in the sense that its testable (in isolation) and all kinds of good things follow from that (separation of concerns, IoC and dependency injection, etc, etc)
    – Murph
    Commented Jan 30, 2011 at 14:14
  • 1
    @Murph yeah TDD helps keep you honest :)
    – Alb
    Commented Jan 30, 2011 at 14:20
  • 1
    To be honest I'm not actually convinced by the "easier to get up to speed" argument - the tests may be helpful, but the code (as a whole, not necessarily in isolation) can be somewhat harder to decode since some stuff will appear as if by magic e.g. you don't know what implementation of IInjectedThing you're using.
    – Murph
    Commented Jan 30, 2011 at 14:47
  • @Murph The theory is that the implementation IInjectedThing is also designed well and covered by good tests, so you don't really need to know what it is to be able to understand a class that it's injected into.
    – Adam Lear
    Commented Jan 30, 2011 at 15:19
  • @Anna - yes, up to a point... if you're trying to work out where something is broken (I always feel bug hunting is a good way to find ones footing in a project) or where something needs to be changed/added you need to know where. Even if that where is well encapsulated you still need to find it... and if it means replacing something (new implementation of IWhatsit) then you need to know how to use the alternative implementation. Again, I'm not disuputing that the construction is bad - too much evidence to the contrary - rather suggesting that some things may be less obvious.
    – Murph
    Commented Jan 30, 2011 at 17:31
5

I've been thinking about this a lot, even though I do not practice TDD that much myself. There seems to be a (strong?) positive correlation between code quality and following TDD.

1) My first take is that, this is (primarily) not due TDD adding "better quality" into code (as such), it's more like TDD helps weeding out worst parts and habits, and so indirectly increasing the quality.

I would even advocate that it's not the test themselves -- it is the process of writing those tests. It's hard to write tests for a bad code, and vice versa. And keeping this in the back of the head while programming, eliminates a lot of bad code.

2) Another point of view (this is getting philosophical) is following the mental habits of the master. You don't learn to become a master by following his "external habits" (like, a long beard is good), you must learn his internal ways of thinking, and this is hard. And somehow making (novice) programmers follow TDD, align their ways of thinking closer to those of master.

3
  • +1 I think you nailed it, Maglob. I especially like your explanation that "TDD helps weeding out worst parts and habits, [...] indirectly increasing the quality". And the long beard analogy is very good too.
    – CesarGon
    Commented Jan 30, 2011 at 15:59
  • you do not write tests for a bad code, but you write a test and then write the code to make the test pass.
    – user1249
    Commented Jan 30, 2011 at 21:48
  • Maglob, for the love of the more practical side of things, you've got it covered best. @Thorbjørn, I think Maglob was trailing more along the lines that if your projected design sucks, your tests will surely have to directly suck-up to the level of suckyness your attempting to materialize and the rotten smell of it should reek in your tests before you even get down to writing any actual code. Commented Jan 31, 2011 at 14:29
3

The "write test + refactor till pass" approach looks incredibly anti-engineering.

You seem to have a misconception about both refactoring and TDD.

Code refactoring is the process of changing a computer program's source code without modifying its external functional behavior in order to improve some of the nonfunctional attributes of the software.

Thus you cannot refactor code until it passes.

And TDD, specifically unit testing (which I consider the core improvement, since other test seem rather plausible to me), is not about redesigning a component until it works. It is about designing a component and working on the implementation until the component works as designed.

Also it is important to really grasp, that unit testing is about testing units. Due to the tendency to always write a lot of things from scratch, it is important to test such units. A civil engineer already knows the specs of the units he uses (the different materials) and can expect them to work. These are two things that often don't apply to software engineers, and it very pro-engineering to test the units before using them, because it means using tested, high-quality components.
If a civil engineer had the idea to use some new fibre tissue for making a roof to cover a stadium, you would expect him to test it as a unit, i.e. define the needed specs (e.g. weight, permeability, stability, etc.) and thereafter test and refine it until it meets them.

That is why TDD works. Because if you build software of tested units, chances are much better it works, when you plug them together and if it doesn't you can expect the problem to be in your glue code, assuming your tests have good coverage.

edit:
Refactoring means: no change in functionality. One point of writing unit test is to ensure, that refactoring doesn't break the code. So TDD is meant to assure, that refactoring doesn't have side effects.
The granularity is not a subject of perspective, because as I said, unit tests test units and not systems, whereby the granularity is exactly defined.

TDD encourages good architecture. It requires you to define and implement specifications for all of your units, forcing you to design them ahead of implementation, which is quite the contrary of what you seem to think. TDD dictates the creation of units, that can be tested individually and are thus completely decoupled.
TDD doesn't mean I throw a software test at spaghetti-code and stirr the pasta until it passes.

In contradistinction to civil engineering, in software engineering a project usually constantly evolves. In civil engineering, you have the requirement to build a bridge in position A, that can carry x tons and is wide enough for n vehicles per hour.
In software engineering, the customer can basically decide at any point (possibly after completion), he wants a doubledeck bridge, and that he wants it connected with the nearest motorway, and that he would like it to be a lifting bridge, because his company recently started to use sailing ships.
Software engineers are tasked to change designs. Not because their designs are flawed, but because that is the modus operandi. If software is well engineered, it can be redesigned at high level, without having to rewrite all low level components.

TDD is about building software with individually tested, highly decoupled components. Well executed, it will help you to respond to changes in requirements signifficantly quicker and safer, than without.

TDD adds requirements to the development process, but it doesn't prohibit any other methods of quality assurance. Granted, TDD doesn't provide the same security as formal verification, but then again, formal verification is extremely costy and impossible to use on a system level. And still, if you wanted to, you could combine both.

TDD also encompasses tests other than unit tests, that are performed at system level. I find these easy to explain but difficult to execute and hard to measure. Also, they are quite plausible. While I absolutely see their necessity, I do not really value them as ideas.

In the end, no tool actually solves a problem. Tools only make solving a problem easier. You can ask: How will a chisel help me with great architecture? Well if you plan to do straight walls, straight bricks are of help. And yes, granted, if you give that tool to an idiot, he'll probably slam it through his foot eventually, but that's not the chisel's fault, as much as it is not a flaw of TDD that it gives false security to novices, who do not write good tests.
So at the bottom line, one can say TDD works much better than no TDD.

2
  • I don't think I have a misconception; I agree with the definition of code refactoring that you have posted, but I also think that you need to look at the granularity of the changes in the code. When you say "the process of changing a computer program's source code", you need to realise that, from the perspective of a certain whole, the behaviour does not change, but the behaviour of the parts does indeed change. That is how change is effected. Besides this, I hear you about why TDD works (and I share it), but how is architecture addressed as per my original post?
    – CesarGon
    Commented Jan 31, 2011 at 14:04
  • @CesarGon: Post updated.
    – back2dos
    Commented Jan 31, 2011 at 18:12
2

I don't like your saying 'the test, rather than the user, sets the requirement'. I think you're considering only unit testing in TDD, whereas it also covers integration testing.

Apart from testing the libraries that make up the base of the software, write the tests that cover the interactions your users have with the software/website/whatever. These come straight from the users, and libraries like cucumber (http://cukes.info) can even let your users write the tests themselves, in natural language.

TDD also encourages flexibility in code - if you spend forever designing the architecture of something, it's going to be incredibly hard to make those changes later on if necessary. Start with writing a couple of tests, then write a little code that passes those tests. Add more tests, add more code. If you need to radically change the code, your tests still stand.

And unlike bridges and cars, a single piece of software can undergo huge changes over its lifetime, and doing complex refactoring without having the tests written first is just asking for trouble.

3
  • I hear you about the benefits that you claim for TDD. But as far as I understand you don't address the issues of architecture and test quality that I explicitly ask for in my question.
    – CesarGon
    Commented Jan 30, 2011 at 13:40
  • @CesarGon: I think your specific questions apply to any type of testing, not just TDD. So I just focused on the specific features of TDD that 'work'. Commented Jan 30, 2011 at 13:43
  • 1
    Integration tests definitely make more sense than stand-alone unit tests. Most bug cases I stumble upon would have never been found by unit tests, only by testing the entire real system with all its bolts and whistles in place.
    – user8685
    Commented Jan 30, 2011 at 14:40
2

I think you're approaching the first point from the wrong angle.

From a theoretical point of view, we are proving that something works by checking against failure points. That's the method used. There may be many other ways you can prove that something is functional, but TDD has established itself due to the simplicity of it's bit-wise approach: if it doesn't break it works.

In practice, this just outspokenly translates to: we can now move on to the next thing (after we have successfully applied TDD to satisfy all the predicates). If you approach TDD from this perspective, then it's not about "write tests + refactor till pass" it's more about having completed this, I'm now wholly focusing on the next feature as the now most important thing.

Think how this applies to civil engineering. We're building a stadium that can house a public audience of 150000 people. After we have proven that the structural integrity of the stadium is sound, we've satisfied safety first. We can now focus on other issues that become immediately important, such as lavatories, food stands, seatings, etc... making the audience's experience a more pleasurable one. This is an oversimplification, as there's much more to TDD, but the crux is that you don't make the best damned possible user experience if your focusing both on new and exciting features and maintaining integrity at the same time. You get it half-ways in both cases. I mean, how can you know exactly how many lavatories and where should you place for 150000 people? I've rarely seen stadiums collapse in my own lifetime, but I've had to wait in line during half-time on so many occasions. That says that the lavatory problem is arguably more complex and if the engineers can spend less time on safety, they may finally be able to resolve the lavatory issue.

Your second point is irrelevant, because we've already agreed that absolutes are a fools endeavor and because Hank Moody says they don't exist (but I can't seem to find a reference for that).

3
  • +1 for a good explanation of my first point, and for the reference to Hank Moody. Glorious.
    – CesarGon
    Commented Jan 31, 2011 at 14:10
  • 2
    Thanks, I appreciate it. I view TDD more as a psychological phenomena, rather than a technical approach/process. But that's just my world-view on the matter. Commented Jan 31, 2011 at 14:32
  • Can you know exactly how many lavatories and where they should be placed? The answer is yes - go ask any architect and they'll tell you this information is made up-front and sometimes with clear statistical data to back it up.
    – gbjbaanb
    Commented Sep 14, 2013 at 22:28
1

TDD in software engineering is good practice, in the same way as error handling in applications is good practice as well as logging and diagnostics (although it's part of the error handling).

TDD is not to be used as a tool to reduce software development into trial & error coding. But still, most programmers stare at runtime logs, watch exceptions in the debugger or use other signs of failure/success during their development phase which consists of coding/compiling/running the app - all day long.

TDD is just a way to formalize and automate those steps to make you as a developer more productive.

1) You cannot compare software engineering to bridge construction, the flexibility in bridge construction is no way near that of designing a software program. Constructing the bridge is like writing the same program over and over again into a lossy machine. Bridges cannot be duplicated and reused as software can. Each bridge is unique and has to be manufactured. The same goes for cars and other designs.

The hardest thing in software engineering is reproducing faults, when a bridge fails is usually very easy to determine what went wrong, and it is easy in theory to reproduce the failure. When a computer program fails it can be a complex chain of events that brought the system into a faulty state and it can be very hard to determine where the error is. TDD and unit test makes it easier to test the robustness of software componenents, libraries and algorithms.

2) Using weak unit tests and shallow test cases that does not stress the system to build false sense of confidence is just bad practice. Ignoring the architectural quality of a system and just fulfill the tests are of course as bad. But cheating at the construction place for a skyscraper or a bridge to save material and not follow the blueprints is as bad and it happens all the time...

3
  • I disagree with your implication that it is easy in physical (i.e. non-software) systems to reproduce failures. Look at the extremely complex, hard work that is necessary to determine the root causes of mechanical failures in air traffic accidents, for example.
    – CesarGon
    Commented Jan 30, 2011 at 16:02
  • Hm, now you are comparing a crashing Airliner with a failing bridge, a bridge usually cannot fly, case closed. But the comparison between airplanes and software is sometimes valid. Both areas are very complex and require a structured test methodology. So when a bridge fails, you know that it has been overloaded. When an airplane crashes, well you know that the abnormal state of flying above ground failed, but the reason usually requires a thorough investigation the same with software failure.
    – Ernelli
    Commented Feb 12, 2011 at 19:30
  • Bridges can be duplicated - or at least, the bridge blueprint you buy from the architect can, roughly, with modifications to suit your exact circumstances. The point is that if you need a bridge you will go to the architect and he will give you a list of only a few types you can have - suspension, box, arch etc, and a limited list of materials to build it out of.
    – gbjbaanb
    Commented Sep 14, 2013 at 22:30
1

If you accept that the sooner bugs are found, the less the cost of fixing them, then that alone makes TDD worthwhile.

1
  • 1
    Do you have any evidence that bugs are found sooner in a TDD setting? Also, what about the side-effects of TDD, such as impact on architecture?
    – CesarGon
    Commented Jan 30, 2011 at 22:39
0

TDD isn't really about testing. And it's certainly not a replacement for good testing. What it gives you is a design that's well-thought out, easy for the consumer to consume, and easy to maintain and refactor later. Those things in turn lead to fewer bugs and a better, more adaptable software design. TDD also helps you think through and document your assumptions, often finding that some of them were incorrect. You find these out very early in the process.

And as a nice side benefit, you have a big suite of tests that you can run to make sure that a refactoring does not change the behavior (inputs and outputs) of your software.

2
  • 6
    -1. Lots of people keep saying this, but I have yet to see the magic that makes it happen. Commented Jan 31, 2011 at 13:07
  • @Bart van Ingen Schenau, have you done TDD? I have been doing it for about 4 years, and I have definitely seen the "magic" happen.
    – Marcie
    Commented Jan 31, 2011 at 16:46
0

I'll give you a short answer. Typically TDD is looked at the wrong way just like unit testing is. I never understood unit testing until recently after watching a good tech talk video. Essentially TDD is just stating you want the following things to WORK. They MUST be implemented. Then you design the rest of the software the way you normally would.

Its kind of like writing use cases for a library before designing the library. Except you can change the use case in a library and you might not for TDD (I use TDD for API design). You are also encouraged to add more test and think of wild inputs/uses the test may get. I find it useful when writing libraries or APIs where if you change something you must know you broke something. In most day to day software i dont bother since why do i need a test case for a user pressing a button or if i want to accept a CSV list or a list with one entry per line... That doesnt really matter i am allow to change it thus i shouldnt/cant use TDD.

0

Software is organic, when structural engineering is concrete.

When you build your bridge, it'll remain a bridge and it's unlikely that it will evolve into something else within a short period of time. Improvements will be made over months and years, but not hours and days like in software.

When you test in isolation, there are normally two types of frameworks you can use. Constrained framework and unconstrained. Unconstrained frameworks (in .NET) allow you to test and substitute everything, regardless of access modifiers. I.e. you can stub and mock private and protected components.

Most of the projects that I have seen use constrained frameworks (RhinoMocks, NSubstitute, Moq). When you test with these frameworks, you have to design your application in such a way that you can inject and substitute dependencies at runtime. This implies that you must have a loosely coupled design. Loosely coupled design (when done right) implies a better separation of concerns, which is a good thing.

To summarise, I believe that thinking behind this, is that if your design is testable, therefore it's loosely coupled and it has a good separation of concerns.

On a side note, I've seen applications that were really testable, but poorly written from object oriented design perspective.

0

Why does TDD work?

It doesn't.

Clarification: automated tests are better than no tests. However I personally think that most of unit tests are waste because they usually tautological (i.e. says things obvious from actual code under test) and it can't be easily proven that they consistent, not redundant and cover all border cases (where errors usually occur).

And most important: Good software design doesn't magically falls out from tests as it's advertised by many agile/TDD evangelist. Everybody claiming otherwise please provide links to peer-reviewed scientific research which proves this, or at least reference to some open source project where benefits of TDD can be potentially studied by its code changes history.

Not the answer you're looking for? Browse other questions tagged or ask your own question.