invariants
A test that breaks when you rename a field is not a test. It is a copy of your code written twice.
I kept seeing this. Every refactor broke something. Every requirement change meant touching tests that had nothing to do with correctness. The tests were wrong. And nobody talked about it.
Code modeling the world is a lie.
There are two kinds of errors. Domain errors: the fiction was wrong, the requirements changed, the world moved. Programming errors: you made a mistake. Off by one. Invalid state. Null where null shouldn't be. Most tests try to catch both and end up catching neither well.
Invariants are mainly for the second kind. A precondition is a contract: this must be true before we start. A postcondition is a promise: this will be true when we finish. An invariant is a law that holds no matter what path the code takes.
They live in the function, not just in the test, because functions can be called from anywhere. It takes two to contract puts it simply: the caller promises the precondition, the function promises the postcondition. Every boundary is a contract.
Good types are just invariants the compiler checks for you. Make illegal states unrepresentable. Parse, don't validate. The boundary between domain and programming errors dissolves when the type system is expressive enough.
Mostly avoid unit tests that mirror your domain model. They are not testing correctness. They are testing whether two implementations of the same fiction agree.
Test correctness, not behavior. Behavior changes.