# Test-Driven Development

In test-driven development you write a failing test first, then just enough code to pass it, then tidy up. Follow three short cycles that build a shipping calculator.

Apprentice · Unit Testing · C# · Updated November 3, 2026

## After this lesson you can

- Explain the red, green, refactor cycle of test-driven development
- Write a failing test first, then just enough code to make it pass

## Key takeaways

- Test-driven development repeats red, green, refactor: write one failing test, write just enough code to pass it, then tidy the code while the tests stay green.
- See each new test fail first. A test that has never failed might not test anything at all.
- Just enough code means just enough: returning a fixed `5.99m` is right until a test asks for more.
- Run every test after refactoring: if they're still green, the tidy-up broke nothing.

So far you've written tests for code that already exists. **Test-driven development** (TDD) turns that around: you write the test **first**, watch it fail, and only then write the code. The tests drive what you build.

## Red, green, refactor

TDD is a short cycle, repeated for each requirement:

1. **Red.** Write one test for the next requirement. Run it, and see it **fail**. (Test Explorer shows it red.)
2. **Green.** Write **just enough** code to make it pass. Run every test, and see them all pass.
3. **Refactor.** Tidy the code (better names, no repetition) while the tests stay green.

Seeing the test fail first matters. A test that has never failed might not test anything at all: a typo in its check, or the wrong method called. Red proves the test can catch a problem.

## An example: a shipping calculator

The requirements for an online store's shipping:

- Orders **under $50.00** cost **$5.99** to ship.
- Orders of **$50.00 or more** ship **free**.
- An **empty** order ($0) costs nothing to ship.

### Cycle 1

**Red.** The first test, for the first requirement:

```csharp
[TestMethod]
public void Cost_UnderFiftyDollars_IsFiveNinetyNine()
{
    ShippingCalculator calculator = new ShippingCalculator();

    decimal cost = calculator.Cost(20.00m);

    Assert.AreEqual(5.99m, cost);
}
```

It doesn't even build yet: there's no `ShippingCalculator`. That counts as red.

**Green.** Just enough code to pass. Really, just enough:

```csharp
public class ShippingCalculator
{
    public decimal Cost(decimal orderTotal)
    {
        return 5.99m;
    }
}
```

Returning a fixed number feels like cheating, but it's the point: no test asks for anything more yet. The next test will.

### Cycle 2

**Red.** The second requirement:

```csharp
[TestMethod]
public void Cost_FiftyDollarsOrMore_IsFree()
{
    ShippingCalculator calculator = new ShippingCalculator();

    decimal cost = calculator.Cost(50.00m);

    Assert.AreEqual(0m, cost);
}
```

```text
Failed Cost_FiftyDollarsOrMore_IsFree
  Error Message:
   Assert.AreEqual failed. Expected:<0>. Actual:<5.99>. 'expected' expression: '0m', 'actual' expression: 'cost'.
```

**Green.**

```csharp
public decimal Cost(decimal orderTotal)
{
    if (orderTotal >= 50.00m)
    {
        return 0m;
    }
    return 5.99m;
}
```

Both tests pass.

### Cycle 3

**Red.** The empty order:

```csharp
[TestMethod]
public void Cost_OfAnEmptyOrder_IsZero()
{
    ShippingCalculator calculator = new ShippingCalculator();

    decimal cost = calculator.Cost(0m);

    Assert.AreEqual(0m, cost);
}
```

It fails: an empty order costs $5.99.

**Green**, then **refactor**: the numbers get names, so the rules read like the requirements:

```csharp
public class ShippingCalculator
{
    private const decimal FreeShippingFrom = 50.00m;
    private const decimal StandardCost = 5.99m;

    public decimal Cost(decimal orderTotal)
    {
        if (orderTotal == 0 || orderTotal >= FreeShippingFrom)
        {
            return 0m;
        }
        return StandardCost;
    }
}
```

Run all three tests after refactoring: still green, so the tidy-up broke nothing.

## Why work this way

- **Every line of code is there because a test asked for it**, so every line is tested.
- **You think about what the code should do before how.** Writing the test first means reading the requirement closely.
- **Refactoring is safe.** The tests tell you at once if a change breaks something.

Many professional teams work this way: no production code without a test that demands it.

## Check yourself

2 questions to try on the lesson page.

## So what

Red, green, refactor: one failing test, just enough code, then tidy. It feels slow for the first few cycles, then it feels like the safest way to build anything.

## Key terms

- [Test-Driven Development](https://www.ask-codey.com/glossary/test-driven-development)
- [Red, Green, Refactor](https://www.ask-codey.com/glossary/red-green-refactor)
- [Passing and Failing Tests](https://www.ask-codey.com/glossary/passing-and-failing-tests)

## Cheat sheets

- [Test-Driven Development](https://www.ask-codey.com/cheat-sheets/unit-testing-with-mstest-test-driven-development-cheat-sheet)

Source: https://www.ask-codey.com/lessons/unit-testing-with-mstest-test-driven-development
