4 ms·
I think this highlights that the core of TDD is understated. The core of TDD is encapsulated in the saying Red, Green, Refactor - Write the Test, they're going
by no_wizard 5y ago
I think this highlights that the core of TDD is understated. The core of TDD is encapsulated in the saying Red, Green, Refactor
- Write the Test, they're going to fail cause you have no implementation
- Write the implementation, so that those tests are green
- Refactor to clean up the code, make it real nice
Rinse and repeat as needed, until you've settled on a fully tested solutions. If you do this, you are practicing TDD as its intended. I think the overall message around TDD, how it gets talked about in industry, and how its gotten promoted (or not promoted, I guess?) makes it so confusing. This is the heart of it here, is to rinse and repeat these 3 steps, always starting with writing Tests first, which is to validate that you understand what you're building
What I have found in my decade+ doing this is that most of the time when I run into team members who draw blanks or feel that TDD is a roadblock is that's one way or another they don't have enough information about the requirements of the work involved in what they're doing.
Not sure if this helps anyone or not, this has been my general experience and may not capture every case, though I feel confident enough that this is a shared experience that I hope it brings another way to think about TDD in terms of simplicity.
- eesmith 5y agoI've never liked what's missing under the red-green-refactor TDD description. Perhaps your experience can offer insights? Consider Martin's primes kata, at http://www.butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata http://www.butunclebob.com/ArticleS.UncleBob.ThePrimeFactors... . The final version is: public class PrimeFactors { public static List<Integer> generate(int n) { List<Integer> primes = new ArrayList<Integer>(); for (int candidate = 2; n > 1; candidate++) for (; n%candidate == 0; n/=candidate) primes.add(candidate); return primes; } } In red-green-refector TDD, where do I add tests that are expected to pass? In this case, boundary analysis says that if the function takes an int, then I should include tests for negative numbers, and tests for large values, like 2^31-1, which is MAXINT and also a Mersenne prime. (Neither of these are in Martin's tests, which only test 1, 2, 3, 4, 6, 8, and 9.) When should I add the test for 2^31-1? Your "Write the test" says we should only write tests which will fail because it has no implementation, but in this case we expect it to pass because we have an implementation. Do we not write that test? Which leads to my issue with the "refactor" step of "red-green-refactor." Suppose you add that test for MAXINT. It takes about 5 seconds to run because of it does >2.1B modulo tests. Implicitly, TDD tests are a supposed to be fast. Not 5 seconds per test. Or the spec might explicitly require (say) a 1ms execution time, or you might find that system tests fail because this algorithm is too slow. There are any number of faster factoring methods, as Eratosthenes well knew, so pick one and implement it. Is this in the "refactor" step? Technically "Substitute Algorithm" is one of Fowler's refactorings, so yes. But Fowler's refactoring are meant to make things cleaner and easier to understand. Not whole-sale replacements with additional complexity. In the discussions I've seen, the refactor step in red-green-refactor starts and ends with the same tests. While a more complicated implementation may have its own set of special cases to consider. (For example, a complex sorting method like Timsort needs more tests than quicksort to cover all the code paths.) The descriptions of "red, green, refactor" TDD I've seen completely ignore these issues of when to add additional tests you expect to pass, and how to refactor for purposes other than "make it real nice." I agree that starting with MAXINT would immediately 'validate that you understand what you're building.' But most TDD examples seem to start with the easy cases first, not the hardest. They teach an incremental design approach where experience from the easy cases helps progress towards the final solution. My experience is that approach can lead to an implementation which requires a design methodology more powerful than red-green-refactor to resolve.
- michaelpb 5y agoYeah, I TDD / TFD as much as I can, much simpler. Even just stubbing out a few simple smoke tests that I red/green/refactor saves so much time. For me, I think I spend less time writing code as well, since it forces me to be clear to myself about requirements early on (even if the story/ticket wasn't clear enough), and keeps a "check" on my natural tendency to go down rabbit-holes before properly speccing out non-essential features or tempting optimizations. I ask myself: Does this help me pass the tests and complete the feature? If no, then I need to sit on my hands and not get all code-cowboy trigger-happy :)
- thatswrong0 5y ago> Write the Test, they're going to fail cause you have no implementation But this is where I falter every time: the test necessarily depends on the implementation. I could write a test for my first pass at a function... but then if I decide I need to actually split that function into two or go for a different approach entirely, then I have to basically scrap that test. And then I've just created a bunch of friction that slows down my development process.. for what gain? And to your point, yes, it may have to do with the fact that often my requirements are amorphous and I discover them as I go. For example, say a designer wants a specific behavior, but then I realize it doesn't work in an edge case that we will probably hit, and fixing that edge case will take twice as much time. So then I work with them to find a less time consuming compromise -> bam, new implementation, new tests. Or maybe it's not a user facing behavior, and I realize after 10 hours there's a way simpler way of doing the thing I want. Same thing -> new tests. Once I've got the basic code layout in a satisfying state, only then do I feel comfortable starting tests and ensuring I most or all of my conditional branches -> success and error cases. I feel like I'm missing something
- no_wizard 5y ago> I could write a test for my first pass at a function... but then if I decide I need to actually split that function into two or go for a different approach entirely, then I have to basically scrap that test. Then you're writing a function coupled to the test, not writing logic you can plug into the test to reasonably verify it works as intended. That's the bit people miss I think. Tests should reflect how the code is consumed, and test for that (inputs and outputs, more or less) not for implementation details. Your test shouldn't care if its 1 function or 7.
- saurik 5y agoBut the point was that to know how the function will be consumed before you write it implies you fully specified it ahead of time without the benefit of knowing how--or frankly even if--it will work, which (to me) is backwards.
- 5y ago
- thewarrior 5y agoOkay so I've been programming for many years, work at a well known company and all that. But when it comes to writing tests and designing my code (OO or FP) I still am not sure what the best way to go about it is. There is so much contradictory and sometimes just plain confusing advice. Mocks vs stubs vs no mocks. Integration vs Unit vs End to End tests. TDD vs no TDD. OOP vs FP. Grey beards of HN. Where do I learn the best ways to do testing ? I am happy for any kind of pointers. Whether they be books, talks, blog posts or code bases. Anything outside of the usual examples would be doubly welcome.
- jhanschoo 5y ago> But when it comes to writing tests and designing my code (OO or FP) I still am not sure what the best way to go about it is. There is so much contradictory and sometimes just plain confusing advice. Well, programming is not a monolith. Different high-level requirements demand different standards and processes, even though for many things a standard solution works. A safety-critical product (or just one that can lose lots of money) demands a different process than a toy app.