Regression — a bug reintroduced by a change; regression tests catch them
Beginner5 min read·eng-17-014
interview
Concept
Regression — a bug that existed, was fixed, and then reappeared later (usually due to a subsequent code change). "Regression" because the code "regressed" to a broken state.
How regressions happen:
- Developer A fixes a bug in
PaymentService. - Months later, Developer B refactors
PaymentServicewithout knowing about the original bug. - The refactoring accidentally re-introduces the bug. Regression.
Regression test: A test written specifically to verify that a bug stays fixed. After fixing a bug, you write a test that reproduces the bug. The test will catch the regression if the bug comes back.
The process:
- Bug is reported: "Discount of 0% is calculated as 100% off."
- Before fixing: Write a failing test that reproduces the bug.
assertEquals(100.00, applyDiscount(100.00, 0))→ FAILS. - Fix the bug.
- Test passes. GREEN.
- Test lives in the test suite forever. If the bug ever returns → test goes RED immediately.
Why regression tests are valuable:
- They document the bug. Reading the test tells you what broke and how.
- They provide confidence during refactoring — if you break something, a test catches it.
- They prevent "regression to earlier broken states."
Without regression tests: The same bug gets fixed multiple times by different developers. Nobody knows the full history of what's been broken and fixed.
Mutation testing helps find missing regression tests — if a mutation doesn't break any test, you probably don't have a regression test for that code path.
Code Example
php
<?php
// SCENARIO: Bug reported — "Applying a 0% discount removes ALL price"
// Root cause: wrong formula: price * discountPercent/100 instead of price * (1 - discountPercent/100)
// STEP 1: Write a failing test BEFORE fixing (reproduces the bug)
class DiscountCalculatorRegressionTest extends \PHPUnit\Framework\TestCase
{
private DiscountCalculator $calc;
protected function setUp(): void { $this->calc = new DiscountCalculator(); }
/** @test regression: issue #142 — zero discount returned zero price */
public function test_zero_discount_returns_original_price(): void
{
$result = $this->calc->apply(price: 100.00, discountPercent: 0.0);
$this->assertEquals(100.00, $result); // FAILS with the bug
}
}
// STEP 2: Find the bug
class DiscountCalculator
{
public function apply(float $price, float $discountPercent): float
{
return $price * ($discountPercent / 100); // BUG: should be (1 - discountPercent/100)
// apply(100, 0) = 100 * 0 = 0 ← returns 0 instead of 100!
}
}
// STEP 3: Fix the bug
public function apply(float $price, float $discountPercent): float
{
return $price * (1 - $discountPercent / 100); // FIXED
// apply(100, 0) = 100 * (1 - 0) = 100 ← correct!
// apply(100, 10) = 100 * 0.9 = 90 ← correct!
// apply(100, 100) = 100 * 0 = 0 ← correct!
}
// STEP 4: Test now passes (GREEN) — regression test in place forever
// If anyone changes this code in the future and re-introduces the bug, this test FAILS immediately
// Additional regression tests for edge cases found during the fix
class DiscountCalculatorRegressionTest extends \PHPUnit\Framework\TestCase
{
/** @test regression: issue #142 */
public function test_zero_discount_returns_original_price(): void
{
$this->assertEquals(100.00, (new DiscountCalculator())->apply(100.00, 0.0));
}
/** @test regression: issue #142 — also verify max discount */
public function test_100_percent_discount_returns_zero(): void
{
$this->assertEquals(0.00, (new DiscountCalculator())->apply(100.00, 100.0));
}
/** @test regression: issue #156 — floating point precision issue */
public function test_10_percent_discount_precise(): void
{
$this->assertEqualsWithDelta(90.00, (new DiscountCalculator())->apply(100.00, 10.0), 0.001);
}
}
// TRACKING REGRESSIONS in CI
// Your CI pipeline runs tests on every PR
// If anyone introduces a regression, CI catches it before merge
// phpunit.xml: <testSuite name="Regression Tests" directory="tests/Regression"/>