Previously, I talked about the perspective that the reason to write tests is to catch bugs, and this is a good thing all around. Now I want to talk about code design - about using tests to help me design my code well. Some people argue this is the "true meaning" of "Test-Driven Development".
Unit tests can point me towards good design: if its difficult to write a good test, it means the code is poorly factored, especially that the thing I want to test is inappropriately coupled with something I don't care about right now. Introducing indirection at this point can open my code up to a valuable abstraction.
The "good test" that is "easy to write" will look something like:
testSubject = new Foo(/*initial state*/);
result = testSubject.Bar(..);
That's Arrange-Act-Assert, with one line of each. No need to write comments to that effect; no need for blank lines. (There are a few other similar forms in the 2-4 line range.)
This kind of test is only possible if the code under test is not tightly coupled to the rest of the system. More than just "programming to interfaces" so I can inject dependencies, I look to eliminate dependencies. I don't have much setup code. I don't use mocks because I don't need to.
It's not just about "testing a class", it's also about "testing a business rule". If I can test my business rules this way, they are DRY: each piece of knowledge has exactly one canonical expression in my codebase. I minimize emergent phenomena, so my whole system is easier to reason about.
Since I have unit tests that express my business rules, I use terminology from my business domain in the tests. That terminology will flow in to my system-under-test, giving rise to ubiquitous language (within my bounded context of course.)
This kind of test will naturally be super-fast and completely reliable, which supports the "catch bugs" value described before. But I also write a lot fewer bugs, because I have well-factored, well-named, decoupled, DRY code that is easy to reason about. Writing fewer bugs is more effective than trying to find-and-fix the bugs with tests.
I can treat bugs as another kind of design feedback: I can ask what made it easy for this bug to appear, and look for a way to eliminate the whole class of bugs. I may use a unit test for this purpose, but simply writing a test for the specific bug is not enough - I want to address the whole class.
Refactoring is still really important, and (unless I have great tools in C# or Java) I must count on the tests to protect me while refactoring. But now I have the advantage that a) my code is relatively well-factored already, and b) my tests are helping me figure out good ways to refactor, so refactoring is much more fruitful.
You only get this value if you listen to the feedback your tests are giving you.
This value appears when tests are written.