Regression Defects
A regression defect makes something fail that worked before a change. The ISTQB glossary calls the result a regression: a degradation in the quality of a component or system due to a change. It worked in an earlier version, it fails in a later one, and between them lies a change: new code, a fix, a library upgrade, a configuration setting or a data migration.
For example, a developer adds a postcode check for a new courier, and from that build on, checkout rejects every saved address without a postcode. Nobody touched the checkout page, yet it broke.
Why it matters
Users lose something they already relied on, often in a part of the product the release notes do not mention, so nobody was looking there. Support often hears about it before the team does.
For the tester, a regression has an advantage most defects lack: a known good version to compare with. A report that names the last version that worked and the first that failed lets the developer search a few changes instead of the whole code base, and each regression shows a gap the regression suite can close.
What makes a defect a regression
Two facts make a defect a regression: the behaviour worked in an earlier version, and a change made it fail. Check the first fact instead of assuming it: run the same test on the previous build or release. Many teams then add a regression label and record both versions.
- Introduced: the change contains the mistake, such as a shared function edited for one caller that breaks another.
- Uncovered: the change exposes an older, hidden defect, for example a fix lets input reach a check that never ran before.
- Reintroduced: a fixed defect comes back, often through a merge that lost the fix. Some teams call it a recurring defect.
- Not a regression: behaviour that never worked, in a new feature or as an old defect found late. It fails on the previous version too.
Why regressions happen
A change is usually reviewed for its own purpose, not for every place that relies on it. The ISTQB Foundation syllabus notes that its adverse effects can appear in the changed component, in other components of the same system or in connected systems, and that regression testing is not limited to the test object: it can also concern the environment. A regression reaches users when no test checks the behaviour that broke, the test did not run, or it failed and nobody acted. Typical causes:
- Shared code: a function, component, style rule or API used in several places, changed with one of them in mind.
- Fixes and refactoring: right for the reported case, or meant to change nothing, yet different for an edge case.
- Upgrades of libraries, frameworks, browsers or the database; configuration and feature-flag changes.
- Data: a migration, or a new rule meeting records saved under the old one.
- Merges and connected systems: a conflict resolved the wrong way, or a service whose contract changed.
Detecting regressions: suites, baselines and bisecting
The regression suite exists to detect regressions: tests of behaviour that already worked, re-run after changes. Because it runs again and again and grows with each release, the ISTQB syllabus calls regression testing a strong candidate for automation. A test that passed on the previous build and fails on this one is the clearest regression signal. First rule out a flaky test, an environment or data problem, or a test made outdated by an intended change, as in the lesson on failed and blocked tests. Then run the test on the last version that should be good (the baseline), in the same environment. Comparing the outputs of the two versions, such as two API responses, often shows exactly what changed.
Bisecting narrows the range between the last good and the first bad version to one change. It is a binary search: test the version halfway, keep the half where the result switches from pass to fail, and repeat. 16 candidate builds need at most 4 tests; about 1,000 commits (changes saved in Git) need 10. git bisect does the bookkeeping over commits; without the source, you can bisect build numbers or releases in a test environment the same way.
- Use a reliable check: a flaky test sends the search into the wrong half.
- Change only the version; keep the environment, configuration and data the same.
- Skip a step that cannot be tested (
git bisect skip) instead of guessing. - The result is the first change where the failure appears. It may have uncovered an older fault, so it is evidence for the developer, not blame.
git bisect start
git bisect bad # the version you are on fails
git bisect good v2.7.0 # the last version known to pass
# Git checks out a commit halfway: build it, run the test, then mark it
git bisect good # or: git bisect bad
# repeat until Git prints "<commit> is the first bad commit"
git bisect reset # return to where you startedTry it: API Response Comparator Try it: JSON Diff Try it: Text Diff
Worked example: bisecting a checkout regression
The nightly regression run passes on build 140 on Monday and fails on build 156 on Tuesday: TC-311, checkout with a saved address that has no postcode, now stops at payment with "Address is incomplete". Re-run on build 156, TC-311 fails the same way; on build 140, in the same environment with the same customer, it passes. So this is a regression, entered in one of builds 141 to 156, each containing one merged change.
Four tests find it. Build 147 contains only PR-882, which added a required postcode check to the shared validateAddress() function for a new courier: right for new addresses, wrong for every address saved before it. The pull-request checks had passed because their test data held only new addresses.
Build number, test TC-311
- 140–146Good: TC-311 passes
- 147–156Bad: TC-311 fails from build 147 on
- 1140 Monday nightly run: last known goodRe-run in the same environment: passesPass
- 2156 Tuesday nightly run: known badRe-run: fails the same wayFail
- 3148 Bisect test 1Next range: 141–148Fail
- 4144 Bisect test 2Next range: 145–148Pass
- 5146 Bisect test 3Next range: 147–148Pass
- 6147 Bisect test 4: first bad buildContains one change: PR-882Fail
- Report title: "Checkout rejects saved addresses without a postcode (regression since build 147)".
- Evidence: last good build 146, first bad build 147, suspected change PR-882, caught by TC-311 in the nightly run.
- The fix applies the check only to the new courier, and TC-311 joins the pull-request checks.
Text version
Known good: build 140 Monday nightly run, TC-311 passed
Known bad: build 156 Tuesday nightly run, TC-311 failed
Candidates: builds 141..156, one merged change each (16 builds)
Test Build TC-311 First bad build is in
1 148 fail 141..148
2 144 pass 145..148
3 146 pass 147..148
4 147 fail 147 -> first bad build
Build 147 contains one change: PR-882 "Require postcode for new courier"Preventing regressions
No team prevents every regression, but each can be made less likely and caught closer to its cause. The earlier a run catches it, the fewer changes lie between the last good and the first bad result: one change at a pull-request check, a day’s changes in a nightly run, the release’s changes in the release regression with exploratory testing, and the same release’s changes in production, but after users are affected.
- 1Change writtencode, configuration, a dependency or dataDo an impact analysis before merging
- 2Pull-request checksRelevant regression tests · one change to search
Gate: merged
- 3Nightly run on the main branchFull regression suite · a day’s changes to search
- 4Release regressionRegression tests and exploratory testing · the release’s changes, before users see them
Gate: released
- 5ProductionUsers report it · the release’s changes, after users are affected
Earlier: fewer changes between last good and first badLater: more changes to search, users affected
Illustrative: teams name and combine these runs differently.
- Do a quick impact analysis before merging: who else uses this code, and which stored data meets the new rule?
- Run the regression tests relevant to the change, chosen by impact and risk, on every pull request and the full suite on the main branch, as a quality gate that can stop a change.
- Keep test data that looks like production history: old records, empty optional fields, data saved under earlier rules.
- For every regression fixed, add the test that would have caught it, at the lowest level that can express it.
- Fix or quarantine flaky tests, so that a red run always means something.
- Prefer small changes, and upgrade libraries in their own change with a regression run; small changes are easier to review and to bisect.
Try it: Regression Scenario Generator Try it: Risk-Based Test Generator
Common mistakes
- Calling a defect a regression without checking that it worked in an earlier version.
- Reporting a regression without the last good and the first bad version.
- Bisecting with a flaky test, or changing the environment or data between steps.
- Treating the change that bisecting finds as blame: it may have uncovered an older defect.
- Fixing the regression without adding the test that would have caught it.
- Getting used to a red regression run, where new regressions hide among known failures.
The QA perspective
When a regression test fails, make the failure trustworthy first: re-run it, check the environment and data, and run it on the last good version. Then report it with the regression evidence: the last good and first bad version, the test that caught it and, if you bisected, the suspected change. If you cannot build old versions, bisect together with a developer: your reliable test and their access to the history make a strong pair.
In triage, ask two extra questions: which change caused it, and why did our tests miss it? The second answer becomes an action, such as a new test, missing test data or a test moved earlier in the pipeline. Being a regression does not change severity, which follows impact, but it often raises priority, because users lost something they relied on and the cause is a recent, known change.
Practice exercise
Classify, bisect and report a release regression
Release 3.1 is tested against release 3.0 (build 200), which is in production. Builds 201 to 232 each contain one merged change. The test cycle produced the three reports below.
Decide which are regressions, plan the bisect, write the regression fields of the bug report, and propose two prevention actions.
Release 3.1 candidate: build 232. Release 3.0 in production: build 200.
Builds 201..232 each contain one merged change.
A TC-540 Searching "cafe" finds the product "Café Latte"
build 200: pass build 232: fail
B New in 3.1: "Filter by brand" lists the brands in random order
C A date of birth of 29/02/2000 cannot be saved in the profile
build 232: fail release 3.0 in production: fail- Classify A, B and C, and name the evidence that decides each.
- How many bisect tests are needed at most? Which build do you test first, and which next after a pass or a fail?
- The bisect ends at build 219, which only upgrades the search library. Write the title, last good and first bad build, suspected change and the test that caught it.
- Propose two prevention actions.
Show the model answer
A is a regression: TC-540 passes on build 200 and fails on 232. B is not: the feature is new in 3.1 and never worked. C is not either: it also fails in release 3.0, so it is an old defect found late. B and C are reported without the regression label.
32 builds need at most 5 tests. Test 216 first; if it passes, test 224 next (range 217 to 232); if it fails, test 208 (range 201 to 216). Keep the environment and data the same.
Title: "Search no longer matches product names with accents (regression since build 219)". Last good 218, first bad 219, suspected change the search library upgrade, caught by TC-540. The upgrade may have changed a default, such as how accents are compared, rather than contain a fault; the developer confirms which.
Prevention, any two: run the search tests on every pull request that touches search or its dependencies; upgrade libraries in their own change and read their release notes for changed defaults; add accented, Arabic and mixed-script product names to the test data.
Frequently asked questions
Is every defect found during regression testing a regression defect?
No. A regression run can also fail on an old defect, for example when a new test or new test data is added to the suite, and a regression can be found by any activity, including exploratory testing and user reports. What makes a defect a regression is the evidence that it worked before a change, not the activity that found it.
Do I need to know Git to bisect?
No. Bisecting is a method, not a command: you can apply it to build numbers in a test environment, to releases, or to a set of changes switched by feature flags. git bisect automates it over commits, and git bisect run repeats the steps with a script whose exit code decides: 0 means good, 125 means skip, and the other codes from 1 to 127 mean bad.
Should a regression always get a higher priority?
Not automatically. Severity and priority are set in triage on impact and urgency, as for any defect. Regressions often rank higher because users lost something they relied on and the cause is a recent change, but a regression in a rarely used report screen can still wait.
When a fixed bug comes back, do I reopen the old defect?
Yes, reopen it when the same failure returns in a build that contains the fix; a different symptom gets a new defect linked to the old one. Some teams’ workflows always file a new, linked record instead, so follow your team’s working agreement. Either way, record that it came back and find out why the test that should guard it did not catch it.
References
- ISTQB Certified Tester Foundation Level (CTFL) v4.0The foundation syllabus of software testing: test levels and types, test design techniques, test management and tools.
- ISTQB GlossaryThe standard definitions of testing terms — test case, regression testing, severity, priority, boundary value analysis and the rest.
- Reference — GitThe official reference for every Git command and its options, grouped by task — setup, branching, sharing, patching, inspection and debugging.
- Continuous Integration (Martin Fowler)The widely referenced description of continuous integration: integrate often, keep the build self-testing and fast, and fix a broken build immediately.
Practise with Mutqan tools
Generate regression scenarios from a change description and a list of affected and related modules.
Compare two API responses (e.g. staging vs production) and list every structural and value difference.
Compare two JSON documents semantically and highlight added, removed and changed values.
Compare two texts line by line or word by word and highlight differences.
Score features by likelihood and impact, then generate a prioritised risk-based test plan.
Go deeper
Your progress on this topic
Not started
Mark the topic complete when you have studied it and done the exercise.
Sign in to track your progress
Mark topics complete, continue where you left off and unlock specialisation exams.