The tests worth writing
Coverage measures which lines ran, not whether anything is verified. What actually catches regressions is narrower and cheaper than a coverage target.
A suite can hit high coverage and catch nothing, because coverage records which lines executed, not whether any assertion would fail if they misbehaved.
What catches regressions is a smaller, more specific set of tests than most coverage targets produce.
Test the boundaries you promise
The highest-value tests sit at the edges of your system: an HTTP request in, a response out. They exercise routing, controllers, models and views together, and they break when any of it breaks.
They are also the most stable, because they assert on behaviour a user could observe. Refactor freely underneath and they keep passing — which is exactly what you want from a test, and exactly what heavily mocked unit tests fail to deliver.
Test the logic that is genuinely hard
Anything with branching, arithmetic or ordering deserves direct tests: a pricing rule, a date-boundary calculation, an eligibility check, a parser.
These are cheap to write, run instantly, and are where bugs actually live. A test that a model responds to an attribute is not one of them.
Test the bugs you have already had
This is the highest-yield habit available and almost nobody does it consistently.
Every production bug becomes a test before it is fixed. The test fails, the fix makes it pass, and that specific failure can never silently return. Over a year this accumulates into a suite shaped by your actual failure modes rather than someone's idea of what should be tested.
A regression suite built from real incidents is worth more than one built from a coverage target, because reality chose the cases.
What to stop testing
Framework behaviour. A test that validates :email, presence: true rejects a blank email
is testing Rails, not your code.
Deep mocks. A test that asserts a method called another method with particular arguments passes when the code is broken and fails when it is refactored — the exact inverse of useful.
Exhaustive permutations of trivial logic. Three representative cases plus the edges beat forty combinations that all exercise the same branch.
Speed is correctness
A suite that takes twenty minutes stops being run. People push without it, or run a subset, and the value collapses regardless of what it covers.
The usual culprits are database setup per test, real HTTP calls, and sleeps. Fix those and a large suite runs in seconds. A fast suite that tests less catches more, because it actually gets run.