可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have mixed feelings about TDD. While I believe in testing I have a issues with the idea of the test driving my development effort.
When you code to satisfy some tests written for an interface for requirements you have right now, you might shift your focus from building maintainable code, from clean design and from sound architecture.
I have a problem with driven not with testing. Any thoughts?
回答1:
No.
If done right, Test Driven Development IS your design tool.
I hope you forgive me for linking to my own blog entry, wherein I discuss the pitfalls of Test Driven Development that went wrong simply because developers treated their tests as, merely, tests.
In a previous project, devs used a highly damaging singleton pattern that enforced dependencies throughout the project, which just broke the whole thing when requirements were changed:
TDD was treated as a task, when it
should have been treated as an an
approach. [...]
There was a failure to recognize
that TDD is not about tests, it’s
about design. The rampant case of
singleton abuse in the unit tests made
this obvious: instead of the test
writers thinking “WTF are these
singleton = value; statements doing in
my tests?”, the test writers just
propagated the singleton into the
tests. 330 times.
The unfortunate consequence is that
the build server-enforced testing was
made to pass, whatever it took.
Test Driven Development, done right, should make developers highly aware of design pitfalls like tight coupling, violations of DRY (don't repeat yourself), violations of SRP (Single Responsibility Principle), etc.
If you write passing code for your tests for the sake of passing your tests, you have already failed: you should treat hard to write tests as signposts that make you ask: why is this done this way? Why can't I test this code without depending on some other code? Why can't I reuse this code? Why is this code breaking when used by itself?
Besides if your design is truly clean, and your code truly maintainable why is it not trivial to write a test for it?
回答2:
There's always a risk of overdoing either the TDD design or the upfront design. So the answer is that it depends. I prefer starting with a user story/acceptance test which is the base of the requirement that my tests will aid in producing. Only after I've established that, I start writing detailed unit tests TDD-style. If the only design and thinking you do is through TDD, then you risk too much of a bottom up approach, which might give you units and classes that are excellent in isolation, but when you try to integrate them into the user story fulfilling task you might be surprised by having done it all wrong. For more inspiration on this, look att BDD.
A great "debate" about this has been recorded between Robert C. Martin and James Coplien, where the former is a TDD advocate and the latter has stated that it ruins the design of a system. This is what Robert said about TDD and design:
"There has been a feeling in the Agile
community since about '99 that
architecture is irrelevant, we don't
need to do architecture, all we need
to do is write a lots of tests and do
lots of stories and do quick
iterations and the code will assemble
itself magically, and this has always
been horse shit. I even think most of
the original Agile proponents would
agree that was a silliness."
James Coplien states that merely driving your design from TDD has a great risk:
"One of the things we see a lot, in a
lot of projects, is that projects go
south on about their 3rd sprint and
they crash and burn because they
cannot go any further, because they
have cornered themselves
architecturally. And you can't
refactor your way out of this because
the refactoring has to be across class
categories, across class hierarchies,
and you no longer can have any
assurances about having the same
functionality."
Also he gives a great example of how a bank account probably would look if you test drove it as compared to using your upfront knowledge to drive the architecture:
"I remember when I was talking with
Kent once, about in the early days
when he was proposing TDD, and this
was in the sense of YAGNI and doing
the simplest thing that could possibly
work, and he says: 'Ok. Let's make a
bank account, a savings account.'
What's a savings account? It's a
number and you can add to the number
and you can subtract from the number.
So what a saving account is, is a
calculator. Let's make a calculator,
and we can show that you can add to
the balance and subtract from the
balance. That's the simplest thing
that could possibly work, everything
else is an evolution of that.
If you do a real banking system, a
savings account is not even an object
and you are not going to refactor your
way to the right architecture from
that one. What a savings account is,
is a process that does an iteration
over an audit trail of database
transactions, of deposits and interest
gatherings and other shifts of the
money. It's not like the savings
account is some money sitting on the
shelf on a bank somewhere, even though
that is the user perspective, and
you've just got to know that there are
these relatively intricate structures
in the foundations of a banking system
to support the tax people and the
actuaries and all these other folks,
that you can't get to in an
incremental way. Well, you can,
because of course the banking industry
has come to this after 40 years. You
want to give yourself 40 years? It's
not agile."
The interesting thing here is that both the TDD proponent and the TDD antagonist are saying that you need design up front.
If you have the time, watch the video. It's a great discussion between two highly influential experts, and it's only 22 minutes long.
回答3:
I completely agree with pjz. There is no one right way to design software. If you take TDD to an extreme, without any forethought except the next unit test, you may make things harder on yourself. Ditto for the person who sets out on a grand software project by spending months on diagrams and documentation, but no code.
Moderate. If feel the urge to draw up a quick diagram that helps you visualize the structure of your code, go for it. If you need two pages, it might be time to start writing some code. And if you want to do that before you write your tests, so what. The goal is working, quality software, not absolute conformity to any particular software development doctrine. Do what works for you and your team. Find areas where improvements can be made. Iterate.
回答4:
I completely agree with you on that subject. In practice I think TDD often has some very negative effects on the code base (crappy design, procedural code, no encapsulation, production code littered with test code, interfaces everywhere, hard to refactor production code because everything is tightly coupled to many tests etc.).
Jim Coplien has given talks on exactly this topic for a while now:
Recent studies (Siniaalto and
Abrahamsson) of TDD show that it may
have no benefits over traditional
test-last development and that in some
cases has deteriorated the code and
that it has other alarming (their
word) effects. The one that worries me
the most is that it deteriorates the
architecture.
--Jim's blog
There is also a discussion over on InfoQ between Robert C. Martin and James Coplien where they touch on this subject.
回答5:
My way to think about it is, write what you want your code to look like first.
Once you have a sample of your target code (that right now does nothing) see if you can place a test scaffolding onto it.
If you can't do that, figure out why you can't.
Most of the time it's because you made a poor design decision (99%), however if that's not the case (1%) try the following:
- determine what the crazy requirements are that you need to abide to that wont let you test your code. One you understand the issues redesign your API.
- if someone else decided this requirements discuss about it him/her. They probably had a good reason for the requirement and once you know their reason you'll be able to perfect your design and make it testable. If not now you can both rework the requirement and you'll both be the better for it.
After you have your target code and the test scaffolding. Implement the code. Now you even have the advantage of knowing how well your progressing as you pass your own test (Its a great motivator!)
The only case where testing may be superfluous, from personal experience, is when you are making an early prototype because at that point you still don't understand the problem well enough to design or test your code accurately.
回答6:
There are three steps to complete software:
- Make it work
- Make it right
- Make it fast
Tests get you #1. Your code is not done just because the tests have passed. Preferably you have some concept of project structure (Utilities, commonly accessed objects, layers, framework) before you start writing your tests/code. After you've written your code to make the tests pass, you need to re-evaluate it to see which parts can be refactored out to the different aspects of your application. Yuo can do this confidently, because you know that as long as your tests are still passing, you code is still functional (or at least meeting the requirements).
At the start of a project, give thought to the structure. As the project goes on continue to evaluate and re-evaluate your code to keep the design in place or change the design if it stops making sense. All of these items must be taken into account when you estimate, or you will end up with spagetti code, TDD or not.
回答7:
It's always a balance:
- too much TDD and you end up with code that works, but is a pain to work on.
- too much 'maintable code, clean design, and sound architecture' and you end up with Architecture Astronauts that have talked themselves into coding paralysis
Moderation in all things.
回答8:
I'm relatively new to TDD and unit testing, but in the two side projects I've used it on, I've found it to be a design aide rather than alternative to design. The abilty to test and verify components / sub-components independently has made it easier for me to make rapid changes and try out new design ideas.
The difference I've experienced with TDD is reliability. The process of working out component interfacing on smaller levels of component at the begining of the design process, rather than later, is that I've got components I can trust will work earlier, so I can stop worrying about the little pieces and instead get to work on the tough problems.
And when I inevitably need to come back and maintain the little pieces, I can spend less time doing so, so I can get back to the work I want to be doing.
回答9:
For the most part I agree that TDD does provide a sort of design tool. The most important part of that to me is the way that it builds in the ability to make more changes (you know, when you have that flash of insight moment where you can add functionality by deleting code) with greatly reduced risk.
That said, some of the more algorithmic work I've contracted on lately has suffered a bit under TDD without a careful balance of design thought. The statement above about safer refactoring was still a great benefit, but for some algorithms TDD is (although still useful) not sufficient to get you to an ideal solution. Take sorting as a simple example. TDD could easily lead you to a suboptimal (N^2) algorithm (and scads of passing tests that allow you to refactor to a quick sort) like a bubble sort. TDD is a tool, a very good tool, but like many things needs to be used appropriately for the context of the problem being solved.
回答10:
There are many informal opinions here, including the popular opinion (from Jon Limjap) that bad results come from doing it wrong, and claims that seem unsupported by little more than personal experience. The preponderance empirical evidence and published results point in an opposite direction from that experience.
The theory is that a method that requires you to write tests before the code will lead to thinking about design at the level of individual code fragments — i.e., programming-in-the-small. Since procedures are all you can test (you still test an object one method at a time, and you simply can't test classes in most languages), your design focus goes to the individual methods and how they compose. That leads, in theory, to a bottom-up procedural design and, in turn, to bad coupling and cohesion among objects.
The broad empirical data substantiate the theory. Siniaalto and Abrahamsson, (Comparative Case Study on the Effect of Test-Driven Development on Program Design and Test Coverage), ESEM 2007, found that "Our results indicate that the cohesion may be worse (even though Beck claims that TDD produces highly cohesive systems). In our second study we noticed that the complexity measures were better with TDD, but the dependency management metrics were clearly worse." Janzen and Saledian (Does Test-Driven Development Really Improve Software Design Quality? IEEE Software 25(2), March/April 2008, pp. 77 - 84) found that “[T]he results didn't support claims for lower coupling and increased cohesion with TDD”.
A literature review will uncover other publications furthering these cases.
Even my dear friend Uncle Bob writes: "One of the more insidious and persistent myths of agile development is that up-front architecture and design are bad; that you should never spend time up front making architectural decisions. That instead you should evolve your architecture and design from nothing, one test-case at a time. Pardon me, but that’s Horse Shit." ("The Scatology of Agile Architecture,"
http://blog.objectmentor.com/articles/2009/04/25/the-scatology-of-agile-architecture)
However, it's worth noting that the broader failure is that people think it's a testing technique rather than a design technique. Osherov points out a host of approaches that are often casually equated with TDD. I can't be sure what's meant by the posters here. See: http://weblogs.asp.net/rosherove/archive/2007/10/08/the-various-meanings-of-tdd.aspx.