Tests' Influence on Design
github.com
github.com
The reason I think this is important is that ideas about TDD people had from 2000-2007 still has a profound impact on testing tools and practices more broadly. For example, very very few developers I meet know how to apply test doubles rigorously and what their purpose is. This might help that.
As far I've seen, the "designing by writing tests" approach, mostly succeeds in the web-apps field where one has an implicit/assumed CRUD/MVC structure, writes tests for behavior and filling in functions based on this assumed structure.
I haven't seen anything about how one proceeds with TDD if one has the more complex task of implementing an algorithm, a language or something with highly structured requirement. I'd be interested in how one would do that.
The process he's using there falls in line with the higher-level experiences described on the wiki.
Has anyone had this experience/success with this? What can help make the difference towards getting that nice flow out of TDD.
I'm inspired by Corey Haines and a bunch of code experiments he's illustrated over the years. One technique that struck me was that a nil return from a method was was an expected and desirable step during a TDD cycle and that guard clauses can be very helpful to driving out TDD designs while maintaining flow.
I'm looking for similar patterns for TDD decision making, especially those that help the dev stay in a good rythm.
Would love any ideas towards exploring this further.
There is a lot of solid reasoning (and examples) leading up to this point, but one notable point was this conclusion:
``` [London-school TDD] sits at the extreme end of the trade-off between coupling and design feedback: incredibly rich feedback about the design of the system, typically resulting in small, hyper-focused units with carefully-chosen naming and easy-to-use APIs. They come at the cost, however, of significantly decreased freedom to refactor the implementation aggressively, which is why Discovery Testing recommends developers default to deleting-and-redeveloping local sub-trees of dependencies when requirements change significantly. This can result in reliably comprehensible designs, but with an increased base cost to requirement changes. ```
I really agree with this. I spent a few years working with a consulting company which preaches London-school TDD. We did a lot of work in dynamic languages, and refactoring was particularly difficult because we had to make sure that all of our mocks (and other test code) lined up with the refactored APIs.
I hate to take the discussion here, but I wonder how Static Typing changes the experience of London-school TDD. Does it become easier and less-frustrating to refactor your test-code?
I am working on a Rails project and a Haskell project simultaneously; the Haskell project requires far fewer tests because the compiler can catch so many potential bugs. Then when you add ghci (Haskell REPL) it becomes almost as flexible and exploratory as a dynamic language. Some Haskellers even like to say that TDD stands for Type-Driven Development. Combine that with REPL-driven development, add in property-based generative testing with Quickcheck, and you have a lot of very powerful tools to help you build the thing right.
To expand on the theory here:
Specifically, this is a tension between coupling and cohesion. Coupling means dependencies between modules; cohesion means dependencies within a module. High cohesion reflects a module’s focus on just one responsibility, which really does need everything in the module to get done.
> > Woah, increased coupling is always a bad thing, right!?
Increased coupling is always a bad thing – all else being equal. But if increased coupling results in increased cohesion too, it may be worth it. Higher cohesion is better, and lower coupling is better.
> All use is coupling. All reuse is additional coupling. A system with minimal coupling is a system without any abstraction or invocation.
How do you know you don’t want such a system? Because it has very low cohesion. The duplicated parts all over your codebase will only be used by a few parts of their modules, rather than most of their modules.
Note that cohesion is measured relative to the size of the module, not as an absolute count of connections. If you pointlessly extract the full body of a method to another differently-named method, you have increased the number of intra-module connections. But you have decreased cohesion, because the module is now bigger, yet your new method is only called once within the whole module.