6 ms·
Problems with TDD
- kscaldef 17y agoI'm overall sympathetic to the opinions in this article, but I think he's gone astray in the section "Complaint: TDD freezes the API too early". One of the ideas of TDD is that writing tests guides the API, rather than forcing you into an API. That is, if you write tests based on the actual business requirements, and write them first, that process will lead you to discover the best design of the API. Note that for this to work, you have to be writing tests based on the real use requirements. Simple synthetic unit tests will not help you here, and may lead to exactly the problem the author is complaining about.
- vgurgov 17y agogood article. can give few examples myself when too much focus on code perfection led startups to dead... just get things done, man!!
- ShabbyDoo 17y agoI find TDD impossible for all but the most trivial of coding tasks. As the article asks, what should the resulting API look like? The act of solving a problem gives me insight into the actual requirements, and the API results from that knowledge. Another issue I have with unit testing is that the cases which, for me at least, identify the most bugs pre-production are more integration-ish. Does the timeout setting for the HTTP API work as expected? Does this transaction really roll-back against an Oracle DB with the "thin" driver on JBoss? These are cases which are really hard to catch with traditional, use-the-UI testing, either manual or automated. I should read more about what others are doing for these semi-integration cases. A couple of years ago, I committed a test case called TestHowDatabasesWork because of a team argument about how Oracle handled concurrency and locking. It got a good laugh, but it also documented a few behaviors specific to the problem at hand.
- btilly 17y agoA couple of years ago, I committed a test case called TestHowDatabasesWork because of a team argument about how Oracle handled concurrency and locking. It got a good laugh, but it also documented a few behaviors specific to the problem at hand. There is a good possibility that in documenting what you saw working you failed to notice some important details of its optimistic locking policy. If you're ever again faced with such questions about Oracle I highly recommend picking up an appropriate book by Tom Kyte. For a developer in particular I would recommend http://www.amazon.com/exec/obidos/tg/detail/-/0072230657 http://www.amazon.com/exec/obidos/tg/detail/-/0072230657.
- steveklabnik 17y agoSee, I really enjoy freezing the API early. The API should be very, very simple. I tend to code by designing top down, then coding from the bottom up. APIs should _not_ be from the perspective of "this is how I solve this problem" and should be from the perspective of "this solves my problem." For example, TempCalc tc = new TempCalc(); tc.f = 50; System.out.println("50 degrees farenheit is " + tc.convertToCelcius() + " degress celcius"); is bad design. Yes, you may need to get a farenheit value, and change it to celcius, but I don't care about setting internal state, then calling functions to change it. I just want an "fToC(int f)" function. This is an admittedly contrived example, but I see this principle in API design all the time. Everything should be from the perspective of the user, not the coder. "I'd love to use this api if it looked like this." Anyway... as far as that integration stuff goes, have you used Selenium? I'm not super up on Java testing stuff, as I'm a Rubyist, but I use it, and I know it has Java bindings of some kind...
- ShabbyDoo 17y agoSelenium 2 (the Google "Webdriver" stuff) works quite well and isn't specific to Java in any significant way. If you can bring up your UI with a bunch of mocks/stubs under it, Selenium is a great way to do automated regression testing of your web UI. On your API design comments: My issue with TDD is that it's implicitly big-design-up-front. I used to think I understood a problem before I stated coding, but I don't anymore. So, anything I design in the beginning ends up being modified (if not scrapped entirely). There's no better way for me to understand a problem than by trying to solve it. Also, for important enough APIs (public stuff, widely used in a codebase, or whatever), the design of the API is dependent on the problem being solved. Let's say you are building an API for a yet-to-be-developed photo storage SaaS. For any solution of significant sophistication, can you really think through all the issues that have API design implications before attempting to create the service itself? Is it better to stream back the bits of a photo found via a tag search or should a "ticket" in the form of a GUID be provided instead? I don't know, but I'll have a better idea once I spend some time solving the problem.
- steveklabnik 17y agoThanks for confirming on the Selenium thing. I guess it just comes down to personal taste, then. I feel that I can't understand how the coding should be laid out until after I've designed how it will be used.
- CWuestefeld 17y agoAgree. TDD in the middle tiers of the app is easy, but that's not where the difficulties lie. Testing the UI layer is quite difficult (at least in the absence of something like MVC, which too many developers can't handle and only want VB-like or WebForms-like coding). And in any paradigm I've ever heard of, real testing of the bottom layers of DB interaction is darned near impossible. As the OP asserts, imposing a well-defined testing regimen is where most of the bang for the buck derives.
- btilly 17y agoTesting all of the ins and outs of possible DB behavior is out of scope for most software projects. You need to assume your driver works until proven otherwise. As for testing interactions with the database, one approach that I've seen work is to have data in your test cases with which you build up database objects that you then interact with. If your classes all have appropriate helpers and appropriate ways of inserting reasonable defaults on fields not explicitly set in the testing code, this makes testing the expected database interactions fairly straightforward.
- steveklabnik 17y ago> And in any paradigm I've ever heard of, real testing of the bottom layers of DB interaction is darned near impossible. Is it that because the code is written in an untestable way? A pretty standard mocking approach should be able to test this.
- mbrubeck 17y agoIn my experience, problems "at the bottom" usually occur because underlying third-party components like the database (or the filesystem or the network stack or the remote server) don't behave the way you thought they did. They're buggy or undocumented, or you just misunderstood their specs. When you mock them away for testing, the mock objects act like you expect and don't have the same quirks as their real-life counterparts. If you don't mock them away, then you have hard-to-run or hard-to-reproduce tests, or tests that give different results with different instances of the external dependencies.
- pilif 17y agoI do agree with the general sentiment of the article, but on the other hand, you should begin writing tests before it's too late and the code is too interdependent to test without a LOT of effort. When you write your tests first, you will naturally never end up in that situation. Of course, when you see that you have a hard time at writing unit tests because, well, the units are too big, then you can (and should!) start refactoring, but how do you refactor with confidence when you don't have tests to see whether the refactoring went well? Chicken and egg I guess.
- dalke 17y ago"but on the other hand"? Isn't that the same hand? "My own practice is to have good, automated tests, but these don't get put into place until the cost/benefit ratio makes the tests worthwhile; which is rarely at the start of the code development and always by the end." and "The important requirements are to have good, complete automated unit tests, to develop code for testing, ..."
- michael_dorfman 17y agoI think that TDD is like Wittgenstein's ladder, which can be discarded once you climb through the window, or the Buddha's raft, which can be discarded once you cross the river. TDD offers novice (or stuck) programmers two powerful nudges: 1) to break tasks down to trivial units, and 2) to think about what you're trying to accomplish before writing the code. Once you get the hang of doing this, TDD is just dogma to cling to.
- steveklabnik 17y agoI think this is a great insight. I know that I prefer test-first because I haven't been testing for very long, and if I don't test first, I don't get around to testing.
- michael_dorfman 17y agoThat's great, if it works for you. I certainly never discouraged my employees from using test-first, if that was what helped them write good code. But it's a long way from there to a categorical imperative.
- dalke 17y agoGoing from Wittgenstein to Kant's categorical imperative - nice!
- Nwallins 17y agoDon't mistake the instance for the class. The class of categorical imperatives, per Wikipedia: A categorical imperative ... denotes an absolute, unconditional requirement that asserts its authority in all circumstances, both required and justified as an end in itself. Whereas Kant's Categorical Imperative is an instance of that class, specific to morality: Act only according to that maxim whereby you can at the same time will that it should become a universal law.
- dalke 17y agoWittgenstein isn't the only one who owns a ladder, and there are many boats and even bridges one can use to cross a river. Some rivers you can even wade or swim across. Testing is not a treacherous expanse of water crossed solely by own's own efforts. But that's just a quippy response. More seriously, TDD doesn't really have nudge #2. I showed examples where TDD-based solutions weren't thought through well enough to be declared done, and I showed that in the prime factor case that the tests aren't sufficient to be good tests for the implemented algorithm. Specifically, tests should help others verify that the code works (which is not a goal of TDD), and the lack of tests with prime factors greater than 3 is a warning flag that should have been addressed during the development iterations. That the code happens to works (for sufficiently small numbers) is not the issue - the issue is that the tests aren't good enough to remove reasonable doubt.
- MicahWedemeyer 17y agoThis is a truly excellent article, and I really appreciate the tone. Calling attention to the false dichotomy between pure TDD and test-last development especially spoke to me. In the real world, everything is a shade of grey, and the same is true with software testing. Puritanical pursuit of theories and practices is silly. TDD is useful in some instances and not in others. A skilled and experienced programmer should be able to know when it's appropriate and when it's not.
- darkxanthos 17y agoGreat article. I enjoyed how well the author builds his case and argues for it rather reasonably. While I do disagree with his article on the whole, I do agree that TDD is not the end all be all. There is good TDD and there is bad TDD. TDD is a very general practice and what really defines your tests as good or bad is really your OOD skills. One thing (really two things that work hand in hand) that I've found to augment my OOD skills so that I write better tests and better software is domain driven design and behavior driven testing. Using these techniques together has helped me to learn to ask better questions of my domain experts, create more decoupled and flexible software, as well as solve that age old how do I test the UI when it isn't already in an MVC framework (the correct answer is to treat it or portions of it as their own domains imo). This is the kind of article that provokes thoughtful discussions by having a well thought out opinion and not just blabbering the first idea that comes to mind. Thanks and keep it up!
- gokhan 17y agoA classic example of getting TDD wrong. Happens all the time. TDD is about design, the article is actually criticizes unit tests. TDD helps you achieve SOLID principles, which really helps in real world. Fibonaccis, primes etc. are only for demonstration purposes. And 100% coverage is for sissies, only a TDD noob talks about it in this context since it's not sane.
- dalke 17y agoIf the demonstration programs contain problems (ie, the tests aren't good enough and the implementations contain unexpected performance and overflow problems, as I discussed), then why should I expected the practices to scale up to more complicated problems? SOLID applies to OOD, and an essay I don't plan to write is why I consider OOD to be the wrong solution for most cases. In any case, my point is that TDD adds very little to the process of developing good software. I mentioned several times that TDD provides weak tests and is insufficient to guide the development process. With examples. I did not criticize unit tests and I said that that's what I use in my own work. The 100% coverage reference I gave comes from Kent Beck. I'm surprised that he's considered a TDD n00b. You are more than welcome to reply in more detail.
- gokhan 17y agoI couldn't be able to pass the first steps of applying Scrum where I work for years, while web was flooded with positive experiences. Than, I met with a person with firsthand experience and found the flaw in my thinking, I was trying to follow everything in the book. Some people combines Scrum with their existing process, some set 4 months iterations while others insists on not more than a month. Some can't live with a Scrum Master who is a part time developer. By the book, they all wrong. Yet, they're successful Scrum implementers. So here's my account of TDD, regardless of what Mr. Beck said in the book (I am actually an Alt.Net follower): Unit tests and TDD, for me, are totally different things. TDD is a way of construction, while unit tests are for inspection. Unit tests' re-validation power is great, but less and less needed if you have a good design overall. Hence, - TDD should not care to emphasize good test cases. It shouldn't even emphasize a complete test suite against a method. If someone want to test every possible way a function can be called, there are even automated software for doing it. It's a QA thing, not design. - Worst case scenarios should be left to your test framework. Same as above. Row attribute in MbUnit. Range, Random, Combinatorial etc. attribute in NUnit (I'm from .Net world). - No approach should give someone confidence that his code works all the time. Even the best QA can't catch all. But "When the bar is green, you know where you stand." means you are safe on what you test. If you test only for the correct input, you're safe for it. You'll know that missing inputs are untested. (Just a couple of unit tests, though) - There's no 100% coverage. Since 100% coverage might also mean a <div>'s positioning. Screw it and all the UI breaks loose. Critical paths should be covered as much as possible (some 80/20 rule applies here. %20 of tests cover %80 of critical path etc.). - When you want to change your api, do so. Write new tests, then a new api. The tests you have written are not part of your code real estate. Either feel totally ok for throwing them away, or don't use them. You will break your mental model of the old api when writing the new one without tests (we all write unit tests in our heads when coding), so what's wrong with breaking the digital representation? - SOLID is nice, don't overuse OOD. But, what's wrong with having OCP everywhere all the time? Need OAuth? Extend your account service. Want to also publish to twitter from your app's main feed? Extend feed service. Unit tests can be written good or bad. It depends on one's ability, testing framework in use, even the mocking framework. But in my experience TDD only improves your code, makes it more modular, more open to extension, more open to change.
- omouse 17y agoBoth the fib and prime factor examples are shit. The tests are individual cases which is treating the input as a Cartesian product (a point on a line since these are single-variable functions). What you want are sets. The inputs for both are all natural numbers. However, this doesn't tell you much, there's still a lot of state to work with. So what you want are end conditions, but again that doesn't tell you much. Tests are no substitute for reasoning and TDD is just another fad.
- JulianMorrison 17y agoWhy do people still write unit tests? You are stabbing in the dark, in a very nonrandom way, and you're doing all the donkey-work manually. If you care about testing, use a quickcheck clone. Bonus: it's much easier to refactor tests when you're saying "it takes an X and a Y" instead of "it does this and then that". And if you discover a bug caused by a corner case of Y and add it to your Y simulator, then everything that takes a Y gets tested with that corner case.
- awa 17y agoIn the code kata's I have been to, we use the following model to TDD: 1. If (all tests pass), Do one of the following: a. Write a failing test b. Write a passing test c. Refactor d. Start a 2 min discussion 2. Else (If a test is failing) Fix the code. I would imagine the above model to TDD would fix many of the complaints in the article.