What Changed When We Took Developer Testing Seriously

How we test changes before QA, verify real product behavior, and prepare clearer handoffs with evidence from an analytics tracker migration.

A change moves through test scenarios, verification, and recorded evidence before the QA review.
FIG. 02 Developer checks give QA a concrete starting point.

A task can feel finished as soon as the implementation is in place. The build passes, the new screen looks right, and the pull request is ready. Moving it to QA feels like the natural next step.

There is still an important piece of work to do: verify that the change behaves as intended in the product.

Paying more attention to developer testing has made that responsibility clearer in our workflow. Before handing a task to QA, we want to know which scenarios we exercised, what we expected, what actually happened, and where our confidence stops.

The value shows up in the handoff itself. We can explain the behavior we checked, provide evidence someone else can follow, and give QA a more useful starting point for their own investigation.

What we mean by developer testing

Developer testing is the verification we own as part of implementing a change. It includes automated tests, focused manual checks, and, where the feature depends on several services, checking the running system together.

A unit test might verify a validation rule. An integration test might check that a request preserves the right data. A browser check might confirm that the user can complete the flow and see the expected result.

Each answers a different question. Passing a build tells us the application can be built. Passing a focused test tells us the scenarios it covers behaved as expected. A merged pull request tells us the change reached a branch. We still need evidence for the user-facing behavior we are handing over.

QA brings another perspective to that work. The developer knows the implementation and its likely weak points. QA can challenge the assumptions behind it, explore different paths, and assess how the change fits the wider product.

DORA’s guidance on test automation supports this shared responsibility: developers should help create and maintain reliable automated tests, while testers remain closely involved in exploration, usability, and improving the test suite.

How we approach a change before QA

We start by making the expected behavior specific. “The report works” is too broad to verify. “The selected source and date range produce the expected records and totals” gives us something concrete to check.

From there, we look at three areas: the intended flow, relevant failure cases, and existing behavior the change could affect.

For a report change, that might mean checking a populated result, an empty result, a dependency error, and whether changing a filter resets pagination correctly. For a tracker change, the important cases may be event counts, existing integrations, invalid configuration, and whether data reaches the reporting screen. The scope follows the behavior we changed.

We run the relevant automated checks and exercise the feature with identifiable test data. For changes that depend on deployment, we also verify the affected flow in the environment QA will use. Local checks and checks against deployed services are recorded separately, because they establish different things.

In our analytics work, a dedicated test source and a unique test-run identifier help us connect a browser action to the event it produced. We inspect the request, then find the resulting data under the expected source and session. We compare the values and counts with what we deliberately sent.

This matters with asynchronous processing. An endpoint returning 202 Accepted confirms acceptance for processing; it does not confirm that processing has finished. That distinction is part of HTTP’s defined semantics. When the feature promises a reportable event, our check needs to reach the report.

The result is a small set of explicit outcomes: passed, failed, or blocked, with the expected and observed behavior beside them. Anything we did not verify stays visible. A missing environment or unavailable dependency should not quietly turn into a passing result.

A small API change with a much larger test surface

One of our tracker migrations made this approach especially concrete. The visible change was a new public API name in installation snippets. Checking that the new name existed in the browser would have covered only a small part of the work.

Existing installations still needed to work. A configurable alias needed to refer to the same tracker. Calls through different names needed to preserve the same session, and the payload sent by the browser needed to survive processing.

We tested fresh HTML and Next.js installations, the existing and new names, and a custom alias. We sent one commerce event through each name and checked for exactly three events sharing one session. We verified that the commerce fields reached the report unchanged and that repeated queue flushes did not resend events.

We also checked invalid configuration and conflicting request headers. For rejected requests, the expected result included both the error response and the absence of corresponding events in the report.

Those checks passed in the recorded run. The useful outcome was a specific account of what compatibility meant and which parts we had verified. QA then received acceptance criteria covering setup, compatibility, event accuracy, consent, headers, and reporting.

That example also changed how I think about the size of a task. A small visible edit can affect several existing promises. Developer testing helps us identify those promises while the implementation is still fresh in our minds.

The benefits that have become most useful to us

Our handoffs have become more precise. We can replace a broad “tested and working” message with concrete results. In the tracker example, “three calls produced three events in one session” communicates much more than “tracking passed.” It gives the next person a result they can challenge.

We have a better understanding of the full feature. Following a change from the browser to the report forces us to account for the services and transformations in between. We become more aware of where values can change, where compatibility matters, and which part of the system owns the final result.

We preserve context for later investigation. A test-run identifier, representative payload, and expected-versus-observed result give us a record to return to. When a question comes up, there is something more useful than a developer’s memory of clicking through the page.

Existing behavior gets explicit attention. It is easy to concentrate on the newly added path. Including regression cases makes us consider the people already using the product. In the migration, that meant testing the existing API name alongside the new one and checking that both retained the same behavior.

The conversation with QA starts from clearer information. We can show which paths we checked and which questions remain open. QA can reproduce a result, question an expectation, or explore a different scenario without first reconstructing the developer’s setup and intent.

There are further benefits we want from this habit: fewer avoidable returns from QA, less time spent rediscovering context, and earlier discovery of regressions. Those are worth measuring over comparable tasks. A successful test run demonstrates the behavior covered by that run; a claim about reduced rework or faster delivery needs evidence across the workflow.

Keeping the habit practical

The amount of verification should match the change. A copy update needs a route and layout check. A change to event collection needs checks around payloads, compatibility, and processing. A shared reporting change needs attention to the views and filters that depend on it.

We want the checks to be useful enough that developers keep doing them. For repeatable rules and recurring regressions, automated tests make the evidence easier to reproduce. Manual exploration remains valuable for interactions and combinations that deserve a fresh look. My API testing matrix describes how I turn expected behavior into focused test cases.

A useful QA handoff can stay short. It should explain what changed, identify the tested environment and relevant data, list the scenarios with their results, and link the evidence. Known limitations and unverified cases belong beside the passing cases.

Acceptance criteria should describe the behavior QA needs to assess. For example: “Existing and new tracker names work on the same page, each explicit call produces one event, and both preserve the same session.” That leaves room for QA to choose additional ways to challenge the feature.

The standard I want to carry into every handoff is simple: I should be able to show how I checked the behavior I changed. Doing that work while I still have the context gives QA something concrete to build on and makes quality a responsibility we share throughout development.