Artificial intelligence—it’s everywhere. I’ll admit it: I used AI to help write this blog post. The ideas and opinions? All mine. The sentence structure? That’s AI-assisted. No shame. I’m a software engineer — run-on sentences are my bread and butter. You know what my jam is? Programming. Specifically, Test-Driven Development. And when it comes to TDD, I don’t use AI.
Here’s why.
The True Purpose of TDD
TDD isn’t just about having tests. It’s about design. I often refer to it as Test-Driven Design because the process forces you to think through functionality and architecture before writing the implementation. Done right, it results in more robust, maintainable, and intention-driven code.
Unfortunately, many folks reduce TDD to “write tests for your code.” And honestly? I’ll take tests over no tests any day. But if you’re writing the code first and then asking AI to generate tests afterward, you’re missing the point. You’re not letting the tests guide your design—you’re just validating decisions you’ve already made.
The Experiment: Letting AI Write the Tests First
So, I decided to flip the process. I asked AI to write the tests first, before any implementation, just to see what it would come up with. For extra irony, I based the prompt on Kent Beck’s Test-Driven Development by Example—specifically, the classic “Money Example.”
Here’s the prompt I gave:
I am writing code to deal with multi-currency Money.
We need to be able to add amounts in two different currencies and convert the result given a set of exchange rates. We need to be able to multiply an amount (price per share) by a number (number of shares) and receive an amount.
Can you write the unit tests I need for this?
ChatGPT chose C# and xUnit, then assumed this domain model:
public class Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency);
public Money ConvertTo(string targetCurrency, IExchangeRateProvider rates);
public static Money operator +(Money a, Money b);
public static Money operator *(Money money, decimal multiplier);
}
public interface IExchangeRateProvider
{
decimal GetExchangeRate(string fromCurrency, string toCurrency);
}
And here are the tests it generated:
public class MoneyTests
{
private class FakeExchangeRateProvider : IExchangeRateProvider
{
private readonly Dictionary<(string, string), decimal> _rates;
public FakeExchangeRateProvider(Dictionary<(string, string), decimal> rates)
{
_rates = rates;
}
public decimal GetExchangeRate(string fromCurrency, string toCurrency)
{
if (fromCurrency == toCurrency) return 1m;
if (_rates.TryGetValue((fromCurrency, toCurrency), out var rate))
return rate;
throw new InvalidOperationException($"No rate from {fromCurrency} to {toCurrency}");
}
}
[Fact]
public void Multiply_Money_By_Number_Returns_Correct_Result()
{
var money = new Money(10.00m, "USD");
var result = money * 5;
Assert.Equal(50.00m, result.Amount);
Assert.Equal("USD", result.Currency);
}
[Fact]
public void Add_Money_With_Same_Currency_Returns_Sum()
{
var money1 = new Money(15.00m, "USD");
var money2 = new Money(20.00m, "USD");
var result = money1 + money2;
Assert.Equal(35.00m, result.Amount);
Assert.Equal("USD", result.Currency);
}
[Fact]
public void Add_Money_With_Different_Currencies_Throws_Exception_Or_Is_Invalid()
{
var money1 = new Money(15.00m, "USD");
var money2 = new Money(20.00m, "EUR");
Assert.Throws(() => _ = money1 + money2);
}
[Fact]
public void Convert_Money_To_Another_Currency_Uses_ExchangeRate()
{
var money = new Money(10.00m, "USD");
var rates = new FakeExchangeRateProvider(new()
{
{ ("USD", "EUR"), 0.9m }
});
var result = money.ConvertTo("EUR", rates);
Assert.Equal(9.00m, result.Amount);
Assert.Equal("EUR", result.Currency);
}
[Fact]
public void Add_Money_With_Conversion_Then_Convert_Result()
{
var usd = new Money(10.00m, "USD");
var eur = new Money(10.00m, "EUR");
var rates = new FakeExchangeRateProvider(new()
{
{ ("EUR", "USD"), 1.1m },
{ ("USD", "USD"), 1.0m }
});
var eurInUsd = eur.ConvertTo("USD", rates);
var total = usd + eurInUsd;
Assert.Equal(21.00m, total.Amount);
Assert.Equal("USD", total.Currency);
}
}
At first glance, the tests looked pretty good. I even panicked a little — was AI about to prove me wrong?
Where AI Fell Short
So I started to write the code to make them pass. But once I looked more closely, the issues started to show.
1. No Tests for Rounding
Every example uses nice, round numbers — no cents, no edge cases. But in real-world currency, rounding is always a concern. If you’re not testing slightly messier calculations like 10.23 * 1.5, you’re asking for subtle bugs later. It’s a classic blind spot.
2. Currency as Strings
The domain model uses string for currency, which means no type safety and no constraints. I didn’t specify the list of currencies in my prompt, so fair enough — but the number of acceptable values is finite. An enum would’ve made much more sense here, preventing typos and improving clarity. Converting known strings to enum values via an extension method would be simple and far more robust.
3. Not Fulfilling the Prompt
Here’s the big one. My prompt said:
“Add amounts in two different currencies and convert the result…”
But one of the tests explicitly asserts that adding different currencies should fail:
[Fact]
public void Add_Money_With_Different_Currencies_Throws_Exception_Or_Is_Invalid()
That’s not just missing the point — it’s actively working against the requirements.
Sure, I could’ve made my prompt more explicit: “In a single method or operation, we need to add different currencies and convert the result.” But guess what? That’s Test-Driven Design in action.
When you’re writing the tests you want, you’re not just thinking about correctness — you’re designing your API. You’re exploring your assumptions, clarifying your goals, and shaping how the system should behave.
AI skipped that part completely.
Final Thoughts
AI is great at generating boilerplate and filling in blanks. But TDD isn’t about the blanks — it’s about the thinking that happens before the code.
You can’t outsource that part. And, if you try, you’re not doing Test-Driven Development. You’re doing Test-After Implementation, dressed up with a clever tool.
So go ahead and use AI. I do. But don’t hand over the wheel. Especially not when you’re still figuring out where you’re going.