RobinSinghAI Engineer · Interface
← Writing
Engineering30 May 2026·10 min read·Robin Singh

My Experience with Test-Driven Development

A practical, point-by-point guide to how I use TDD in real projects: what it is, why it helps, and how to start without overthinking.

test-driven developmentTDDunit testingred green refactorcode qualityhow to start TDD
01

Why I changed how I write code

Software is never truly finished. Requirements evolve, edge cases appear, and features that looked complete yesterday break after one new change today. I used to write code first and tests later (or never). That usually led to long QA loops, regressions, and uncertainty while refactoring.

TDD did not remove complexity. It gave me a way to control it.
Robin Singh
02

What TDD actually means (in simple words)

Core idea
  • 1) Write a test for a small behavior before writing implementation.
  • 2) Run tests and watch it fail (Red).
  • 3) Write the minimum code to pass the test (Green).
  • 4) Clean up the code while keeping tests green (Refactor).
Fig. 1 — the loop
Step 01
Red
Write the test. Watch it fail for the right reason.
→
Step 02
Green
Write the minimum code that makes it pass.
→
Step 03
Refactor
Clean the code while the tests stay green.
↺
↺ repeat, 2–10 minutes per cycle

This cycle forces clarity. Before coding, I have to decide what success looks like. That one habit alone improved how I design functions, APIs, and components.

03

Why this helped me in real projects

Practical benefits I noticed
  • Faster debugging: failures point directly to broken behavior.
  • Safer refactors: I can restructure code without guessing what might break.
  • Better architecture: logic naturally moves into testable units instead of bloated UI files.
  • Less QA ping-pong: many regressions are caught before the ticket goes for review.
  • Higher ownership: if I break something, I catch and fix it immediately.
↓
Re-opened tickets
↑
Confidence to refactor
lower
Time spent firefighting
04

Red → Green → Refactor example

A tiny example shows the flow better than theory. First, define behavior through tests. Then implement just enough logic to satisfy them.

tsred → green
import { describe, it, expect } from 'vitest'

// RED: define behavior first
describe('calculateDiscount', () => {
  it('returns 10% discount for premium users', () => {
    expect(calculateDiscount(200, 'premium')).toBe(20)
  })

  it('returns 0 for standard users', () => {
    expect(calculateDiscount(200, 'standard')).toBe(0)
  })
})

// GREEN: minimum code to pass tests
function calculateDiscount(amount: number, tier: 'premium' | 'standard') {
  if (tier === 'premium') return amount * 0.1
  return 0
}
05

A mistake I made (and how TDD fixed it)

In one feature, I mixed API calls, formatting rules, and UI state in the same component. Tests became fragile because network responses changed. I split the transformation logic into a pure function and tested that function independently. Result: simpler component, stable tests, and faster feature updates.

Anti-patterns I avoid now
  • Writing huge tests that verify too many things at once.
  • Mocking everything blindly instead of isolating one unit clearly.
  • Asserting implementation details instead of behavior.
  • Treating test count as quality. Reliable tests matter more than many tests.
06

How to start TDD without getting overwhelmed

Beginner-friendly starting plan
  • Pick one small pure function (formatting, validation, mapping).
  • Write 2-3 clear tests for expected behavior and edge cases.
  • Use naming like "should return X when Y" so intent is obvious.
  • Keep one test file close to the code it validates.
  • Run tests frequently; keep cycles short (2-10 minutes each).
07

Checklist for a good test

Before you commit
  • Is the test readable without opening implementation?
  • Does it fail for the right reason when behavior breaks?
  • Is it deterministic (no flaky network/time/random dependencies)?
  • Does it validate outcome, not internal private details?
Start small, stay consistent, and let tests guide design. TDD becomes powerful when it is a daily habit, not a one-time sprint ritual.
← ArchiveNext: Passing data between microservices →