Write tests that describe a promise
Move beyond happy-path examples and protect the behaviour another person or service is actually relying on.
A test can pass while the application still breaks its most important promise. This happens when the test describes implementation details more precisely than it describes behaviour. Checking that a helper was called is not the same as checking that a person received the correct result.
Name the promise in ordinary language
For an image-upload endpoint, the promise might be: a supported file becomes an accessible image owned by the authenticated user. That sentence suggests more useful tests than “upload returns 200.” It includes validation, persistence, access, and ownership.
It also reveals what is outside the promise. If thumbnail generation is asynchronous, the initial response should not imply the thumbnails already exist. A test can enforce that distinction by checking the accepted job state and then checking completion separately.
Use examples to map the boundary
Parameterised tests make it convenient to run the same behavioural check against several inputs. Pytest supports this directly. The important work, however, is deciding which examples expose different rules rather than adding many nearly identical happy paths.
For a function that validates an image dimension, useful cases might include the smallest allowed value, the largest allowed value, one just outside each boundary, and a value of the wrong type. If the API accepts dimensions as text, test the parsing boundary separately from the domain rule. That separation makes failures easier to interpret.
Test the thing that can actually go wrong
A mock repository can help isolate business logic, but it cannot prove a real database uniqueness constraint works. A mocked network client cannot show what happens when a dependency accepts a request and its response is lost. Place integration tests at boundaries where the real behaviour matters.
For example, two concurrent attempts to reserve the same unique name should be tested against the storage mechanism that enforces uniqueness. The desired outcome is one accepted reservation and a meaningful conflict for the other, not merely two calls to a mocked method. PostgreSQL's constraint documentation explains the database-side rule; the application must still translate its outcome appropriately.
Make failures useful to the next person
Tests should explain the violated expectation without requiring someone to reconstruct the entire system. Use descriptive case names, focused assertions, and deterministic setup. When a test depends on time, control the clock where practical rather than sleeping and hoping the scheduler behaves consistently.
Avoid asserting every incidental detail of a response when the contract does not require it. That creates noisy failures during harmless refactoring and makes important failures easier to ignore. Be strict about externally meaningful behaviour and deliberate about everything else.
A useful suite is a collection of promises the team can read. Its value is not the number of green marks, but how quickly it tells you that a change has broken something another person was entitled to rely on.
Further reading: Pytest: Parametrising tests and PostgreSQL: Constraints.