Does your code work? Are you sure? How many times have you added a feature only to break an old one? At Meteora Web, we see this every day in projects that land on our desks. Test Driven Development (TDD) is not a Silicon Valley fad; it's a method that saves hours of debugging and sleepless nights. And it always starts with a cycle: red, green, refactor.
The Red-Green-Refactor Cycle: How It Really Works
TDD is built on three phases, repeated for every single unit of functionality:
- Red: Write a test that fails. You haven't implemented anything yet, but the test describes the desired behavior.
- Green: Write the minimum code needed to pass the test. It doesn't matter if it's ugly or inefficient. A few seconds, essential lines.
- Refactor: Clean up the code while keeping tests green. Remove duplication, improve readability, optimize if needed.
Then repeat. Always. Each cycle takes from a few seconds to a few minutes. If the cycle gets longer, you're doing something wrong.
Why It Works
TDD forces you to think about behavior before implementation. You don't start with "how do I write this class", but with "what should it do". The result? More modular code, automatically tested, with fewer hidden dependencies. In projects we manage, teams that adopt TDD reduce production bugs by 40-60% within the first sprint. And the time spent writing tests is recovered at least threefold during debugging.
Practical Example: A Discount Function for E-Commerce
Imagine you need to implement a function that applies a percentage discount to a price, rounding to two decimals. Let's start with the test.
// File: tests/DiscountTest.php
use PHPUnit\Framework\TestCase;
class DiscountTest extends TestCase
{
public function test_applies_percentage_discount()
{
$price = 100.00;
$discount = 20; // 20%
$result = applyDiscount($price, $discount);
$this->assertEquals(80.00, $result);
}
}Run the test: red. The applyDiscount function doesn't exist. Now write the minimum code to make it pass.
// File: src/Discount.php
function applyDiscount(float $price, float $percent): float
{
return $price - ($price * $percent / 100);
}Test passes: green. Now refactor: the function is simple, but we could round to two decimals, add parameter validation, or extract a class. For now it's enough. Then add another test: 100% discount should give 0, negative discount? And so on, cycle after cycle.
Essential Tools for TDD in Production
Writing tests by hand is not enough. You need to integrate them into your workflow. Here's what we use in our projects:
PHPUnit (PHP), Jest (JavaScript/TypeScript), pytest (Python)
Each language has its standard. Pick it and learn to use it from the command line. With --watch, tests run automatically on every save.
Coverage, but wisely
Don't chase 100%. Cover critical paths: edge cases, errors, main use cases. The rest will come with time. We check that every logical branch has at least one test.
Continuous Integration (CI)
Tests must run on every push. Use GitHub Actions, GitLab CI, or Jenkins. If a test fails, the pipeline stops. No exceptions. On the servers we configure, even automatic deployment requires green tests.
Common Mistakes (And How to Avoid Them)
- Tests that are too large: if a test verifies 10 different behaviors, you won't know what broke. Each test should cover a single logical unit.
- Skipping refactoring: green doesn't mean done. Without cleanup, code becomes a tangle of ifs and duplication. Refactoring is an integral part of the cycle.
- Tests dependent on execution order: each test must be independent and isolated. Use
setUpto reset state. - Over-mocking: mock only what is external (APIs, database). If you mock everything, you are not testing your code, you are testing your ability to mock.
Integrating TDD into an Existing Project
You don't need to rewrite everything. Start with the most unstable or critical modules. Add a test for every bug you find (Regression Testing). As you touch code, extract pure functions and cover them with new tests. We do this every time we inherit a legacy project: first write tests for the flows that already work, then make changes.
Our Field Experience
We built a proprietary platform to manage social media presence for multiple clients. Without TDD, integrating with Instagram's API would have been a nightmare. Instead, every API call was tested with mocks, and when Facebook changed the endpoint, we fixed two lines, not days of debugging. Same for the clothing ERP system we manage: seasonal discount rules were developed using TDD, and changes come without breaking settlements.
What to Do Now
- Install the testing framework for your language. PHP?
composer require --dev phpunit/phpunit - Write your first test for a simple existing function. Make it fail, then make it pass.
- Run tests automatically with
--watch. Get used to seeing red and green. - Add a test for every bug you find. That way the bug never comes back.
- Read the official documentation: PHPUnit or Jest.
At Meteora Web, we keep repeating: code without tests is technical debt that will eventually be paid with interest. Better to start today with a small red-green-refactor cycle. Your client's revenue will thank you.
Sponsored Protocol