Cram your test driven development up your ass
ww2.samhart.com
ww2.samhart.com
Here are things TDD gets me in my everyday work:
* I don't have to refresh the web page and re-fill in my forms to see if the record saves correctly this time.
* I don't have to log out and log in as a different user to see how things work
* I can plan an API much easier with tests that I write first.
* I know that my features are done when my tests pass
* I can write new tests to prove that bugs my users encounter are really bugs. (You can't get everything tested the first time, but fixing bugs is much easier with tests)
* I can upgrade to a new version of a framework or library because when stuff breaks, I have a roadmap of what I need to change. I can then give a customer an accurate estimate of what it will take to do the work cos I KNOW what I have to fix.
You're doing it wrong if you write ALL your tests first. You write one, and you only write enough test to cover the feature you're implementing. then you implement the code to make it pass. This iteration takes no significant time at all to an experienced programmer, and automated tools can run in the background, monitoring your code for changes and only running the tests your code impacts. Because we're programmers, and we know how to write code that does that.
You think this is a joke? Something to be ignored? Go ask a CPA (accountant) how they do the books.
They sure don't just do the math once and say "trust me I'm really just that good." They balance the books. Dual-entry accounting. They do this because they are disciplined professionals. And people pay big money to accountants to get that right. Your tests shouldn't exist to exist, they should prove your code does what you want. Checks and balances.
Of course the path to this is the same path one takes to learn a new language. It will be slow to start. It was for me, but really, it's such a huge win for my long-term productivity. I am happier, my clients are happier.
Really, what's not to like?
Can't we just for once and for all realize that some techniques work well in some situations, and for some people, but that there is no golden bullet?
Anecdotally, all the guys I know that drone on about BDD/Rspec/Cucumber/TDD Dejour... never actually use it.
I try to think about how I'll test something when I'm coding it (so that it's easy to test), I try to consider what I'd actually be testing in a given piece of code (so the concerns are separated), and I try to stick pieces near each other that are coherent (so they can use similar setup/teardown for a testing context as needed). But I don't have the actual test code down.
I'll fight anyone that says automated tests aren't wortwhile though.
QuickCheck is a Haskell framework. It allows you to formulate properties about your functions. Like --- given any list of comparable items, `sort' should conserve its length, produce an ordered list, and conserve the occurrence (and non-occurence) of each item. It should also be idempotent.
QuickCheck then generates a lot of random inputs and tests whether your properties hold. You (almost) never have to write test cases by hand. And since Haskell is pure, you never have to care about setting up the right state before invoking your tests.
This approach allows me to code with success, even when I am not intensively focused. (Of course Haskell helps with this even without QuickCheck. Not having to worry about state, assignment, order of execution, loop indices or recursion keeps the number of items I need to hold in my working memory low.)
I wrote a QuickCheck-eqsue library for Lua, FWIW: http://silentbicycle.com/projects/lunatest/ (It's the first released version, and the documentation still needs work...) For table "objects" and userdata, it checks the metatable for how to generate an arbitary instance.
Yes. There's even a paper about how to guarantee the balance invariants in a red-black tree with the type system. (Can't find it now, it's probably written by Ralf Hinze.) You can see an implementation at http://www.cs.kent.ac.uk/people/staff/smk/redblack/rb.html
Many people use unit tests as a crutch for a lack of strong typing.
Example:
/* old */
LoadWeapon(WeaponTypeEnum type);
UnloadWeapon(WeaponTypeEnum type)
LaunchWeapon(WeaponTypeEnum type);
/* new */
WeaponCommand(WeaponTypeEnum type, WeaponCommandEnum command);
Note... assume that the interface is a GUI, so changing these methods doesn't change any external interface.Alas, it's difficult to tell if the strawmen being setting up (e.g., tests preclude estimation) are part of a serious argument or just more dramatic effect to help underscore the general point.
I wish this post were separated into two articles, one of which discussed the genuine tradeoffs of TDD (such as the possibility of painting yourself into a corner), and the other of which contained all the spurious, unsupported assertions (tests cause scope creep, tests are blinders, etc.) One would make excellent discussion fodder, and the other, well, the Delete key is right over there.
Edit: I think the people who oversold TDD in the first place ought to have known better, though. Either they believed their own silver-bulletism or they cynically computed that it was the only way to push something in the corporate software world. I'm not sure which is worse.
But I did want to bring up an issue with the second part of your comment, TDD (Test Driven Douchebags), you are emperors without clothes. That added very little positive contribution to the conversation, and dragged your comment's overall value much lower than it should have been.
Just to point out a line from the guidelines... When disagreeing, please reply to the argument instead of calling names. E.g. "That is an idiotic thing to say; 1 + 1 is 2, not 3" can be shortened to "1 + 1 is 2, not 3."
(Of course, if I were interviewing you, I wouldn't have asked you about TDD anyway)
Love it? Hate it? I don't care anymore. I know what works for me and that's enough.
I feel the same way about strong inferred typing.
Get assigned ticket for new feature. Write test outlining how it would work with the assumption that the existing code does what it says it does.
If you find - surprise! - the code doesn't do what it says it does, create a ticket and recurse.
Compared with trying to make the tiniest possible change and running into gotcha after gotcha, this is a far more sane and sanitary procedure. It takes longer, but in the end "your" code meets requirements and you can prove it.
Having said that, articles that are titled "Cram your X up your ass" don't pass the conversational sniff test with me. That's not the way you talk to strangers. So either you have some kind of emotional problems, nobody taught you how to express yourself in writing without sounding like you're seven, or you're trying to manipulate me through hyperbole.
Shame. Bet there was some good material in there somewhere
For the experienced or good programmers TDD is a hassle and often results in less efficient code and often in less code. (read: less done in the same time). Later tests are written to cover the code which is of course arguable. [1]
For the beginning or (below) average programmers TDD is great as it often results in much better code quality and helps produce more robust code. [2][3]
Here is an excellent article about TDD and its aspects. http://use.perl.org/~Ovid/journal/38616
[1] TDD Effective or is it ? http://www.theruntime.com/blogs/jacob/archive/2008/01/22/tdd...
[2] In favor of TDD, http://collaboration.csc.ncsu.edu/laurie/Papers/TDDpaperv8.p...
[3] Indications that TDD increases productivity http://portal.acm.org/citation.cfm?id=1070618.1070834
Please, present a more thoughtful argument without the wild-eyed zealotry.
The problem is usually with zealotry and dogma itself, though. If you approach the problem from the angle of, "Ok, let's try to figure out where TechniqueX is a good fit and where it's a poor fit," it gets rid of most of the arguing. It also tends to highlight the people who persist in arguing that TechniqueX will solve all problems for all people, all the time.
It doesn't drive blog traffic as well, though. (The middle path usually avoids all that drama...)
- Extra, often useless up-front development This paragraph just makes less and less sense the more I read it. I think he is ranting against testing spikes, because spikes will get thrown away. Well, know what? Spikes are not tested. Spikes are written to see what is possible and thrown away after this.
- Development with blinders on Well, his point is: you might develop the wrong thing with TDD. Well, duh. I write my tests in the wrong direction, and thus, my code is written in the wrong direction. Good. Throw the tests away. What remains is: my code is written in the wrong direction. Where are tests evil there?
- Tests are weighted more than the code Here he shows that he has pretty much no clue about the second meaning tests have (besides checking the code a bit): Interface design. By using your unit in a unit test, you can design your interface, and usually, it is easier to get the interface in a nice, usable way, because you are using it already. Thus, if you write the test first, you first think about the interface of a unit (or, at least a small part of it) and later on, you implement this. So, is it bad to think about nice, clean interfaces first?
- Coding yourself into a corner This is another of those points of the kind 'Your development practice is bad, because you might end up in a dead end'. I hate those points, because they are so general that they can be made about ALL development practices, unless you are god.
- Narrowly applicable uses I can 'just' test drive my libraries, datastructures and such? Well. That is a very, very bad 'just'. I watched my brother went nuts when he was working on a compiler with several optimizations heavily based on binary decision diagrams. Eventually, I was able to convince him to just write a bunch of automated tests for this datastructure. He wrote them, found several bugs and everything was done in a few days. So, reiterate: testing the foundation of your application can be of gigantic value, far, far more than his 'just' allows, because, if the foundation is broken or shaking, things break down in unexpected and hard to debug ways.
- Tests solve problems that don't exist His point: Tests don't prevent all bugs in a software. Again, a point I can just answer with 'well, duh.'. The only thing which could prevent all software would be a correct formal verification of the program (which is surprisingly hard). So, this point simply holds for ALL real-life programming practices again.
- Scope creep Point: Separation of concerns is bad? Management of Requirements is not well done in TDD? Well, if his point is the first one, he would just disqualify himself completely. If his point is the second one, his point is that some code creation strategy has no clue about managing requirements. Well, duh? Managing requirements is in the management area, TDD is in the area of coding things, so they are entirely orthogonal.
- Inefficiency Yes, I have to give him this point. If you have tests, reworking things can take longer, if the product radically changes. It has to be noted that this radical changes are reached on the assumption of fauly requirement engineering, which will cause major problems with all other development methods either.
- Impossible to develop realistic estimates of work His point: TDD has no clue about estimations. Well, duh. Estimation is, in the world of Kent and Extreme*, a management and planning problem, and a development strategy has no clue about planning and managing.
Don't get me wrong, I don't want to be one of those "Do TDD or I whack you with a flyswatter"-guys. I am just a humble, fellow programmer who has learned that writing the right tests for the right components early can be a blessing. Certainly, writing the wrong tests or writing too many senseless tests can be bad, I give you that. Tests cannot make all bugs disappead, I give you that (I already had to track down some really nasty bugs which occured due to subtle assumptions which were not obvious from the tets). I also give you that you might write more code, and I give you that some things cannot be tested. But please, give me that my tests catch a lot of stupid errors, like off by one errors, or mistypes. That my tests help me to define the interface I want to used, that my tests tend to encourage modularity, because modularity increases testability. That userstories, and the acceptance tests for them help me to guide myself where to develop to (and yes, once you have the userstory and the acceptance test, it is race-horse style ;) )
If you had a bunch of tests you could just re-run, then re-testing the entire system becomes trivial (it's a given that you'd need to rewrite the tests for the changed parts, but if your tests are written well, then you won't need to rewrite them all).
I like how he links to the Wikipedia article on Horse Tack, but doesn't bother to back up any of his claims.
One also wonders how, or by whom, you might wish his argument to be "backed up". A compelling argument should stand or fall on its own merits.
Heck, in "Tests solve problems that don't exist" he sidesteps right past the real problem that TDD solves: refactoring code without inadvertently breaking something else.
So yeah, "No true Scotsman" would ever rail against TDD without proper evidence.
In that section he's complaining specifically about the type of tests that TDD results in -- that they strongly tend to test for totally unrealistic bugs, structural crap that would only possibly come up when writing the code from scratch.
He's not anti-test, he's anti-test-first.
When I am coaching, mentoring, or training people, I have no problem with bringing up both TDD and automated testing at the same time.
* We write a failing test
* We then fire up the automated test runner
* We make changes to our code until the test passes
* We repeat, adding new tests.
I can show that our automated test runner catches anything we do wrong immediately.
I don't believe you get the full benefits of TDD if you don't automate the process, and you can't have automated testing without good tests. I used to write lots of code without tests and I paid the price - lots of wasted time tracking down bugs, delayed releases because I could't figure out why my new feature broke old stuff, the usual stuff.
Investing time and energy learning how it's supposed to work has been the most valuable thing I've ever done for myself, my clients, and my company. The process I teach others is the process that I learned, and I feel that we're all much happier developers for it.
I just want to come back to the point I made earlier... a lot of the people who evangelize this stuff believe in it not because it's "cool", but because it has actually saved us and our projects. If you want to separate the hype machines from the true believers, ask one to show you. The ones who believe in it will almost certainly devote their time to show you how they do it. I know I would.
In all fairness, the language about type systems is often impenetrably mathematical, so I'm not surprised that the parallel isn't clear. (I really like OCaml, so I tried reading _The Definition of Standard ML_ to get a better understanding of the language family. Wham, brick wall. Then again, I'm not a mathematics grad student...I studied history.)
* Which is just a different way of communicating semantic constraints ("this is never null", "this list always has at least two unique UTF-8 string values", "this can only be used on writable files", etc.) to the language and having them automatically checked.
That example seems trivial.. but here's a real example: I had a very complex health insurance system I helped write. A mistake in the formula could have cost someone thousands of dollars in premiums. We asked the end users to give us names of people and their health scores. "Jon, with this height, weight, bmi, and all this other criteria should get a score of x"
Writing the test cases first helped out a ton. Even with a compiled strong typed language, I still would have needed tests to make that work.
And it's been six months since then and I've heard of no issues with the calculation routine. From that, I'm reasonably happy with test-driven development and that's why, regardless of compilers or type systems, I still find it vital to my success as a developer.
Type systems are also "just another tool", though, and more useful in some cases than others. I wonder how many other properties about a program could be declared, inferred, and verified for the complete domain of input at compile time -- this is safe to use concurrently, that is fully isolated from non-deterministic input, this can have its results memoized, those can fully evaluated once at compile time and don't even need to run in final executable, etc. (Haskell can do some of this, but I don't like lazy evaluation as a default, among other things.) Playing with Prolog and logic programming has gotten me curious about how far that sort of analysis could be pushed.
About the insurance example -- I know exactly where you're coming from. I've done something similar for the architectural industry, and a huge suite of example results helped tremendously.
* And very likely Haskell, SML, Clean, and others, but my "advanced type system" experience is mostly with OCaml.