Tdd

By the end of this lesson you'll be able to drive your code with tests instead of writing them afterwards — working the Red-Green-Refactor cycle in tiny, safe steps, using failing tests to shape clean designs, and knowing the few places where TDD costs more than it pays.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C# course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

TDD is like writing the exam answer key before you sit the exam . First you decide, in black and white, what a correct answer looks like (the failing test). Only then do you write the answer and check it against the key. Because the target was fixed before you started, you can't fool yourself into marking a wrong answer "close enough" — and you instantly know the moment a later change breaks a question you'd already got right. The key isn't bureaucracy; it's the thing that lets you move fast without lying to yourself.

Then repeat — one tiny loop per behaviour. The discipline is: never write production code without a failing test asking for it , and never refactor on a red bar .

1. Red → Green → Refactor (the worked cycle)

Here's the whole cycle in one go, in xUnit. You start in Red : a test for RomanConverter that won't even compile because the class doesn't exist yet. Then Green : the smallest converter that passes all four tests. Then Refactor : tidy the code while the tests stay green. Read every comment, then picture running dotnet test and watching the bar flip from red to green.

Note: this example uses the xUnit framework, so it runs with dotnet test in a test project rather than in the simple online editor. The "Try it Yourself" exercises below are plain C# you can paste and run anywhere.

2. Baby Steps & Triangulation

Baby steps means you only ever write enough code to pass the current test — sometimes that's literally return 0; . That looks like cheating, but it's deliberate: it keeps you in a known-good state and stops you building features nobody has asked for yet. Triangulation is how you escape a fake answer: you add a second test with a different input, and now the hard-coded value fails, forcing real logic to emerge. Two points define a line; two tests define the behaviour.

3. Your Turn: Make the Failing Test Pass

Now you drive the cycle. The program below already contains a failing test (Red) for IsLeapYear — the assertions are written, but the method body is empty, so they'd print FAIL . Don't touch the test: fill in the one blank in the method to turn every line green ( PASS ). This is plain C# with a tiny built-in assert helper, so you can run it in any compiler.

4. Your Turn: Write the Next Test First

Real TDD is a loop, so here you add the next failing test yourself before extending the code. Fizz(n) already handles multiples of 3. Your job: add a new assertion that 5 should return "Buzz" (it fails now — that's Red), then add the one line to Fizz that makes it pass (Green). Fill in both blanks.

5. Refactoring Under a Green Bar

The third step is the one beginners skip — and it's where TDD pays off. Once your tests are green, they pin the behaviour in place . That means you can rewrite a tangled implementation into a clean one and instantly know whether you broke anything: if the bar stays green, you didn't. Refactoring without tests is a leap of faith; refactoring under green is routine.

A test should assert what the code does (its observable behaviour through its public surface), never how it does it . If you assert on private fields, the exact sequence of internal calls, or a specific algorithm, your test breaks the moment you refactor — even though the behaviour is identical. That turns your safety net into a ball and chain.

Rule of thumb: if a correct refactor makes a test go red, that test was coupled to the implementation, not the behaviour.

The same idea is why you mock at boundaries (a database, a clock, an email gateway) but not your own pure logic — you want freedom to rewrite the inside.

TDD is a tool, not a religion. Reach for it where behaviour is well-defined and stable; for genuinely exploratory work, prototype freely and add tests once the shape settles.

It feels slower for the first few minutes and is usually faster over the life of the code. You spend less time debugging, less time afraid to change things, and you never build features nobody asked for. The tests also become living documentation of what the code is supposed to do.

Yes. A test that passes before you've written the code is silently broken — maybe it asserts the wrong thing, or never runs. Seeing red first proves the test can fail, so green later actually means something.

Q: What's the difference between TDD and just writing tests?

Order. TDD writes the test before the code, so the test shapes the design. "Test-after" writes tests for code that already exists — useful, but it can't influence the design and tends to test what the code happens to do rather than what it should do.

Small enough that if the test goes red unexpectedly, you know exactly what caused it. If you can't make a failing test pass in a couple of minutes, your step was too big — back up and take a smaller one.

No. It's brilliant for business logic, algorithms, and bug fixes, and awkward for UI work and throwaway prototypes. Use it where behaviour is well-defined and you'll refactor a lot; relax it where you're still exploring what to build.

No blanks this time — the failing tests are given (Red) and the implementation is up to you. Work in baby steps: make FizzBuzz(1) pass, then 3 , then 5 , then 15 , tidying as you go. Watch your ordering — check divisible-by-15 ( "FizzBuzz" ) before 3 and 5, or you'll never reach it. Run it and every line should print PASS .

Practice quiz

What are the three steps of the TDD cycle, in order?

  • Refactor, Red, Green
  • Green, Red, Refactor
  • Red, Green, Refactor
  • Write, Run, Review

Answer: Red, Green, Refactor. The cycle is Red (failing test) then Green (smallest pass) then Refactor (clean up).

In the Red step, what do you do?

  • Write one small failing test and run it to watch it fail
  • Write the production code
  • Refactor the code
  • Delete old tests

Answer: Write one small failing test and run it to watch it fail. Red means write a failing test first and run it, proving it actually tests something.

In the Green step, how much code should you write?

  • The entire feature
  • As much as possible
  • Only comments
  • The smallest code that makes the bar go green

Answer: The smallest code that makes the bar go green. Green means write the smallest code that passes - even a hard-coded value is acceptable.

Why must you watch a test fail before writing the code?

  • To measure speed
  • To prove the test can actually fail, so a later green means something
  • It's required by xUnit
  • To warm up the compiler

Answer: To prove the test can actually fail, so a later green means something. A test green before you write code is testing nothing; seeing red first proves it can fail.

What is 'triangulation' in TDD?

  • Adding a second test with a different input to force real logic out of a fake answer
  • Using three test frameworks
  • Testing in three environments
  • A refactoring pattern

Answer: Adding a second test with a different input to force real logic out of a fake answer. A second test with a different input makes a hard-coded value fail, forcing real logic to emerge.

What does the Refactor step require about the test bar?

  • The bar must be red
  • Skip running tests
  • Improve the code while the bar stays green
  • Add new failing tests

Answer: Improve the code while the bar stays green. You refactor only under a green bar; the passing tests prove you didn't change behaviour.

A good test should assert what?

  • Private fields and internal call order
  • Observable behaviour through the public surface
  • The exact algorithm used
  • Memory addresses

Answer: Observable behaviour through the public surface. Test behaviour, not internals - asserting on internals breaks tests on every refactor.

If a correct refactor makes a test go red, what does that usually mean?

  • The refactor was wrong
  • The framework is broken
  • The test is fine
  • The test was coupled to the implementation, not the behaviour

Answer: The test was coupled to the implementation, not the behaviour. A behaviour-preserving refactor shouldn't break a behaviour-focused test; red means it tested internals.

What is the difference between TDD and just writing tests?

  • TDD uses more assertions
  • Order - TDD writes the test before the code, so the test shapes the design
  • TDD requires mocks
  • There is no difference

Answer: Order - TDD writes the test before the code, so the test shapes the design. TDD writes the test first so it influences design; test-after can only check what the code already does.

When is TDD generally LESS suitable?

  • Business rules with clear inputs/outputs
  • Algorithms and validators
  • Throwaway spikes, exploratory prototypes, and pixel-level UI work
  • Bug fixes

Answer: Throwaway spikes, exploratory prototypes, and pixel-level UI work. TDD shines for well-defined logic; it gets in the way of exploratory prototypes and visual UI work.