6 ms·
Getting Stuck While Doing TDD, Part 3: Triangulation to the Rescue
- taeric 10y agoBuilding a sum method.... I get that these techniques should scale to real problems. I hate that I have never really seen them do so.
- mtsmith85 10y agoThat's always where I struggle, too. "How does this scale to real life problems?" That being said, the article goes into that—pretty well, I think—after the sum method. It focuses on an "OrderKindValidator" class that is expanded.
- pzh 10y agoYet real system design is usually so complex that no single technique can cover it. I shudder when I hear TDD proponents claiming that this technique can even be used for emergent design. An amusing example is Ron Jeffries's Sudoku solver attempt: http://ravimohan.blogspot.com/2007/04/learning-from-sudoku-solvers.html?m=1 http://ravimohan.blogspot.com/2007/04/learning-from-sudoku-s...
- taeric 10y agoYes, that is a particularly poignant set of posts. I am still fond of dancing links to solve Sudoku, and I can't easily see how I would have found that through TDD. And yet, among the most agreed ideas you can ever make in popular programming circles is that you are fond of TDD...
- kybernetikos 10y agoIn my experience tdd is useless for discovering algorithms. It is however very useful for implementing them. I've implemented dancing links and i absolutely used tdd for significant parts of the design.
- spc476 10y agoIf I didn't know who Ron Jeffrie was (one of the creators of Test Driven Design) I would have said that those articles were a brilliant piece of satire on test driven design.
- crdoconnor 10y agoThere's a lot of people out there doing unit test driven development that ought to be doing integration/acceptance test driven development. Unit test driven development only really works on "algorithmic" style code. Stuff that's mostly linking disparate systems together - if you try writing unit tests for it you'll end up with a mess of mocks that aren't actually testing if your code works.
- waterlink 10y agoI definitely am using these techniques on real projects with huge complexities to them. Quite successfully. Without bugs. Continuously Integrating and Continuously Deploying to Production. I'm pretty sure, that the case study of such project will take up the whole book, not just 3 blog posts ;)
- thdespou 10y agoI agree that this can work in one liners or very simple programs but it fails to deliver in real world scenarios when nothing is trivial. I just don't get it.
- waterlink 10y agoThank you for your feedback. I'm encouraging you to read the whole article, better, whole series. Because sum method was used to quickly explain Specific/Generic rule on the simplest example. Then it follows through with real-ish (a bit modified) business case, that has gotten me stuck in the past. I know that readers on the internet have limited attention span, so I shouldn't have put a sum method example at the beginning, rather I should have started with the main case study and made a detour before applying this rule to the sum example. Thanks for the feedback again, next time I will try to not make this mistake.
- taeric 10y agoApologies if it sounded overly critical. I actually did read the whole thing. I like the technique. It is very very appealing. I just have a hard time finding evidence in real projects where it has worked. My complaint is akin to the modern complaints on the gang of 4 patterns. Individually they make compelling arguments and if you follow the discussion, it is appealing. And I don't want to throw out the baby, so to speak. I think all of these make fine guidelines. We are just an industry that seems hellbent on finding prescriptive directions where it is becoming apparent none exist.
- waterlink 10y agoTo reassure: I do use this technique on a daily basis. And I have teached it to my team and we are happily applying it. It works well on big complex projects and makes sure you didn't miss any test case, so that Semantic Stability of the test suite is up high.
- waterlink 10y agoAnd thanks for the warm words! :)
- ebbv 10y agoI missed Part 1 so I went back to Part 1 of this series and I have to say, I don't agree with this approach at all. By only writing the simplest/most naive solution to get your tests to pass maybe you are saving unnecessary work, but I feel like most of the time, and certainly in this example, you're just deferring work you know you're going to have to do. So instead of doing the work as you go through the tests you're pushing it all off to the end and then having to write the whole class in mostly one go. If instead you wrote what's necessary to pass each test properly as you went, you'd be building the class incrementally, and importantly you'd have a series of commits which each have worthwhile code in them instead of a bunch of useless slop.
- eyelidlessness 10y agoHonestly I think the real problem is the scope of the tests. The "red, green, refactor" approach is (despite conflicting ideologies) best suited to tests which express a behavior in the domain where you're trying to solve a problem. If you write a "unit" test where the "unit" provides some expected value or outcome absent meaning in the problem domain, of course the way to make the test "pass" is to hard-code some meaningless return value. If you write a test addressing a behavior relevant to the domain you're solving a problem for, and then create the conditions for that test to fail, you're almost certainly going to write code that is meaningful to solving that domain problem in order to make the test pass. The fact that the examples are using Rspec, a BDD framework, but writing traditional input->output test signatures, is a huge warning sign. Those kinds of tests are useful to catch boundary condition regressions (which is to say they're not totally useless; they should be written when there is hairy implementation-specific logic that needs to be preserved). But they're not going to help you write a program to solve a problem for human beings.
- lgunsch 10y agoKent Beck talks about Triangulation in Test Driven Development by Example as just a tool to break out when you are stuck. He doesn't recommend it for every situation, but recommends it when you hit a road-block and need to back off and work around it.
- 10y ago
- mempko 10y agoProgramming by evolutionary process. You may get to the solution eventually, but damn if it's the slowest and most brain dead way to get there. Potential for falling into local minimum is so great it strikes me as almost a fraud. Do people really pay for this kind of work?
- waterlink 10y agoYes, they do, and pretty good. Because it is actually faster and less error-prone once you get familiar with Evolutionary Design. A good read from M. Fowler: http://martinfowler.com/articles/designDead.html http://martinfowler.com/articles/designDead.html - this talks, why a lot of Extreme Programming practices come into the synergy to make Evolutional Design effective (and not only). Drop enough of these practices, indeed, it will stop being effective.
- deleted 10y ago[deleted]
- ereyes01 10y agoI'm pretty fastidious about following TDD practices, and I am very careful to cover as much program state as the language I'm using reasonably allows. However, my mental model for approaching TDD I think is much simpler than the approach presented here. It basically is: Do I _really_ understand the design implications of the unit I'm about to develop? - No: Play around and prototype, and learn about my design, don't worry about tests - Yes: Throw away any prototype and then really do the unit test-first. I found that I gravitated towards this process after I learned (partly thanks to blogs from Uncle Bob Martin) that unit tests are really formal design specs. They are so formal and specific that they are expressed in code- a program describing what your program does. Before you can write one of those (or as many as your unit needs), you need to have a very precise concept of how you are about to proceed, and what exactly the product code is about to do. The benefit of this process is that I prefetch a really deep understanding of the system I'm building. This leads to less bugs, and it leads to sensible, well-documented, and meaningful tests. It just takes a bit of discipline to follow at first, when you're not used to working this way. Some argue that it takes longer to write code this way, especially when I throw away my hard-earned working prototype! It does take a little longer at first, but: - Once I know the answer/outcome I seek, the tested version comes out way faster than the prototype. - I spend much less time later on fixing my own bugs, which I learned are mostly due to my own lack of understanding the full dimensions of my system's design.
- waterlink 10y agoThis is a pretty interesting process you have there. I usually use throw-away Prototypes/Spikes, when I do not know how to write my first test or I feel uncomfortable test-driving the problem. Once I reach enough confidence, I remove spike's code and start test-driving it. I let the design of the system emerge on its own, while I'm listening to how hard it is to write tests and steering the wheel using Red-Green-Refactor (2nd cycle of TDD) and while I'm following Specific/Generic rule (applying Triangulation together with Transformation Priority Premise) (this is the 3rd cycle of TDD). Red-Green-Refactor on its own indeed is very well known for leading quickly to the local maximum of the solution and its design. 3rd Cycle of TDD breaks out of the local maximum of the solution to the problem. 4th cycle of TDD breaks out of the design maximum (4th cycle of TDD - Hourly, we step back and look at the overall design and architecture of the system, taking a careful look at the architectural boundaries and seeing if we are about to cross any of them with our efforts, we proceed with informed decisions steering to the right design we want for the next hour). Here is the Robert C. Martin's blog post on the cycles of TDD: http://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html http://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfT...
- vinceguidry 10y agoI like TDD if the code is already clean and well-tested, and also if the existing test code is clean. If the domain code is clean but the test code isn't, I'll clean up the test code before TDDing the new feature. Shouldn't take long at all. If it is not clean, but well-tested, then I need to clean the code before extending it further. Also the tests will probably be messy, I'll spend time cleaning those up if I have time. But in all likelihood I'll only be able to clean up the code this time around. If it is clean, but not well-tested, then a test-after approach is better. Take your feature-implementing extension, and write tests for both the extension and what it's extending. Then the next time I'll have clean, well-tested code to TDD off of. If it is neither clean nor well-tested, then I wouldn't bother writing tests at all, for this iteration. Instead I'll clean up the code, make my extension, and call it a day. The next time I extend that part of the codebase, I'll use the aforementioned test-after approach. Tests take time to refine and get right. They are an added layer of security, but a much better line of defense is nice clean code that you know what it is doing at a glance. Tests will not save you from bugs, but clean code will make bugs way easier to iron out. The prevailing wisdom seems to be to don't refactor unless you have test code to fall back on and if you don't have that, to write smoke tests. I do not like this approach, I think if you have to deal with unclean, untested legacy code, then you need to do the work to understand it, and the best way to come to an understanding of code is to clean it up. As you clean it up, domain concepts become clearer. Tests are a form of documentation, their intent is to help you understand the codebase. The code itself tells you how it's accomplishing business objectives, tests tell you what those business objectives actually are. Unless you are very unlucky you should have alternative sources for that knowledge. So tests aren't strictly necessary, but they are very nice to have.
- keithnz 10y agoI started with XP in 99 after seeing the wars on OTUG, and then made great use of the TDD approach, I was busy using TDD to build real systems by first getting legacy systems under test and then progressively refactoring and introducing new functionality. At the same time on the mailing lists a lot of the XP consultants started advocating quite "pure" approaches. At first it was "as a learning / thought experiment", but a curious thing started to happen in the 2000s, "Agile" developed a doctrine and religion about it and the free flowing thoughts that were pushing how to design software better started to dry up. A lot of the TDD advice out there at the moment is from the religious stage of Agile. Here's my take-aways on TDD... - It' about designing software through feedback - Code should compose together easily, refactor till it does, be mindful of the tiny details - Test as little as possible (or I think Kent said "as is Responsible"). The tests are there to validate your intent and provide feedback. Tests are liabilities that need to be maintained, so only maintain good ones. learning TDD? do it on throw away problems, play around with how small the steps you can take are. Play around with refactoring, don't have have preconceived ideas about the solution. TDD learning problems with other people. Pay attention how it changes how you design something. Do some bigger things, start noticing the problems of TDD, especially when dealing with outside frameworks and systems. Production TDD? It's a tool and may help provide design ideas or help stitch together preconceived design ideas, use everything in your arsenal to design and create good software, there are no rules or doctrine. But understand as many tools and "rules" that the software community offers and the reasoning behind it. Collaborate with others to create and design, and make sure you have quick feedback cycles. Be quick to recognize new ideas, tools, and techniques that either provide feedback or designs.
- keithnz 10y agooh, one other thought, if you are a TDD advocate and TDD all the time and are quite productive. Try this, code something without tests. A curious thing occurs, your software mostly works fine, it's often correct straight away. Basically, you learnt how to design and code. The tests are just feedback. The code itself should also be surprisingly testable if you wanted to test it (if not, still lessons to learn about design and coding). Now, since you are coding and designing pretty well without tests, pay attention to what actually breaks. Gives you a good idea about what kind of Tests are important. Then think about the minimal test that would have stopped you making the mistake.
- superasn 10y agoBit OT but TDD works great where you have the specs for your project down to a T, but sometimes you're working on a website or project in which you have an end goal and some fair idea of what you're making but not everything is concrete and in such scenarios doing TDD can waste a lot of time (at least it did for me). For such projects at least for my own case I have seen that an Excel driven development (Yes i just made it up) is a really good idea. Instead of writing tests and creating mocks and doing a lot of legwork I just write the function name in an Excel file, with the constraints and output in columns. Hardly takes me 1 minute since I always have the .xls file open. Then I go into a free-flying mode, creating my function and code and never think about it until the project is finally complete. I of course am mindful that I will have to write tests for it later so I do use DI and staw away from Singletons, etc. Then when everything is ready (think v0.01), then I go back to each function in the XL file, and write a test for it creating assertions using the constraints and output I jotted down earlier. This way I'm not writing or re-writing my tests because something I hoped to use in my project has changed during the course of development. After that it is TDD only because the v0.01 is out and now I have to careful that any changes I make don't break anything. I don't think this would work for a corporate environment but for solopreneurs and not so serious projects this can really be helpful. It does seem to work for me at least.
- lgunsch 10y agoTDD is designed for exactly the opposite. If you have a really good understanding of problem and solution from experience, then just go ahead and do it. Kent Beck talks about this in his book "Test Driven Development by Example". TDD does best when you need to be able to respond quickly to new knowledge as you dive into the problem domain or make changes due to migrating business requirements. TDD lets you create your abstractions using concrete evidence, instead of trying to guess. However, I have found TDD to be a difficult skill to master. You cannot just try it out for 6 months and expect to be quicker using TDD than you are without it. Until you have a bunch of experience built up in TDD, it will slow you down a great deal - which is what you may have experienced. It took me about 18 months of continuously using TDD daily to really get a solid grasp of TDD.
- 10y ago
- jgalt212 10y ago> RED is as important as other in Red-Green-Refactor cycle. If next test does not fail, it is either: already implemented, or has to wait until a later time (until it will fail). I strenuously disagree with this point in the following scenario: your new test could validate an edge case that your current implementation may only pass by happenstance. i.e. it's important for whomever refactors the code to pass your new test and all the old tests.
- waterlink 10y agoIf the production code does something by happenstance I will consider it a bug and fix it (unless in Legacy Code, where a bug could mean hidden super-valuable-for-customers feature). I.e.: I will make sure that this test fail and all other tests pass. Production code doing something, that is not defined by tests is generally not a good idea, because this is clearly an untested functionality. And it has to be eradicated if nobody needs it (business and customers) or it should be covered with a test, and I want to see this test fail (by applying Mutational Testing).
- waterlink 10y agoAdditionally I would like to stress that RED step is ultimately important - we have to see the test fail in an expected manner, otherwise we would never know that test is actually working as we expect it to. (for example it does not have any assertions, or they do not execute for some reason).