Great questions! I actually have a whole lot to say on this but that'd have to be a whole new article.
For greenfield, functional programming is the answer. You don't need to test "changes deeper down the call stack" because there's no such thing as a change. Pass a value, get a value back.
We've written large complex applications with two flavours of code:
unit-tested functional code.
functional/acceptance-tested mutable/side-effecting code, which wires up the functional code.
Both flavours test-driven, not test-after.
You say that this legacy code is not necessarily bad but missing testability. Assuming this, I say the following:
Approach it by repeatedly extracting as much purity from mutable parts as you can. You'll find there's a lot of code that does not actually need to be mutable.
Turn the left-over mutable parts into immutable parts.
Only do this incrementally and not in a single step. Small commits, small pull requests.
The cost of fixing is still less than rewriting from scratch. See Joel Spolsky's article about "Things you should never do".
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Great questions! I actually have a whole lot to say on this but that'd have to be a whole new article.
For greenfield, functional programming is the answer. You don't need to test "changes deeper down the call stack" because there's no such thing as a change. Pass a value, get a value back.
We've written large complex applications with two flavours of code:
Both flavours test-driven, not test-after.
You say that this legacy code is not necessarily bad but missing testability. Assuming this, I say the following:
The cost of fixing is still less than rewriting from scratch. See Joel Spolsky's article about "Things you should never do".