3 ms·
It's not that simple though. His own article "Canon TDD" [0] still involves writing a test before writing the corresponding code. In my experience, the practica
by deergomoo 3y ago
It's not that simple though. His own article "Canon TDD" [0] still involves writing a test before writing the corresponding code. In my experience, the practicalities of all but the most far-removed integration tests (i.e. when receiving this request, server ultimately returns this response) mean that I would be effectively doubling my workload by having to repeatedly refactor two sets of code as I explore. The TDD steps listed do include refactoring if/when required, but I'm not entirely sure Kent had exploring and comparing half a dozen different approaches in mind for that one. It still seems fundamentally geared towards implementing a solution you've already arrived at.
I'm not disputing TDD as an effective strategy—I've seen it work wonders for some of my colleagues and I will still occasionally employ it myself when making additions/changes to well-established bits of code. But, for me personally, I feel I can stay in the flow and get all the same benefits by just doing step 1 ("Write a list of the test scenarios you want to cover"), implementing the code, then actually implementing those tests at the end.
[0] https://tidyfirst.substack.com/p/canon-tdd https://tidyfirst.substack.com/p/canon-tdd
- jdlshore 3y agoIf you have to refactor both, something is still wrong. Tests should check behavior, not implementation. Refactoring should change implementation, not behavior. If refactoring requires you to change your tests, something is wrong with the way you test, or with the way you refactor. One common possibility is that you’re writing isolated unit tests (using dependency injection to prevent the code under test from calling its dependencies) rather than sociable unit tests (the code under test runs normally).[1] You do you, though! As long as the code works and is easy to refactor, it’s all good. [1] For more about writing sociable tests, see “Testing Without Mocks: A Pattern Language.” I’m on my phone but it’s easily googleable.
- deergomoo 3y agoI regret using the word "refactor" in the context that I did, because I think we're talking about slightly different things. A better way to word it would be that I would have to rewrite both my code and the tests many times. When I'm in the exploratory phase, it's not just the implementation that's changing rapidly, it's the interface and structure of each piece too. I find it far more natural to discover as I go, even though that means I throw away a huge amount of the total code I write. Consider the GP comment's example of having everything in one file to begin with, then splitting it out into separate pieces as becomes apparently appropriate. There's no unit test that would survive that process without equal levels of modification, even though the overall behaviour of all the pieces when combined never actually changes. I suspect most developers have got a lot more of this stuff ironed out before they actually start writing code, but my brain doesn't work that way. Thanks for the recommendation too, from the introduction that article looks really interesting, I'll be sure to check it out.
- jdlshore 3y agoSorry to belabor the point. Again, if what you're doing works for you, more power to you. However, your unit tests should survive starting with one file and breaking it out into multiple files. That's exactly what evolutionary design is, and how I program too. If your tests can't survive it, they're probably too caught up in implementation details. (Or you've fallen into the common trap of defining "unit test" too narrowly.) Also, if you haven't read it, check out Martin Fowler's Refactoring book. It shows how you can do things like splitting one file into multiple without rewriting. What I do is test-drive the simplest implementation I can think of, which will often be one module or class, then factor out additional classes as I discover them. While I'm doing that, I'll leave my tests as is, and they continue to cover the code equally well. As I split things out, I'll typically discover more edge cases I want to support, and I'll test-drive those edge cases as well. I'll typically move any relevant tests at that time, possibly rewriting them to be more specific to the unit under test, but the important thing is that I don't have to, and I'll wait until I have the design figured out in my head before I do so.