Testing as a developer: building confidence before handoff
When I first started writing tests, I tended to think of them as something added after the feature was finished. The code worked locally, so a test felt like an extra task before the pull request.
Over time, I have found testing more useful as a way to understand a change while I am making it. It does not turn a developer into a QC engineer, and it does not remove the need for manual testing or product review. It gives the developer a few earlier signals that the behaviour they changed still makes sense.
Start with the risk, not the test type
Unit, integration, and end-to-end tests are useful names, but I try not to begin by asking which label a test should have. I start with a smaller question: what could this change break, and what is the quickest meaningful way to notice it?
| If the risk is… | A check that often fits |
|---|---|
| A calculation, validation rule or business decision | Unit test |
| A few pieces working together | Integration test |
| A flow a user or caller depends on | End-to-end test |
| Whether it feels right with real content | A manual pass |
If the risk is a small calculation, validation rule, or business decision, a unit test can be enough. It runs quickly and keeps the logic close to the code that owns it.
If the risk is the way a few pieces work together, an integration test can be more useful. For backend work, that might mean a service, a repository, and database behaviour. For frontend work, it may mean a component with its data state, form validation, or a shared provider.
Test the path a user or caller depends on
End-to-end tests are where I usually check a meaningful flow through the running application. A user signs in, fills a form, searches for something, or completes an action. The goal is not to automate every click on every screen. It is to protect the paths that would cause real trouble if they stopped working.
For UI testing, I find it more helpful to check behaviour than implementation details. Can a user reach the main action? Does the screen show a useful loading, empty, or error state? Does the interface respond after an action? These checks tend to survive refactoring better than tests tied closely to a component’s internal structure.
Tools such as Playwright can run these flows in a real browser. They are especially useful for catching problems at the boundary between frontend and backend, where both sides may work on their own but fail when connected.
Keep backend tests close to the contract
For backend work, tests can make a route’s contract explicit. A unit test can cover a service rule. An integration test can check that a repository query or transaction behaves as expected. An end-to-end test can send an HTTP request and check the response, authorisation, and validation at the application’s boundary.
I do not expect one test to prove that an API is perfect. I find it more useful when each test answers a focused question:
- Does invalid input fail clearly?
- Does this action require the right permission?
- Does this business rule still hold after a refactor?
That focus also keeps tests easier to maintain. A test that tries to cover every branch in one setup can become difficult to read when the feature changes.
Tests support judgement, they do not replace it
Automated tests are good at repeating checks. They can tell us that a flow still reaches the expected result, that a regression stays fixed, or that a small piece of logic still follows its rule.
They do not always notice whether a UI feels confusing, whether real content makes a layout awkward, or whether a product flow still supports the original business intent. Those are the reasons I still value a manual pass before handoff, especially for UI work.
For me, a useful testing habit is not about reaching a particular percentage or building every kind of test. It is about choosing enough checks to make the next change less risky, then using human review for the things a test cannot judge well.
Working on something similar? Let's talk