Tests are there to support and ensure defensive programming
Defensive programming protects the integrity of the system at runtime.
Tests are (mostly static) diagnostic tools. At runtime, your tests are nowhere in sight. They're like scaffolding used to put up a high brick wall or a rock dome. You don't leave important parts out of the structure because you have a scaffolding holding it up during construction. You have a scaffolding holding it up during construction to facilitate putting all the important pieces in.
EDIT: An analogy
What about an analogy to comments in code?
Comments have their purpose, but can be redundant or even harmful. For example, if you put intrinsic knowledge about the code into the comments, then change the code, the comments become irrelevant at best and harmful at worst.
So say you put a lot of intrinsic knowledge of your code base into the tests, such as MethodA can't take a null and MethodB's argument has to be > 0
. Then the code changes. Null is okay for A now, and B can take values as small as -10. The existing tests are now functionally wrong, but will continue to pass.
Yes, you should be updating the tests at the same time you update the code. You should also be updating (or removing) comments at the same time you update the code. But we all know these things don't always happen, and that mistakes are made.
The tests verify the behavior of the system. That actual behavior is intrinsic to the system itself, not intrinsic to the tests.
What could possibly go wrong?
The goal with regard to the tests is to think up everything that could go wrong, write a test for it that checks for the right behavior, then craft the runtime code so it passes all of the tests.
Which means that defensive programming is the point.
TDD drives defensive programming, if the tests are comprehensive.
More tests, driving more defensive programming
When bugs are inevitably found, more tests are written to model the conditions that manifest the bug. Then the code is fixed, with code to make those tests pass, and the new tests remain in the test suite.
A good set of tests is going to pass both good and bad arguments to a function/method, and expect consistent results. This, in turn, means the tested component is going to use precondition checks (defensive programming) to confirm the arguments passed to it.
Generically speaking...
For example, if a null argument to a particular procedure is invalid, then at least one test is going to pass a null, and it's going to expect an "invalid null argument" exception/error of some kind.
At least one other test is going to pass a valid argument, of course--or loop through a big array and pass umpteen valid arguments--and confirm that the resulting state is appropriate.
If a test doesn't pass that null argument and get slapped with the expected exception (and that exception was thrown because the code defensively checked the state passed to it), then the null may end up assigned to a property of a class or buried in a collection of some kind where it shouldn't be.
This might cause unexpected behavior in some entirely different part of the system that the class instance gets passed to, in some distant geographic locale after the software has shipped. And that's the sort of thing we're actually trying to avoid, right?
It could even be worse. The class instance with the invalid state could be serialized and stored, only to cause a failure when it is reconstituted for used later on. Geez, I don't know, maybe it's a mechanical control system of some kind that can't restart after a shutdown because it can't deserialize its own persistent configuration state. Or the class instance could be serialized and passed to some entirely different system created by some other entity, and that system might crash.
Especially if that other system's programmers didn't code defensively.