Skip to main content

Command Palette

Search for a command to run...

TDD vs. Spike-Driven Development: When to Test First

Updated
4 min readView as Markdown
TDD vs. Spike-Driven Development: When to Test First

Test-Driven Development (TDD) is highly effective for well-defined, predictable software tasks where the inputs and outputs are known upfront. However, TDD fails during exploratory "spikes" or research phases when you don't yet know how to solve the problem. In those cases, write tests after finding the solution.

I recently had the roof on my house replaced. The crew showed up, climbed up a ladder, and immediately saw it was shot. Because they had a clear, repeatable process, they could plan the exact number of tiles needed, draft a precise invoice, and swap the entire roof in a single day.

This got me thinking about Test-Driven Development (TDD). TDD advocates often treat it as a dogma—asserting you must write tests before writing a single line of production code. But just like that roofing job, TDD only works when you have a predictable blueprint.

What is the Best Use Case for Test-Driven Development (TDD)?

TDD is best suited for well-defined, repeatable engineering tasks where the requirements, inputs, and expected outputs are clear from the start. It excels when you can fully plan the implementation before writing code.

Think of tasks like building a standard REST API endpoint, writing a utility library, or implementing a clear mathematical algorithm. Let's say you need to write a helper function that calculates tax rates based on postal codes. The inputs are clear, the rules are documented, and the outputs are predictable.

You can easily write the test assertions first because there are no structural surprises. The test acts as your design specification, guiding you straight to the implementation without wasted effort.

Why Does TDD Fail on Exploratory Software Projects?

TDD fails when you are tackling highly ambiguous, exploratory tasks where the solution design is unknown. If you cannot plan your execution path, you cannot write meaningful test assertions upfront without constantly breaking them during experimentation.

Imagine your team is tasked with integrating an legacy internal database with a poorly documented third-party API. You don't know the exact response payload, how the authentication handshakes work, or how the data structure should look on your end.

If you try to write tests first, you are guessing. You will end up in a frustrating cycle of writing a test, realizing the API doesn't work that way, refactoring the test, rewriting the code, and repeating. Instead, you need a "spike"—a messy, unstructured playground where you write throwaway code just to see what works.

How to Decide Between TDD and Spike-Driven Development?

You should choose TDD when the path from problem to solution is clear, and Spike-Driven Development when you are still discovering how the system behaves. The choice comes down to predictability versus exploration.

Project Type Recommended Approach Why?
Known inputs/outputs (e.g., bug fixes, standard calculators) TDD (Test-First) You can define success criteria before writing code, preventing regressions.
Ambiguous API integrations (e.g., third-party webhooks, raw research) Spike (Test-Later) You need to experiment and discover the domain shape before locking it down with tests.
Standard CRUD operations TDD (Test-First) The patterns are highly repeatable and predictable, meaning minimal design churn.
Complex architectural refactoring Spike (Test-Later) You need to prove the viability of a design before committing to its test suite.

FAQ

Should I delete my spike code and rewrite it with TDD?

Often, yes. Once your spike has proven a solution works and you understand the domain, the best approach is to treat that prototype as throwaway code. You can then use your newfound clarity to write clean, production-grade code using TDD.

Does avoiding TDD mean I shouldn't write tests at all?

Absolutely not. It simply changes when you write them. If you skip TDD during an exploratory phase, you must still write robust integration and unit tests once your design stabilizes.

How do I handle testing when requirements change rapidly?

If requirements are changing daily, focus on high-level integration tests that assert on system behavior rather than unit tests that assert on implementation details. This gives you the freedom to refactor internal code without breaking your test suite.