可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have been reading about Agile, XP methodologies and TDDs.
I have been in projects which states it needs to do TDD, but most of the tests are somehow integration tests or during the course of project TDD is forgotten in effort to finish codes faster.
So, as far as my case goes, I have written unit tests, but I find myself going to start writing code first instead of writing a test. I feel there's a thought / design / paradigm change which is actually huge. So, though one really believes in TDD, you actually end up going back old style because of time pressure / project deliverables.
I have few classes where I have pure unit tested code, but I can't seem to continue with the process, when mocks come into picture. Also, I see at times : "isn't it too trivial to write a test for it" syndrome.
How do you guys think I should handle this?
回答1:
I find it interesting that none of the responses so far have touched on what I consider to be a fundamental insight into modern development practices, and that is that the "old fashioned" way of writing software by gathering requirements, doing analysis and modelling the desired system before writing any code actually had a lot going for it.
TDD actually embodies this to an extent.
To write a test you first have to know - to put things in very simple terms - what your inputs will be and what your expected outputs will be.
Once you have that knowledge you can write a test to exercise some piece of mythical code and also, to an extent, what other artifacts that code will interact with, before you write that code itself or create those artifacts.
This is what we previously would call "requirements engineering" and "systems analysis" in the old "waterfall" method(s).
Further than that, you will find that once you grasp the requirements at this level, writing tests will come naturally (it is, after all, merely the expression in code of the statement of functionality embodied in those requirements).
And in writing the code that expresses the requirements in the form of tests, you will identify gaps and misunderstandings in the requirements before you've committed those gaps and misunderstandings to the project in the form of executable code.
For modern practitioners of "Agile" methods to admit that they are engaged in a series of "waterfalls" is, I think, too embarrassing, so this need for requirements engineering and understanding is obfuscated behind language that talks around the need to address these things whilst trying desperately to avoid admitting that "Agile" (as commonly understood, or perhaps misunderstood) threw the baby out with much of the bathwater.
回答2:
So, as far as my case goes, I have
written unit tests, but I find myself
going to start writing code first
instead of writing a test. I feel
there's a thought / design / paradigm
change which is actually huge. So,
though one really believes in TDD, you
actually end up going back old style
because of time pressure / project
deliverables.
You may want to try being absolutely disciplined about TDD for a fixed period - say a couple of weeks or a month. When you catch yourself writing code before tests, delete it and start over, with a failing test. Do this until you know you can, until you know what it feels like - and then you've got the ability to do it, and the freedom to make an informed choice - and understand that it's OK if that choice is sometimes to code first. But don't let habit drive you away from good practices. First get the good practices down and then choose when to apply them (and of course you may find the answer to that is "always", too).
I have few classes where I have pure
unit tested code, but I can't seem to
continue with the process, when mocks
come into picture. Also, I see at
times : "isn't it too trivial to write
a test for it" syndrome.
Mocks are one thing - are you good at them yet? If you're not comfortable with mocks, and you're in a situation where mocks are appropriate, then practice until - like TDD - you're good with them.
With respect to the "too trivial" issue - not for the first couple of weeks. Just TDD all the time. And when you find that TDD is driving you to a better design - even for "too trivial" code - note it. Think about what your code would look like without it. And you may discover that no code is too trivial to TDD. Or, not. But my point is, try it, seriously, for a significant period, and then you can make better choices. Get comfortable with it, then evaluate.
回答3:
It's simple:
By learning to think about the "What" __before__ you think about the "How"
In other words, think about exactly what you want to do (interface), rather than you how you are going to do it (implementation)
TDD, as a design tool, based on my experience, really helps you look at things from the user perspective than the coder perspective. In addition, I think TDD helps your mind really thinks about what exactly you are trying to do, what's the expected outcome, etc.
So next time when you are trying out "TDD," ask yourself what it is you are trying to do, and just start writing the code that expresses your intention.
Example:
Say, someone wants you to write them an integer adder.
The person that does not have a TDD mindest will simply just do:
int add(int a, int b)
{
return a + b;
}
Is the above code correct? Sure it is. But this approach, based on my experience, fails when you have a complicated component to write. (That's why you asked this question in the first place; I know, I had been there before (perhaps still? lol))
The great thing about TDD is that it forces you to give attention first and foremost to the interface (the what) of the system, while not asking you immediately for the implementation (the how).
In other words, if someone asked me to write an adder, I would have something like:
void assertOnePlusTwoEqualThree()
{
assert( add(1,2) == 3 );
}
Notice, how, even before even thinking about how the add() is supposed to work, I already ironed out a few things. That is, I already figured out:
- the interface of my adder both inputs and outputs
- the first trivial test case (unit test for free!!)
then you implement the add().
See, it doesn't matter if the logic discussed here is so simple to implement. To have a TDD mindset, is to apply it all the time with no exception. You have to do it so many times that you don't even think about it anymore. It's just part of how you design. I can say this because I saw it happened to me professionally (it took about a year of perseverance).
Just like how if you always do a good job on programming, regardless of the complexity at hand, you approach your job the same way.
Lastly, I think TDD is comparable to "code sketching." That is, you start trying out to see if the interface works well for each scenario (test case). So it's okay if you end up changing the interface, and the names, etc. See, what I've learned is that a lot of times, design is simply about understanding the problem thoroughly. TDD is one tool that allows you to do that.
Human mind, IMO, works easier with concrete examples (test cases/scenarios) rather than abstract thinking (how to implement something). By having test cases, it allows your mind to gradually learn about the problem you are trying to solve at hand.
It's okay to not know (going back to the "traditional" way...relax, let your mind adjust). It's okay if it's not perfect (writing some implementation code is perfectly okay...your brain is just trying to figure things out). Just keep trying, keep reading, keep coding, but never give up! =)
回答4:
"So, though one really believes in
TDD, you actually end up going back
old style because of time pressure /
project deliverables."
Actually, I don't think this can be possibly be true.
If you are "going back old style", the only reason can be because you do not believe that TDD will produce better code more quickly.
I think that someone stops using TDD because they feel it's better produce poor code more quickly.
回答5:
Try practicing code kata.
A kata is meant to be memorized.
Students of a kata study it as a form,
not as a conclusion. It is not the
conclusion of the kata that matters,
it's the steps that lead to the
conclusion. If you want to lean to
think the way I think, to design the
way I design, then you must learn to
react to minutia the way I react.
Following this form will help you to
do that. As you learn the form, and
repeat it, and repeat it, you will
condition your mind and body to
respond the way I respond to the
minute factors that lead to design
decisions.
Code kata have given me get a sense of what TDD is supposed to feel like. I know sooner when I'm starting to loose the rhythm, to slip back into old habits. At that point I know I need to take smaller steps, run the tests more often, make sure I'm not trying to refactor under red, etc.
回答6:
Discipline.
Make the decision to use TDD and stick to it, it's entirely a matter of your own ability to commit to a process and see it through to a conclusion.
回答7:
Trivial code is where bugs hide. Mainly I think because it's human nature to read what was intended to be there rather than what is there.
I've been trying to discipline myself to follow TDD on all my new projects - it's a hard to break old habits, but I think worth it. It appears to take longer as there is more upfront coding, but debugging time is way down.
Don't think about how you would test the code while you write it. Write tests that exercise how the code should work, then write the simplest thing that makes the tests pass. Lather, rinse, repeat. Your code will probably end up being nothing like you imagined, but will definitely work better.
Also, importantly, write your tests to fail. Writing tests that pass won't help find defects. And this is another very good reason for writing the tests first - if you write the code first, and then the tests, your tests will be written to pass the code, and (unintentionally) biased to test the code rather than the desired result. And how will you know that your tests would fail in the absence of correct code? Since tests are just code, they are susceptible to bugs as well.
So write your test first, then code, bit by bit in parallel. Use the tests to assert that your code is correct, and use the code to assert your tests are correct (yes, it can happen that you are sure your algorithm is perfect, yet the tests are still failing, all because you made a stupid typo in the test. I did it this morning).
回答8:
Buy "Test Driven Development: By Example" by Kent Beck, and read it.
Then, write a failing unit test.
回答9:
How do you guys think I should handle this?
I suggest you think your unit tests as dialogs with your object (assuming you are programming with an OO enabled language such as Java, C#, Ruby, PHP, etc..)
Create a test case class, think in your test method about the interaction you want to have with your objects. Imagine you are talking to an intelligent machine using the method you pass to your objects as verbs, and then use Asserts to check the objects have reacted the way you want them to react.
After a while, you might enjoy this style of programmation so much that you would not expect to program without it ;-)
回答10:
Pair Program doing TDD (Ideally With Someone Better Than You),
Even if it is During Your Free Time
TDD is one of those skills where, at least for me, it's easy to be worse at it than I think I am. My TDD skill always improves when I'm on a TDD project and pair programming with someone who can point out to me when I could be doing TDD better. Even Pair Programming with someone who doesn't know it any better than I do helps me flesh out my ideas better than working alone does.
Find (or start) a Code Retreat or a coding dojo with a specific focus on Pair Programming and TDD.
If your company allows you to, organize one on company time or at least on-site at your company, even if it's only an hour a week, or over lunch.
回答11:
TDD is not so much about writing tests first. If all you want is to write your tests first, then you want the Test-First Technique. The problem is that the Test-First technique in a vacuum (without TDD) ends up decaying into something that is almost worse than having no tests at all.
You said what your problem was yourself:
or during the course of project TDD is forgotten in effort to finish codes faster
Yet, if you were doing TDD, and recognizing all the value it had to offer, your team would understand that it is impossible to get code done faster without TDD as it is what makes the process of programming fast in the first place.
My suggestion is to spend some time understanding the value it can give you by recognizing the kinds of artifacts that a good test can replace. With true TDD, a single document fills all of the following roles/needs:
- Test (does this work or not?)
- Analysis (what are we trying to accomplish?)
- Design (how are we going to accomplish it?)
- Specification (how will we know when we are done?)
- Finish-line (hey, we're done!)
- Progress report (hey, are they done, yet?)
- Process control (what do I do next?)
...et cetera. It is my opinion that committing to learning TDD - which is a hard discipline and will take a lot of practice to master - requires a clear understanding of the real prize that is at the end of the trail.
Once you've got those things clearly in your mind, you can start worrying about how to do TDD.
回答12:
When you are in a big mess of legacy code I found Working Effectively with Legacy Code extremely useful. I think improve you motivation for TDD allot even though it is about writing unit tests before you do any changes to your old legacy code. And from the undertone of your question it seems like this is the position you have been.
And of course as many others pointed out discipline. After a while forcing your self you will forgot why you ever did it another way.
回答13:
A lot of coding practices take ... practice.
You can learn and understand the concept of OO programming in a few hours. But many programmers will admit that it was only after practicing OO programming for about 2 years that they suddenly felt "fluent".
A C++ programmer can easily "learn C#" in a matter of days. But it still takes a lot of use before they will be "fluent" and understand the full implications of everything they are writing (rather than being a C++ programmer who converts the syntax to C#).
I originally learned to type in the "two fingered" manner. This advanced to the point where I could type pretty fast with this method, but I decided to try to learn touch-typing. For several weeks I forced myself through the pain and frustration of having to type everything really slowly using touch typing. It was hard, and required real commitment because with every line I typed I just wanted to bash it out with the way that I already knew, the way that would give me quick results. But eventually my touch typing speed matched my two-fingered typing.
I resisted the MFC prefixing conventions (e.g. "m_" and "p" prefixes) when I first started working with MFC. It seemed ugly, ridiculous and pointless extra typing. Then I forced myself to use this system for a month and after this time I couldn't understand how I had ever written code without this naming convention - it helped me understand the code better and faster, and reduced the number of silly bugs.
So how do you develop the discipline to do TDD? Just commit yourself to doing TDD for a month. It will be slow. It will be hard. It will be tempting to just bash out a few lines "the old way". But after a while you will build up experience and speed, and it will become much easier and more natural to work this way. And then you can evaluate TDD and non-TDD and decide which approach works best. You probably won't look back.
回答14:
TDD is really a good practice (But at the same time I don't think that we should try to achieve 100% code coverage).
Its main advantages are:
It makes developer think about the API because he needs to use that API first to write tests. At this step it's very easy to feel that something wrong with your API (bad method's names, to many lines are required for most common operations, too many odd checked exceptions or contrariwise user have no possibility to handle presumable exceptions). And you can improve it and change it as you like because nobody has used it yet.
You think about how your methods should work. This often opens some implicit problems or misunderstandings at early steps.
When you have tests you have a nice way to measure percentage of completed work (i.e. if you have 20 tests and 15 of them are passed then 75% of work is finished). It's more important for you as a developer than for you team or process. You just divide the whole task into many smaller tasks (e.g. 20) and can compete them one after another. It's much more pleasant than to take on the whole thing.
Tests allow you to play with the code. You can do refactoring, you can improve performance etc. And again, even if you won't do it anyway it's very nicely to realize that you have the possibility to do that. It's kinda confidence that everything is fine.
How to make yourself to do TDD? You don't need! If you want to gain all listed benefits then you won't make yourself - you'll just do TDD with pleasure and of your free will. If you don't need/want/able to gain them - don't do it now.
回答15:
Your TDD Mantra for the day...
The T in TDD is TODO or Task Driven
I prefer to encourage people to consider that TDD is about setting out tasks (aka. Tests, or Specs). (Tasks incomplete are Red, Tasks complete are Green.)
Having a continuous test runner which displays the results of your tests/specs is a major help in getting the habit for testing. Personally I think it's this critical feedback loop which makes TDD immediately feel productive / encouraging.
Of course, the moment you start to refactor and alter the codebase, you get immediate feedback on regression errors, too, and that's all part of the benefit.
回答16:
TDD is all about knowing what you are doing and what you have done. If I have a properly TDD setup, then I can comment out some line of code that is perhaps not needed and then immediately run the tests and find out if it indeed was needed by tests breaking - or not by no tests breaking. Those breaking tests should contain enough description that I can understand why this line of code was needed.
Without TDD identifying what breaks when you change code requires potentially massive human regression testing before you are sure you have not broken something. The goal of TDD as with all parts of Total Quality Management is to remove most if not all need for expensive manual testing at the end of the product line. How? By knowing what you are doing. How? By measuring what you are doing and studying how to improve it.
If you discover a bug in your code and fix it. The questions should be: how did it happen? Can I prevent this from happening in future? How?
This may be down to poor requirements. This may be down to factors outside your control. The key is to not just brush problems under the carpet but try to understand and bring out the knowledge of how to do so and incorporate it in your processes.
TDD is only part of this and will not work well without a compatible and encouraging environment.
To get this management needs to buy into the basic principle that agile methods make changes cheaper in the long term. With agile methods the cost of change rises logarithmically whereas development without agile methods makes the cost of changes rise exponentially over time.
There is much more to read on this. Google the following "William Edward Deming", "Total Quality Management", "Cost of Change Curve".
回答17:
My team is trying to get better at following a TDD approach - I have to say that it's not the easiest thing with a larger development team.
Everyone in the team needs to think with their test-first hat on before writing any production code. This can be difficult, as despite what people may say they'll do, there's no guarantee once they get to their keyboard.
For some managers it may appear that TDD is an expensive approach, as I do believe it can take slightly longer to code something (and that is what the bean counters see), but it's short-term "pain" for long-term gain. You then have (hopefully) well written, proven code, with the mechanism in place to pick up errors in the future when things change.
For anyone not convinced, give TDD by example a read and give it a try for an iteration of your software.
回答18:
TDD is very difficult to get started with if you're in a vacuum, and nearly impossible to do when other people on your team are only paying lip service to TDD. My advice is to find a place (in or out of work) where you can either program by yourself, or better yet with a committed friend or colleague, and just start doing it--all the time, and without exception.
TDD (and Agile for that matter) only starts to click when you and your team are all behind it and committed to making it work. You need to get some TDD wins under your belt, and it will make a believer of you. THEN come back into your other projects and be a champion for it. Start somewhere where you can get those crucial early wins though. It makes all the difference.
回答19:
IMO, TDD with test / code / refactor - sounds great & fun but:
- there is no hard-rule that it has to be done in that sequence
- Many times, I found myself writing the code & then writing the tests & finding interesting nuggets which I missed
- Other times, I went test > code > refactor cycle & enjoyed it all the same.
- I think the important aspect is to have unit-tested code & enough coverage to ensure that all the important paths are covered. The order of achieving it is not so critical.
HTH.
EDIT: To clarify my points further
- Although I prefer by starting with writing tests, sometimes it is easier to understand the landscape by writing code (especially for new functionality and/or new projects) & then writing tests
- But, if I am refactoring code and/or fixing bugs, I will first check if tests are written & they sufficiently cover what the peice of code is doing. If not, first I will write the test & then do the refactoring.