Regression vs integration testing

Regression vs integration testing is a false choice: integration names what a test covers, regression names why it runs again. Unit testing fits the same frame.

Two clear glass rails cross over a 3 by 3 grid of clear glass tiles, lighting the centre tile orange

Regression testing and integration testing are not two options on one menu, so asking which to choose has no answer. Integration testing names what is under test: the seams where components meet. Regression testing names why a test runs again: something changed. Kept in the suite, an integration test becomes a regression test when a later change re-runs it.

Integration is a level and regression is a trigger

The ISTQB glossary files the two words in different drawers. Integration testing is “a test level that focuses on interactions between components or systems”. Regression testing sits under change-related testing, which the same glossary defines as “a type of testing initiated by modification to a component or system”. A level describes the object of a test: how much of the system is assembled when it runs. Change-related testing describes the occasion: what happened that makes running it worthwhile now. Every test has both properties at once, which is why “regression testing vs integration testing” reads like a comparison and behaves like a mismatch of units, closer to asking whether a parcel is heavy or late.

Unit testing belongs on the level axis, next to integration. ISTQB calls it component testing, “a test level that focuses on individual hardware or software components”, and lists unit testing as a synonym. The other query in this cluster, unit testing vs regression testing, therefore has the same shape: a unit test is small, a regression run is repeated, and nothing stops one test from being both.

Regression testing in software testing sorts regression work by its trigger and by how much of the suite a change re-runs. This post stays on the narrower question of how the level axis and the trigger axis cross. The definitions themselves sit in the glossary under regression testing and system integration testing.

Scope and trigger need separate columns

Put the three names in one table, give each a cell on both axes, and the asymmetry shows. Unit and integration testing fill the scope column and leave the trigger open. Regression testing fills the trigger column and leaves the scope open.

NameAxis it namesScope: what is under testTrigger: why it runs nowWhat a failure points at
Unit testingScopeOne component, with its collaborators usually replaced by stubs or mocksLeft open by the name: written with the code, then re-run on later changesA function or class, often down to the line
Integration testingScopeTwo or more real components and the interface between themLeft open by the name: written when the seam is built, then re-run on later changesAn interaction, with a suspect on each side of it
Regression testingTriggerLeft open by the name: whatever the suite holds, from single units to whole user flowsA change somewhere in the system, checked against the areas it was not meant to touchBehaviour that worked in an earlier build and no longer does

Reading across a row gives one test’s identity. Reading down the two middle columns shows why the three are not rivals: the levels leave blank the cell that regression fills, and regression leaves blank the cell the levels fill. A working suite is assembled from both answers.

So the design question a team actually faces is which levels its regression suite holds, and how much of each one a given change re-runs. Neither name settles that on its own, and a comparison table that scores integration against regression on speed, cost or ownership is scoring a noun against a reason.

A merged integration test is the next change’s regression test

Follow one test through two days. On Monday a developer builds a refund flow that calls the payments service and writes an integration test that drives a refund through both components with nothing mocked. The test checks behaviour that did not exist before, which makes it a progression test at the integration level. It fails, the code is corrected, it passes, and the branch merges.

On Tuesday somebody else changes how the payments service rounds currency for an unrelated screen. The same test runs again in the pipeline, unchanged byte for byte. Its level is still integration. Its job is now regression, guarding behaviour nobody meant to alter against a change made somewhere else. Nothing in the test moved; the reason for running it did. The calendar played no part: what made the second run a regression run was a change the test was not written for, and with no such change the test would simply have waited.

That handover happens to every test that stays in the suite once its own change has merged, unit tests included, and it is why a regression suite is mostly an archive of earlier progression tests plus the tests added after fixed bugs. A team that says it does integration testing but no regression testing usually means something narrower and easier to repair: its integration tests run on the branch that introduced them and are never wired to run again when other code changes. The tests already exist and only the trigger is missing, so the repair is a pipeline rule that re-runs them, with no new kind of testing involved.

The level decides what a regression failure can name

Once both levels sit inside one regression suite, they catch different regressions, and the split follows from scope.

A unit-level regression run is quick and precise. When it fails, the failing test names a component and often a line, and the change that broke it is usually in the diff under review. Its blind spot is everything the unit’s stand-ins hide. A stub returns what its author believed the dependency returned on the day it was written, which is the weak point mock vs stub describes. When the real dependency starts answering differently, every unit test built on the old stub keeps passing, and the regression ships through a green suite.

An integration-level regression run closes that gap, because the real components answer each other. It pays in time and in diagnosis: a failure names an interaction, and the defect can sit on either side of it, in the data passed between them, or in the environment both run on. Contract testing is one way teams take part of the coverage without the whole cost, checking each side against a recorded agreement without starting both.

That gives the unit-versus-regression question a practical answer. Selective runs on each pull request lean on the unit level, because those failures are cheap to read while the change is still in its author’s head. The integration level runs on merge and on a schedule, where its slower verdict still lands before release. Both are regression runs, each blind to a different set of regressions, and a suite missing either level has a gap the other cannot cover.

A regression found by hand still has to be filed at a level

Some regressions surface above every level a suite holds: a layout that breaks in one browser, a flow that passes in the test environment and fails against real data, a form that submits but leaves the page in a state no assertion inspects. A person finds these, and against the automated suite they count as escaped defects.

Closing the bug finishes half the work. The other half is a regression test that would have caught it, and that test has to be written at a level. The usual rule is the lowest level that reproduces the failure: a unit test when one component misbehaves on its own, an integration test when the fault appears only where two components meet, a full end-to-end check only when nothing smaller shows it. Picking well depends on what the report carries. “Refunds look wrong” could be filed anywhere and tends to end up at the slowest level. A report showing an uncaught TypeError thrown inside one module points at a unit. A report showing a call to the payments API that came back 500 points at the seam.

That evidence is the narrow place Notarus occupies here. A regression reported from a live page through the widget becomes a ticket where the console output and the network calls from the seconds before the report sit beside the screenshot the reporter marked up (evidence capture itemises each field). Those two records are what separate the TypeError report from the 500 one: the console shows which module threw, and the network log shows which call to which service failed and with what status, though never its payload. Notarus runs no tests at either level, and a ticket holds only what the widget was set up and able to record in that browser, so one report may carry the failing call while another brings only the screenshot, and the level question goes back to reproduction. Where the records are there, the engineer can choose between a unit test and an integration test before writing a line of either, and the next change to that code meets a test waiting at the right level.

Common questions

What is the difference between regression testing and integration testing?

Integration testing is a test level: it checks the interactions between components or systems with the real parts assembled. Regression testing is change-related: it re-runs existing tests after a change to catch breakage in areas the change was not meant to touch. An integration test re-run after a change is both at once.

What is the difference between unit testing and regression testing?

Unit testing names the scope of a test, one component checked in isolation, typically with its dependencies stubbed. Regression testing names the reason a test runs, which is a change somewhere else in the code. Unit tests re-run on every change form the fastest layer of most regression suites.

Is integration testing part of regression testing?

Integration tests become part of regression testing once they have merged and a later change, made for some other reason, re-runs them. Before that, while they check the seam they were written for, they are progression tests at the integration level, because there is no earlier behaviour to compare against.

Which comes first, integration testing or regression testing?

An integration test is written and run first, while the connection between components is being built. Regression runs of that same test come afterwards, every time a later change could affect the connection. The order describes one test's life, since a mature pipeline re-runs its integration tests as regression checks on every change.

Should a regression suite hold unit tests or integration tests?

A useful regression suite holds both. Unit tests catch a broken component quickly and name it precisely, but miss a change hidden behind a stub. Integration tests catch breakage between real components and run slower, so many teams run the unit level on each pull request and the integration level on merge and on a schedule.

Where does a regression found by manual testing get its test?

A regression found by hand gets a new automated test at the lowest level that reproduces it: a unit test when one component fails on its own, an integration test when the fault appears only where components meet. The console errors and network calls in the report usually show which level that is.

The Notarus team

The people who build Notarus. We write up the decisions behind the product when the reasoning is worth more than the changelog line.

Keep reading

Reports with the evidence attached.

Start free, one script tag.

<script src="…/widget.js" data-key="pb_pub_…">