Back to SDET Scenarios
Q10Question 10 of 12

What is TDD (Test-Driven Development) and the Red-Green-Refactor cycle

Key Concepts
  • TDD means write a failing test first (Red), write just enough code to pass it (Green), then refactor (Refactor)
  • Red: write one small test for behavior that does not exist yet, confirm it fails for the right reason
  • Green: write the minimum code to pass, resist adding anything the test does not require
  • Refactor: clean up structure, remove duplication, rename for clarity, rerun to stay green
  • Benefits: catches bugs early, drives better testable design, acts as executable documentation that cannot go stale
  • TDD is most valuable for logic-rich code like calculations and rules, applied with judgement for thin glue code

Definition

TDD means write a failing test first, write just enough code to pass it, then refactor, repeating this Red-Green-Refactor loop, which catches bugs early, drives simpler testable design, and leaves tests that document the intended behavior.
  • Red – Write one small failing test for behavior that does not exist yet. Confirm it fails for the right reason.
  • Green – Write the minimum code to make the test pass. Resist adding anything the test does not require.
  • Refactor – Clean up structure, remove duplication, rename for clarity. Rerun to stay green.
  • Catches bugs early – Defects detected the instant you write the code, when context is fresh and fixes are cheap.
  • Drives better design – Writing the test first makes you design from the caller's perspective, producing focused, testable units.
  • Acts as documentation – Tests are executable specifications that cannot drift out of date because they fail if behavior changes.

When building the National Bank of India interest calculator, instead of writing the calculator and hoping it works, you first write a tiny check that says "for one hundred thousand rupees at four percent for one year, the interest must be four thousand." It fails because there is no calculator yet, that is Red. Then you write just enough calculator code to make that check pass, that is Green. Then you tidy the code without breaking the check, that is Refactor. You keep adding one small check at a time. The result is that every rule is proven as you build it, the design stays simple because you only write what a check demands, and the checks themselves become a living description of how interest is meant to work.

Understanding the Concepts

What is the Red-Green-Refactor cycle?

TDD follows a tight loop of three steps repeated for each small piece of behavior.

TEXT
  RED    -> Write a failing test (behavior does not exist yet)
  GREEN  -> Write minimum code to pass the test
  REFACTOR -> Clean up, keep the test green
  REPEAT -> Next behavior

What does the Red step look like?

Write one small test for behavior that does not exist yet. Run it to confirm it fails for the right reason.

JAVA
// RED: test for interest calculation that does not exist yet
package com.bank.service;

import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class InterestCalculatorTest {

    @Test
    void calculateSimpleInterest() {
        InterestCalculator calc = new InterestCalculator();
        BigDecimal result = calc.calculate(
            new BigDecimal("100000.00"),  // principal
            new BigDecimal("7.0"),        // rate
            365                            // days
        );
        assertEquals(new BigDecimal("7000.00"), result);
    }
}
// This FAILS: InterestCalculator class does not exist yet
// That is expected and correct in the Red step

What does the Green step look like?

Write the minimum code to make the test pass. Resist adding anything the test does not require.

JAVA
// GREEN: minimum code to pass the test
package com.bank.service;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class InterestCalculator {

    public BigDecimal calculate(BigDecimal principal, BigDecimal rate, int days) {
        return principal
            .multiply(rate)
            .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP)
            .multiply(new BigDecimal(days))
            .divide(new BigDecimal("365"), 2, RoundingMode.HALF_UP);
    }
}
// Test passes: GREEN

What does the Refactor step look like?

With the passing test as a safety net, improve structure without changing behavior.

JAVA
// REFACTOR: extract constants, improve naming
public class InterestCalculator {

    private static final BigDecimal HUNDRED = new BigDecimal("100");
    private static final BigDecimal DAYS_IN_YEAR = new BigDecimal("365");
    private static final int MONEY_SCALE = 2;

    public BigDecimal calculate(BigDecimal principal, BigDecimal annualRate, int days) {
        BigDecimal dailyRate = annualRate
            .divide(HUNDRED, 10, RoundingMode.HALF_UP)
            .divide(DAYS_IN_YEAR, 10, RoundingMode.HALF_UP);

        return principal
            .multiply(dailyRate)
            .multiply(new BigDecimal(days))
            .setScale(MONEY_SCALE, RoundingMode.HALF_UP);
    }
}
// Test still passes: REFACTOR complete

How do you add the next behavior?

Pick the next edge case or rule and loop.

JAVA
// RED: zero principal should return zero interest
@Test
void zeroBalanceReturnsZeroInterest() {
    BigDecimal result = calc.calculate(BigDecimal.ZERO, new BigDecimal("7.0"), 365);
    assertEquals(new BigDecimal("0.00"), result);
}
// Already passes -> no Green needed, move to next behavior

// RED: negative rate should throw
@Test
void negativeRateThrows() {
    assertThrows(IllegalArgumentException.class, () ->
        calc.calculate(new BigDecimal("100000"), new BigDecimal("-1"), 365)
    );
}
// FAILS -> write validation code (GREEN) -> refactor

Why does TDD catch bugs early?

A defect is detected the instant you write the code, when the context is fresh and the fix is cheap.

TEXT
  Without TDD:
  Write code -> deploy -> test manually -> find bug -> debug -> fix
  Cost: hours to days

  With TDD:
  Write test -> write code -> test fails immediately -> fix now
  Cost: seconds to minutes

Why does TDD drive better design?

Writing the test first forces you to design from the caller's perspective, producing focused, testable units.

JAVA
// TDD naturally pushes toward dependency injection
// because hard-to-test code signals poor design

// HARD TO TEST: hidden dependency
public class TransferService {
    private final FraudChecker checker = new FraudChecker(); // tightly coupled
}

// TDD PUSHES TOWARD: injected dependency
public class TransferService {
    private final FraudChecker checker;
    public TransferService(FraudChecker checker) { // easy to test with a mock
        this.checker = checker;
    }
}

When is TDD most valuable?

TDD shines for logic-rich code. Apply with judgement for thin glue code.

| Code Type | TDD Value | Example |
|---|---|---|
| Business logic | Very high | Interest calculation, fee rules |
| State machines | Very high | Loan lifecycle transitions |
| Algorithms | High | Sorting, searching, parsing |
| Validation | High | Input validation rules |
| Thin glue / wiring | Lower | Controller routing, config |
| Exploratory spikes | Not applicable | Throwaway experiments |

What are common interview follow-up questions?

  • "Does TDD slow you down?" For logic-heavy code it usually speeds overall delivery by preventing defects and rework.
  • "Do you always write the test first?" For logic and rules, yes. For UI wiring and exploratory work, I apply judgement.
  • "Is TDD the same as unit testing?" No. TDD is a development practice where tests drive implementation. Unit testing is a category of test.

Key Takeaway

Test-Driven Development is a practice where I write a failing test before the production code, following the Red-Green-Refactor cycle. Red: I write one small test for a single behavior that does not exist yet and run it to confirm it fails for the right reason, which proves the test really exercises something. Green: I write the minimum code to make it pass, resisting anything the test does not require, because unneeded code is untested code. Refactor: with the passing test as a safety net I clean up the structure, remove duplication, and rename for clarity without changing behavior, rerunning to stay green. Then I pick the next small behavior and loop.

It helps in three concrete ways. It catches bugs early, because a defect is caught the instant I write the code when the context is fresh and the fix is cheap, not later in integration or production. It drives better design, because writing the test first makes me design from the caller's perspective, which produces small focused units with clear interfaces, and hard-to-test code immediately signals a design problem, so TDD naturally pushes toward dependency injection and separation of concerns. And it acts as documentation, because the tests are executable specifications that describe exactly what the code does and which edge cases it handles, and unlike prose they cannot drift out of date because they fail if the behavior changes.

The nuances I add are that TDD is a design discipline as much as a testing one, that tests should target behavior not implementation so refactoring does not break them, that I must see the test fail before making it pass to avoid false confidence, and that it is most valuable for logic-rich code like calculations and rules while applied with judgement for thin glue. At the National Bank of India I used TDD for the interest and fee calculators, writing a failing test for each rule, then the minimal code, then refactoring, and the payoff was both correctness, since a miscalculated fee has real regulatory consequences, and a precise, trustworthy, always-current record of what every rule does. Being able to explain the cycle and tie it to early bug detection, better design, and living documentation is what shows an interviewer I understand TDD as a way to build correct, well-designed, well-documented code rather than a box-ticking ritual.

Remember one sentence: write a failing test first, write the minimum code to pass it, then refactor, and this Red-Green-Refactor loop catches bugs early, drives better design, and creates living documentation.

---

Visual Overview

DIAGRAM
  TDD Red-Green-Refactor Cycle
  ─────────────────────────────

  ┌──────────────────┐
  │  RED              │ Write a failing test
  │  Test fails       │ for behavior that
  │  (expected!)      │ does not exist yet
  └────────┬─────────┘
           │
  ┌────────▼─────────┐
  │  GREEN            │ Write the MINIMUM
  │  Test passes      │ code to make the
  │                   │ test pass
  └────────┬─────────┘
           │
  ┌────────▼─────────┐
  │  REFACTOR         │ Clean up structure
  │  Test still passes│ Remove duplication
  │                   │ Keep test green
  └────────┬─────────┘
           │
           └──> Next behavior (loop)
Share
Use ← → arrow keys to navigate