On top of this, I've also come to find unit tests to generally require more work and be less useful for identifying regressions than integration tests for the work I do. Maybe this is just a result of the kind of work I do though.
On top of this, I've also come to find unit tests to generally require more work and be less useful for identifying regressions than integration tests for the work I do. Maybe this is just a result of the kind of work I do though.
- 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.
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
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.
If you want to take a more exploratory approach and make up the requirements as you go along, fine, but that won't look anything like TDD.
You can use TDD as part of an iterative specification process as well, but then you need to apply it for each iteration: determine the new specifications, write new tests based on those specifications, and update the code until the tests pass. Then revise your specifications based on feedback and repeat the process.
Consider Martin's primes kata, at 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.
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.
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.
I was actually surprised what the author was describing was somehow _not_ TDD. I thought it was pretty normal to try some things out, make some decisions, and then return to codifying those decisions in tests.
We do web app development, and 90% of our tests are browser-based integration tests that test whether the given inputs (usually forms) lead to the expected outputs (usually something printed on a web page).
I wonder if this is more natural when you're mostly writing integration tests?
I dont really get why the practise is so intrinsically linked to the practise of writing unit tests.
Increasingly I'm starting to believe unit tests are a scam, but TDD is going to stick around in one form of another forever.
https://blog.thecodewhisperer.com/permalink/integrated-tests...
It often helps to start with the design first (even if just a napkin sketch). Once I’ve done that I usually have enough information to write system tests (which also forces me to consider edge cases). Then I can then fill in with unit tests as needed as I write the code to get the system tests for that feature passing.
I've observed the same, but having the ability to run any given function in isolation is huge for diagnosing problems. Does this function respond correctly to the correct input? Yes? Ok, that's not where the problem is. Next.