Why TDD Has Become a Standard for PHP Developers?
Imagine: you write code, run it, see an error, fix it — and repeat. Sound familiar? Now imagine that you first write a test that is guaranteed to fail, then minimal code to make it pass, and only then refactor. This is TDD in PHP — test-driven development, which turns chaos into a structured system.
Many developers are afraid of TDD: "It will slow down development!", "I don't know how to test legacy code", "PHPUnit is complicated." But in practice, TDD saves hours of debugging and makes code resilient to changes. In this article, we will break down TDD with real PHP examples, show how the "red-green-refactor" cycle works, and provide ready-made templates for implementation.
What is TDD and How Does It Work in PHP?
Test-Driven Development is a methodology where tests are written before implementation. The main cycle looks like this:
- Red — write a test that fails (expect an error).
- Green — write minimal code to make the test pass.
- Refactor — improve the code without changing behavior.
In PHP, PHPUnit is most often used for TDD — a powerful framework for unit testing. But TDD is not about tools, it's about discipline. Let's see how it works in practice.
Example 1: Testing a Simple Calculator
Let's say we need a function to add two numbers. Without TDD, we would write:
function add($a, $b) { return $a + $b;}echo add(2, 3); // 5With TDD, we first write the test:
use PHPUnit\\Framework\\TestCase;
class CalculatorTest extends TestCase { public function testAdd() { $calculator = new Calculator(); $result = $calculator->add(2, 3); $this->assertEquals(5, $result); }}Run it — the test fails (red). Write the minimal implementation:
class Calculator { public function add($a, $b) { return $a + $b; }}The test passes (green). Refactoring is not needed — the code is simple. But if we wanted to add handling for negative numbers, we would first write a test for that case.
Example 2: TDD for Working with a Database
It is often said: "TDD is not suitable for databases." This is a myth. Use mocks and test databases. Consider an example with UserRepository:
class UserRepositoryTest extends TestCase { public function testFindUserById() { $pdoMock = $this->createMock(PDO::class); $stmtMock = $this->createMock(PDOStatement::class); $pdoMock->method('prepare') ->willReturn($stmtMock); $stmtMock->method('fetch') ->willReturn(['id' => 1, 'name' => 'Alex']); $repository = new UserRepository($pdoMock); $user = $repository->findById(1); $this->assertEquals('Alex', $user['name']); }}The test checks that the repository correctly calls SQL and processes the result. The implementation code will be minimal: just execute the query and return the data.
Popular Tools for TDD in PHP
To make TDD part of your workflow, you need to master several tools:
- PHPUnit — the main framework for tests. Installation:
composer require --dev phpunit/phpunit. - Mockery — a library for creating mocks with a flexible API.
- Prophecy — an alternative to Mockery, often used with Symfony.
- PHPStan / Psalm — static analysis that complements TDD by finding errors before running tests.
For integration tests, you can use Laravel Dusk or Symfony Panther, but to start, it's enough