7 ms·
I really don't get the mantra of "implement only the shortest solution that makes the test pass" I would add a very important second part here "you think is c
by maxnoe 8y ago
I really don't get the mantra of "implement only the shortest solution that makes the test pass"
I would add a very important second part here "you think is correct".
An introduction to TDD that starts by implementing a function called `add` with `pass` or `return 9` is somehow deeply irritating to me.
The example would be so much closer to real world usage if you started with `a + b` and then realized the tests also require you to support 3 arguments.
Also: you introduced the test for three arguments after you wrote the first code for `add`.
- sriku 8y agoIn some ways at least, you can look at data science / ML work as an extreme form of TDD and closer to it in spirit than most proselytizing I've come across. In another great example, Peter Norvig does way more TDD for his Sudoku solver [1] than a "textbook" attempt at it [2] ..like many orders of magnitude more testing when counting cases. The spirit is better if we try to answer the questions of "what do you know about your program" and "how do you know it" through code. [1] http://norvig.com/sudoku.html http://norvig.com/sudoku.html [2] https://xprogramming.com/articles/oksudoku/ https://xprogramming.com/articles/oksudoku/
- petepete 8y agoI'm with you on this. I know it's just an example to get people in the right mindset, but suggesting that a actual programmer would start there is ridiculous.
- wirrbel 8y agoI disagree (although I think I understand that people might find it strange). TDD is in a way a gamification of programming. You go back and forth between being rewarded with a green test suite, to challenging your implementation with another test, to "pinhole-surgery" for improving the implementation. Finally you get the relieve of being able to refactor your implementation without breaking the functionality. So starting with stupid implementations delivers that first reward from the start and helps you find the rhythm of red-green-refactor. Now of course TDD is also about making a correct implementation eventually, and usually you will get there after a handful of test cases.
- jwdunne 8y agoI get where this is coming from but it gets boring fast. That's not how flow works. You don't get into a flow state because of the reward / regular feedback alone. The challenge must match, or slightly exceed, your skill. Otherwise, you get bored. Boredom kills productivity and, ultimately, the point. A competent programmer may as well just write out the addition implementation. I'd argue even that's too low a bar. In fact, I don't think it deserves a test (adding two numbers specifically). That leads me to the second gripe. A majority of TDD examples are trivial problems. A great many are that trivial they wouldn't even warrant the time and expense to test in the real world, which is a genuine factor. If you're spending half your time testing trivial shit that can be verified by sight, you're not spending half your time testing what is crucial that may be difficult to verify without trying out the code.
- afarrell 8y ago> Boredom kills productivity You know that graph[1] with performance on the Y-axis and "arousal level" from bored -> eustress -> overwhelmed on the X-axis? I think that mental model is key to understanding why TDD tutorials start out with really dead-simple examples. One primary cognitive demand when programming is holding many ideas in your head at once, many of which you're only really dealing with for the first time this week. In your day-to-day work, you move from subproject to subproject and have to quickly learn new parts of a codebase. As you are doing this though, you are also holding in your head some ideas which stay there: how the standard library is shaped, what the overall architecture of the project is, who on your team to ask for clarity, and how to use your testing framework. The fact that you already are familiar with these things means that they are "chunked" in the same way that the area code of a phone number is. That means it imposes a lower cognitive cost to hold them in your head. But what if you are currently learning both the idea of TDD and the specific interface of a particular testing library? Then neither will be chunked and you're going to need to devote a lot more brainspace to remembering that recently-learned knowledge. If you are writing complex tests while you do that, you are a lot more likely to be overwhelmed and perhaps give up on TDD. TDD tutorials are written for an audience for whom how to write a basic test is not well-chunked enough to be boring. [1] https://courses.lumenlearning.com/suny-monroecc-hed110/chapter/yerkes-dodson-law/ https://courses.lumenlearning.com/suny-monroecc-hed110/chapt...
- mpweiher 8y ago> "you think is correct". > `add` with `pass` or `return 9` is somehow deeply irritating Similar to others, I understand this feeling. And I would urge you to get over it. First, technically returning 9 for a test of add(4,5) is correct. Your tests define what the function does, not the name. If you are thinking "a correct add adds all numbers", then that isn't wrong, but it is bringing your tacit world knowledge into the code without that knowledge being expressed in the code. And that's Not a Good Thing™. It also seems to me that what you meant here was not "correct" (because it is correct), but "general". And that's one of the points of XP/TDD: as programmers we have a terrible tendency to over-generalise. Really horrible. "Write only the code to make the test pass" works against that tendency (and also has the nice gamification effect mentioned elsewhere). The other point is when to generalise. The first answer is "not", the second is "not yet". Generalise only when you have several instances, otherwise your generalisation will, what's the technical term, suck. Last not least is how to generalise. TDD/XP essentially say that you generalise only as a refactoring, that is a behavior-preserving transformation. So when you add the add(10,2) test case, the really pedantic way of doing it would be to handle the extra case with an if-check, then once the additional test passes refactor the add method to handle all those cases with less code, by removing the case-handling and doing arithmetic. What that means is that you separate the coding that gets the tests to pass from the more challenging bits of coding where you need to generalise, with the latter always under full test coverage. It's a really nice way to make progress. And of course: use your own judgement. If things get to tedious, it's OK to leave out steps. At some point you will probably find that you are running into problems, at that point you want to slow down and re-introduce more of the intermediate steps.
- ramblerman 8y ago> otherwise your generalisation will, what's the technical term, suck. Linus Torvalds didn't write the linux kernel or Git in this manner and ended up pretty well. I get the idea behind TDD, and have used it successfully in areas where indeed the business case was very unclear. But to drink the Uncle Bob Koolaid and pretend this is now the only way to do things is lacking in creativity, and wisdom imo.
- 8y ago
- TeMPOraL 8y agoI agree. In Philosophy of Software Design, J. Ousterhout says that this TDD practice encourages "tactical programming", i.e. trying to solve the current open ticket with minimum amount of work, instead of solving the problem correctly. Personally I'd go further. To me, this approach encourages replacing your brain with a dumb gradient descent process. Instead of thinking about the problem and solution, you just keep writing an ever larger monster whose sole purpose is to interpolate between test cases. Tests themselves are useful. Following a process that makes meaning implicit instead of explicit? Not so much.
- lojack 8y agoI completely disagree with you. In practice you probably don't need to be that strict about TDD, but if you're being introduced to it with no TDD experience at all then you should absolutely be doing this. Dogmatic TDD is the best way to practice and learn TDD. If you don't do this then you _will_ inevitably learn bad habits. Once you learn how to TDD like this you will begin to understand when you don't need to be dogmatic, but that only comes with experience.
- mikekchar 8y agoThere is a reason for the "return 9" trick. The point is to force you to write a test that breaks the previous test. In this style of TDD you need to always be working from "red" (in the red/green/refactor cycle). If your test is "add(4, 5)" and you implement add with "a + b", there is no failing test you can add. However, there are many classes of potential failures. For example, what if "a" or "b" is zero? What if one of them is negative? Even potentially, what about real numbers, etc. If you write your first failing test and then implement it with the entire solution, then you often stop with a single test that doesn't cover the edge cases you care about. However, the real problem is explaining that style of TDD with such a simple function. I would never use a test first style for "add" unless there were some reason to do so (for example, there was some reason to assume that 0 wouldn't work). The "return the answer" trick is really useful when you have a function that has a very complex set of return values. You pick some arbitrary value to test against so that when you are working on the solution, you can see if you've broken then "happy path" test. The "return the answer" bit is not even necessary, but it's handy to ensure that your test harness is working properly. I've often seen people do a "green-green-refactor" style of testing and write hundreds of lines of code only to realise that their tests weren't even running and the code doesn't work at all. It's 30 seconds of, "Can I write a test that makes the tests go red?", "Can I make the tests go green?" OK, let's begin.