Call a function twice. One notification remains in the database. A duplicate-prevention test turns green. It is tempting to stop there, particularly when the change itself looks small.
But suppose the same test passes after removing the new guard. What did it actually establish?
Take a constructed notification service. The application checks whether a notification exists, while a unique database constraint independently rejects a second row. A test that only counts rows observes the database's final state. It may say nothing about whether the application attempted the second operation.
A final state can hide an extra action
The distinction becomes visible when delivery happens before the rejected write. The database can contain one row while two messages were sent. This is a hypothetical failure sequence, not a report of a production incident.
| Requirement | Useful observation |
|---|---|
| No duplicate stored records | Final records and enforced uniqueness |
| No repeated creation attempt | Attempts recorded by a safe test double |
| No duplicate delivery | Delivery calls and their identities |
These are different requirements. A storage constraint is valuable, but it should not receive credit for protecting an unrelated side effect. I would keep the constraint and isolate the application behavior in a test environment. Removing a production safeguard to make a test fail would be a rather expensive way to learn this.
There is a tradeoff. Asserting every internal call makes tests brittle when the implementation changes. The observation should therefore follow an external requirement: one delivery, no second charge, or no additional creation attempt when that attempt itself matters. It should not follow whichever helper happens to be easiest to spy on.
Make the old behavior fail for the right reason
For a regression test, I want to see the failing input exercise the old behavior and then pass with the fix. An import error or a broken fixture is not the red part of that demonstration. The failure should explain the user-visible problem being repaired.
A sequential two-call test also cannot establish safety under concurrency. Two requests that both inspect an empty state before either writes need a separate scenario. Naming a test narrowly is useful: it stops a small successful check from quietly becoming a much larger claim.
Before adding another test, I would remove the fix locally and ask whether the current test notices. If it stays green, the next task is to understand that result. More green runs will not answer the question.
한국어 글: 고치기 전에도 통과하는 테스트라면
