Regression Testing: How to Update an App Without Breaking Existing Features

A product team makes what appears to be a small change to its pricing logic.

The requirement is straightforward: enterprise customers in one market need a different discount calculation. Engineering changes a shared pricing function, tests the new calculation and confirms that the enterprise checkout produces the expected total.

The release goes live.

Several hours later, support notices something unrelated: subscription renewals are failing for customers in another market.

The new discount works perfectly.

The problem is that the same pricing function is also used by the renewal workflow, where a slightly different assumption applies. The team tested what it intended to change but not what the change could unintentionally affect.

That is the problem regression testing is designed to solve.

Regression testing checks whether software that worked before still works after the application has been changed. The change might be a new feature, bug fix, dependency update, refactor, database modification or configuration change. The important point is that testing does not stop at verifying the new behaviour; it also checks for unintended damage to existing behaviour.

DORA's guidance on continuous integration recommends running automated tests whenever changes are integrated specifically so teams can detect regression bugs quickly rather than discovering them after release.

For Levelworks, regression testing is central to Tested App Health. It also supports Seamlessly Updatable because an application becomes much easier to maintain when teams can make changes with evidence that existing functionality has not quietly been damaged.

Regression testing starts with understanding what changed

The fastest way to make regression testing inefficient is to begin with:

Run every test we have.

Sometimes a full suite is appropriate, particularly before a high-risk release. But good regression testing begins with understanding the impact surface of the change.

Suppose engineering modifies customer-address validation.

Obvious tests cover:

  • creating an address;
  • editing an address;
  • invalid address handling.

Less obvious affected workflows might include:

  • checkout;
  • tax calculation;
  • shipping labels;
  • saved payment profiles;
  • account imports;
  • invoices.

The first regression-testing question is therefore:

What existing behaviour depends directly or indirectly on what we changed?

Code review can help identify this impact. Google's engineering practices recommend evaluating not only whether a change performs its intended function but also its interactions with the rest of the system, edge cases and appropriate unit, integration or end-to-end tests.

A useful impact review examines:

  • functions and services called by the changed code;
  • callers of the changed component;
  • shared business rules;
  • database tables or schemas affected;
  • APIs whose contracts changed;
  • user journeys using the same underlying logic;
  • configuration or infrastructure affected.

The more shared the component, the wider the likely regression surface.

Prioritize regression tests by risk

Not every existing feature deserves equal testing after every change.

A CSS adjustment to a settings screen should not trigger the same regression effort as changing authorization logic shared across every API.

A practical regression strategy considers two dimensions:

Likelihood: How likely is this change to affect the feature?

Impact: How serious would it be if that feature broke?

That creates a useful priority model:

Existing behaviour

Likelihood of impact

Business impact

Regression priority

Code directly modified

High

High

Highest

Critical workflow using changed component

Medium–High

High

Highest

Adjacent functionality

Medium

Medium

High

Independent critical workflow

Low

High

Moderate

Unrelated low-value feature

Low

Low

Lower

This is more effective than giving every test equal importance.

For a checkout change, payment, pricing, inventory and order creation probably deserve strong regression coverage. An unrelated profile-avatar workflow may not require the same attention unless the underlying change genuinely touches it.

Risk-based selection keeps regression testing useful as the application grows instead of allowing every release to require an ever-expanding manual checklist.

Test the change at the lowest useful level

Regression tests can exist at several levels.

A calculation defect might be protected with a unit test. A database interaction may require an integration test. A critical checkout journey may deserve an end-to-end test.

The mistake is attempting to protect everything through the user interface.

Martin Fowler's discussion of the Practical Test Pyramid recommends pushing tests toward lower levels where they can provide the same confidence, because lower-level tests generally execute faster and isolate failures more clearly. Higher-level tests remain useful where they verify behaviour that lower-level tests cannot establish.

A balanced regression suite might therefore look like this:

Test level

Best used for

Unit

calculations, validation, domain rules

Component/service

service behaviour in isolation

Integration

databases, APIs, queues, service interactions

End-to-end

a small number of important user journeys

Exploratory

unexpected interactions and newly changed areas

The point is not to follow a particular ratio mechanically.

It is to avoid making every regression check an expensive end-to-end test when a faster test can protect the same behaviour reliably.

Turn important production bugs into permanent regression tests

One of the highest-value sources of regression tests is the application's own defect history.

If a production bug revealed that two simultaneous requests can create duplicate orders, the fix should usually be accompanied by a test reproducing that condition.

The process is straightforward:

  1. reproduce the defect;
  2. create a test demonstrating the failure;
  3. confirm it fails against the broken behaviour;
  4. implement the fix;
  5. confirm the test passes;
  6. retain it in the suite.

The application has now converted a production failure into permanent test knowledge.

Google's code-review practices recommend keeping test changes alongside the behaviour they protect and ensuring that tests would actually fail if the underlying code were broken. (Google Engineering Practices)

This prevents teams from repeatedly learning the same lesson.

[Internal Link: “Why the Same Bugs Keep Coming Back and How to Stop Them”]

Pay extra attention to shared components

The highest regression risk often appears in code that looks small but is widely reused.

Examples include:

  • authentication middleware;
  • pricing engines;
  • validation libraries;
  • shared UI components;
  • serialization;
  • database-access layers;
  • permissions;
  • caching utilities.

Changing one shared function may affect dozens of workflows.

Before modifying shared code, identify its consumers and select representative regression tests from the important paths.

This is one reason a change's size in lines of code is a poor measure of risk.

Changing three lines inside a central authorization function may deserve more regression testing than adding 500 lines to an isolated reporting feature.

Regression scope should follow dependency and impact, not simply code volume.

Include database and data-model changes in regression thinking

Application updates often change more than source code.

Suppose a release makes a database column optional.

The new feature works with newly created records, but older production records follow different assumptions and begin failing elsewhere.

Regression testing should therefore consider:

  • schema migrations;
  • existing production-shaped data;
  • backward compatibility;
  • default values;
  • indexes;
  • data conversions;
  • rollback implications.

DORA's guidance on database change management recommends managing database changes through the same disciplined delivery process as application changes because database modifications are a significant source of deployment risk.

The relevant regression question is:

Will existing data still behave correctly after this update?

Testing only newly created data can miss exactly the condition existing customers will encounter.

Use realistic test data for the risks you are trying to detect

A regression test is only as useful as the conditions it exercises.

Consider a search feature that performs correctly with 50 test records but fails after a change when working with millions of records.

Or an account migration that passes with perfectly formatted test users but fails on older accounts containing null or legacy fields.

DORA's test data management guidance emphasizes realistic test data for validating important user journeys, edge cases, defects and error conditions, while also cautioning against simply copying sensitive production information into test environments.

Regression datasets should therefore deliberately represent:

  • current records;
  • older records;
  • boundary values;
  • unusual but valid states;
  • previously problematic inputs;
  • relevant scale characteristics.

The objective is not to reproduce production exactly.

It is to reproduce the characteristics capable of exposing the regression.

Keep the regression suite fast enough that teams actually use it

Regression suites tend to grow.

Every bug adds a test. Every feature adds more scenarios. Eventually a suite that once took five minutes takes two hours.

Teams then begin avoiding it.

Tests run only before major releases, failures are tolerated because "that test is flaky anyway," and developers receive feedback long after they have mentally moved on from the change.

At that point, regression testing loses much of its preventive value.

DORA's test automation guidance recommends fast, reliable automated suites integrated throughout delivery because delayed feedback makes defects more expensive to diagnose and turns testing into a release bottleneck.

Maintain the suite like production code.

Remove redundant tests. Move scenarios to lower levels where possible. Investigate slow tests. Repair or remove tests that fail unpredictably.

A regression test that nobody trusts provides very little protection.

Treat flaky tests as defects in the safety system

Imagine a regression suite contains 800 tests, but 15 fail randomly on most runs.

Developers learn to rerun failures until the pipeline turns green.

Then test number 16 fails.

Is that the regression introduced by this release or another unreliable test?

Flakiness reduces the signal of the entire suite because engineers gradually stop treating failure as evidence.

A healthy regression suite should make a failing test noteworthy.

If failure becomes normal, the suite can no longer function as a release gate with confidence.

Common sources of flakiness include:

  • timing assumptions;
  • shared test state;
  • external dependencies;
  • nondeterministic data;
  • race conditions;
  • unstable environments.

Fixing those conditions is part of maintaining the regression system, not optional test-suite cleanup.

Automation should cover repeatable regression checks

Regression testing is particularly well suited to automation because many checks need to be repeated after every relevant change.

DORA recommends automated testing throughout the delivery lifecycle and notes that manual regression testing becomes slow and expensive as systems and release frequency grow.

Useful candidates for automated regression coverage include:

  • business rules;
  • API contracts;
  • permissions;
  • calculations;
  • database behaviour;
  • critical transaction paths.

Manual testing still has value, particularly for exploratory investigation, complex UX behaviour and situations where human judgement matters.

The goal is not to automate everything.

It is to avoid spending human attention repeatedly verifying behaviour a deterministic test can check reliably.

[Internal Link: “Automated vs Manual Testing for Application Maintenance”]

Run regression tests close to the change

The longer feedback takes, the harder a regression is to investigate.

If a developer changes pricing code at 10 a.m. and a regression test fails five minutes later, the likely cause is relatively easy to identify.

If the same regression appears in a large release test three weeks later alongside fifty other changes, diagnosis becomes much more difficult.

DORA's continuous-integration guidance recommends running builds and automated tests on every integrated change and fixing broken builds promptly.

This does not mean every test must run on every commit.

A practical pipeline may use layers:

Commit: fast unit and targeted tests.

Integration: wider component and API regression suite.

Release candidate: broader critical-workflow tests.

Post-deployment: small smoke checks against the deployed system.

The faster tests provide early feedback, while broader suites increase confidence as the change approaches production.

[Internal Link: “CI/CD for App Maintenance: How Automated Releases Reduce Risk”]

Regression testing needs maintenance too

An application changes continuously, so a regression suite cannot remain static.

Review it periodically.

Ask:

  • Which critical workflows have no meaningful regression protection?
  • Which tests no longer represent current product behaviour?
  • Which tests duplicate one another?
  • Which failures are routinely ignored?
  • Which tests consume disproportionate execution time?
  • Which recent production defects should have been caught?
  • Which new shared components increase regression risk?

This last question is particularly useful.

If customers discover an important regression that the suite completely missed, do not stop at fixing the application.

Ask what the test strategy failed to understand.

A missed regression is information about both the product and its safety net.

A practical regression-testing approach

Before releasing an application update, a useful workflow is:

  1. Describe the intended change. Be precise about what behaviour should be different.
  2. Identify the impact surface. Trace shared components, callers, data and adjacent workflows.
  3. Rank existing behaviour by risk. Prioritize critical and likely-to-be-affected functionality.
  4. Add or update tests for the new behaviour.
  5. Preserve known defects as regression tests.
  6. Run tests at the lowest useful level.
  7. Use broader tests for critical integrations and journeys.
  8. Investigate failures rather than automatically rerunning them.
  9. Verify important behaviour after deployment.
  10. Improve the suite when production reveals a gap.

The goal is not maximum test volume.

It is enough evidence to make a responsible release decision.

Safe updates depend on preserving what already works

Every software change has two requirements.

The obvious one is:

The new behaviour must work.

The second is easier to forget:

Everything that should remain unchanged must still work too.

Regression testing protects that second requirement.

For Levelworks, this is what makes Tested App Health important beyond conventional QA. A strong regression strategy gives teams confidence to maintain, patch, refactor and improve an application without treating every update as a gamble.

It also supports Seamlessly Updatable App Health. When regression coverage is fast, trustworthy and aligned with the application's real risk, teams can change software more safely because they receive evidence about unintended consequences before customers do.

The aim is not to prove that an update changed nothing.

It is to prove that it changed what you intended—and not what you didn't.

‍

Continue Reading