0

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 PaymentService without 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:

  1. Bug is reported: "Discount of 0% is calculated as 100% off."
  2. Before fixing: Write a failing test that reproduces the bug. assertEquals(100.00, applyDiscount(100.00, 0)) → FAILS.
  3. Fix the bug.
  4. Test passes. GREEN.
  5. 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"/>