> If tests can not be written easily for a class, it has to be refactored.
How do you make sure that the refactored class does the same thing as the old one? Rewriting old code that you don't have test coverage for is way riskier than whatever small change you were going to make to it.
I write a lot of code without tests because a lot of legacy codebases aren't set up to be testable, but they work, and it's important to the business that we're able to deliver small bug fixes and incremental improvements on the existing code while we write whatever replacement system we want to write. As I work on them they'll slowly get more testable, but if you're abandoning working code because it has no tests, you're usually making the wrong decision. (Which the author recognizes.)
Refactorings worthy of the name aren't scoped by class. In fact refactoring is one of the strongest arguments against unit tests; refactoring typically changes the split of responsibilities and alters the articulation points in the design, such that old tests are discarded and new kinds of tests need to be written.
Solid integration tests may work, but it's hard to get really good coverage in any reasonable running time from integration tests.
These days I try to cover the happy path with a fairly integrated flavour of test, the edge cases around the tricky bits of code, and fairly exhaustive coverage for authentication / authorization code paths, and not a whole lot more.
What properties do you write tests for, though? Presumably you're touching the code because there's something wrong with it. How do you know how much of it is wrong? How do you know that all callers are actually thinking the current behavior is wrong, instead of one caller misbehaving and another caller expecting it (possibly because someone noticed and worked around it, and now that workaround is going to break)?
Tests are simply the implementation of knowing what the code is expected to do. If you don't have any basis for that expectation, writing tests is meaningless - either you test the current behavior of the code, which doesn't help you change anything, or you test your imagined behavior of the code, which doesn't help you validate anything.
I agree, and commend how well you've noted the problems when code is written without tests. Such a codebase becomes mentally exhausting and expensive to probe into; much more expensive than the original time saved by ignoring testing practices altogether. Sure, a huge amount of legacy software may not have tests, let alone comments. Then yes, it's like where do you even begin and have any confidence in what youre testing for. But ignoring proper testing practices in new, modern codebases, especially in a business where the single product or service offering is software, is extremely risky and irresponsible. This is a little ranty because why are devs justifying not writings tests in 2018!
How do you make sure that the refactored class does the same thing as the old one? Rewriting old code that you don't have test coverage for is way riskier than whatever small change you were going to make to it.
I write a lot of code without tests because a lot of legacy codebases aren't set up to be testable, but they work, and it's important to the business that we're able to deliver small bug fixes and incremental improvements on the existing code while we write whatever replacement system we want to write. As I work on them they'll slowly get more testable, but if you're abandoning working code because it has no tests, you're usually making the wrong decision. (Which the author recognizes.)