Writing Good Tests in TDD

Article Banner
Author(s): Ajay Kumar
Last updated: 18 Oct 2025

Ever run a test suite where everything’s green… but you still don’t trust it?


Have you ever encountered that unreliable test everyone avoids, or noticed a flaky test that occasionally fails but gets ignored due to tight deadlines? Let’s explore why this happens.

In Test-Driven Development (TDD), tests aren’t just a safety net you add later. They’re the steering wheel. But if that steering wheel wobbles, you’ll never enjoy the ride.

Why ‘Good Tests’ Matter in TDD


Good tests are what make TDD possible—not just productive. TDD depends on trustworthy feedback loops. Poor tests lead to false confidence, brittle design, and slowed development. Great tests illuminate design issues early.

Example: Consider this misleading test:


	// Misleading: Tests implementation, not behavior
	[Fact]
	public void Test_Addition()
	{
		var calc = new Calculator();
		Assert.True(calc.Add(2, 2) == 4); // What does this really prove?
	}
        

A better test would be:


	[Fact]
	public void ReturnsSum_WhenAddingTwoNumbers()
	{
		var calc = new Calculator();
		var result = calc.Add(2, 2);
		Assert.Equal(4, result);
	}
        

What makes a ‘Good Test’? The 5 Pillars


Pillar Description Example
Clarity (Naming) Test names should describe intent, not mechanics.

    // Good
    [Fact]
    public void ReturnsFizz_WhenNumberIsDivisibleByThree() { }
    // Bad
    [Fact]
    public void Test1() { }
                        

Structure (AAA) Arrange → Act → Assert keeps tests readable.

    // Good
    var calc = new Calculator(); // Arrange
    var result = calc.Add(1, 2); // Act
    Assert.Equal(3, result); // Assert
    // Bad
    Assert.Equal(3, new Calculator().Add(1, 2));
                        

Focus One behavior per test, one reason to fail.

    // Good
    [Fact]
    public void ReturnsZero_WhenEmptyInput() { }
    // Bad
    [Fact]
    public void TestMultipleBehaviors() {
        Assert.Equal(0, calc.Add(""));
        Assert.Equal(1, calc.Add("1"));
    }
                        

Readability > Cleverness Don’t try to optimize test code. Tests are for humans.

    // Good
    var result = calc.Add(2, 2);
    Assert.Equal(4, result);
    // Bad
    Assert.Equal(4, calc.Add(2, 2)); // Inline everything
                        

No Logic in Tests Avoid loops, conditionals, or calculations in assertions.

    // Bad
    for (int i = 0; i < 10; i++)
        Assert.True(calc.Add(i, 0) == i);
    // Good
    Assert.Equal(0, calc.Add(0, 0));
    Assert.Equal(1, calc.Add(1, 0));
                        

How Tests Drive Design


Every good test you write shapes your API. A hard-to-test design usually means the code is too coupled. If your test setup feels painful—that’s feedback, not failure.

Tip: Refactor when writing the test feels clumsy. Pain-driven refactoring leads to better design.

Common Pitfalls and Gotchas


  • ❌ Tests that don’t test anything — empty or overly mocked tests.
  • ❌ Assertion Soup — multiple unrelated asserts.
  • ❌ God Test — testing multiple classes or layers in one go.
  • ❌ Logic in Tests — if/else or loops inside tests.

	// Bad: God Test
	[Fact]
	public void TestEverything() {
		var service = new Service();
		var repo = new Repo();
		Assert.True(service.DoWork());
		Assert.True(repo.Save());
	}
	// Good: Split into focused tests
        

Best Practices in the Wild


Here’s a mini before-and-after:


	// Before: Messy
	[Fact]
	public void TestStuff() {
		var calc = new Calculator();
		Assert.Equal(0, calc.Add(""));
		Assert.Equal(1, calc.Add("1"));
		Assert.Equal(3, calc.Add("1,2"));
	}
	// After: Clear, AAA, focused
	[Fact]
	public void ReturnsZero_WhenEmptyInput() {
		var calc = new Calculator();
		var result = calc.Add("");
		Assert.Equal(0, result);
	}
	[Fact]
	public void ReturnsSum_WhenNumbersProvided() {
		var calc = new Calculator();
		var result = calc.Add("1,2");
		Assert.Equal(3, result);
	}
        

Readability improves confidence and flow.

Wrap-Up: The Meta-Lesson


When your tests read like documentation, you know you’re doing TDD right.
Reflect: If someone who never saw your code can understand your tests—would they know what the system does?

Next, we’ll look at how to keep your tests clean even when the code depends on other code—through mocks, stubs, and fakes.

✨ 5-Point Test Quality Check


  • Does the test name describe intent?
  • Is the Arrange-Act-Assert structure clear?
  • Does the test focus on one behavior?
  • Is the test readable by someone new?
  • Is there any logic in the test? (If so, refactor!)
ℹ️ Tip
“A bad test is worse than no test — because it lies.”

Copyright © 2025 Dev Codex

An unhandled error has occurred. Reload 🗙