4.5Automated testing with PHPUnit
The essentials in 30 seconds
- Tests = code that tests code: PHPUnit (via Composer), assertions (
assertEquals,assertTrue…) that fail if actual ≠ expected. - 3 levels: unit (isolated function), integration (with the database), functional (like a user) —
./vendor/bin/phpunitruns them all. - Riad's reflex: test the critical business logic first, rerun after each change — the liberating non-regression.
Running case — Riad locks his prices before the sale
On the eve of the sales, Riad fears a discount might break his prices in Constantine: he writes his first PHPUnit test on prixTTC, adds edge cases (zero, null rate, empty cart), then reruns the whole suite after each tweak. When a colleague changes the calculation, the test turns red BEFORE going live. Last milestone of the thread: from a prototype that "works" to a pro application that proves it.
Final episode of the 4.1-4.4 case — Riad closes the loop: architect, equip, expose, secure.
1. PHPUnit & assertions
Testing by hand is tedious, incomplete and not repeatable: you write code that TESTS code, rerunnable at will. In PHP, the reference is PHPUnit (installed via Composer, run with ./vendor/bin/phpunit). A test checks that an input gives the EXPECTED output via assertions — assertEquals($expected, $result), assertTrue(...), assertNull(...) — which fail if reality differs. Tests organized in classes and methods, run in bulk with a passed/failed report. Three levels: unit (isolated function/class), integration (several parts, e.g. with the database), functional/end-to-end (like a user).
2. The benefits & TDD
The benefits: CONFIDENCE (you know, you do not believe), NON-REGRESSION — the major one — (rerunning after each change warns immediately if a tweak breaks what worked), DOCUMENTATION (expected behaviour, executable and up to date), BETTER DESIGN (testable code is better broken down), EARLY detection (in development, not in production). Laravel and Symfony integrate PHPUnit with dedicated tooling (test databases, simulated HTTP requests). TDD writes the tests BEFORE the code (the test defines the expected, the code makes it pass) — a discipline that improves design. Riad starts small: critical logic first, not 100% right away.
🔎 Concrete example — 🔎 Concrete example — First price-incl-tax test
Riad's first test, with PHPUnit's real APIs:
prixTTC(100, 0.20) → expected 120.0 (assertEquals)
prixTTC(0, 0.20) → expected 0 (edge case)
Run → ./vendor/bin/phpunit → OK (2 tests, 2 assertions)
Change → rerun → green = nothing broken
Reading: one input, one expected value, one assertion — then the loop.
Lesson: edge cases catch the bugs that the demo misses.
Section vocabulary
- Automated tests / PHPUnit
- Code that TESTS your code (rerunnable at will) instead of checking by hand. PHPUnit is the PHP standard (via Composer).
Simple example: Riad runs ./vendor/bin/phpunit and gets his report in seconds.
What it is not: a one-off manual check in the browser.
Reflex: every critical behaviour deserves its test.
- Assertions
- Test checks: assertEquals(expected, result), assertTrue, assertNull… — fail if actual ≠ expected.
Simple example: Riad asserts assertEquals(120, prixTTC(100, 0.20)).
What it is not: an echo saying "looks good" with no strict comparison.
Reflex: expected first, actual second, clear message.
- Test levels
- Unit (isolated), integration (several parts, e.g. with the database), functional/end-to-end (like a user).
Simple example: Riad tests prixTTC as a unit, the order with its database as integration.
What it is not: a single big test mixing everything.
Reflex: pyramid: many unit tests, few end-to-end.
- Test benefits
- Confidence, NON-REGRESSION (spotting what a change breaks), documentation, better design, early detection.
Simple example: Riad's suite catches a broken discount before the sale.
What it is not: discovering the breakage through a client ticket in production.
Reflex: green suite required before each delivery.
- TDD (Test-Driven Development)
- Writing the tests BEFORE the code (the test defines the expected, the code makes it pass); a discipline that improves design.
Simple example: Riad writes the discount test before the function, then codes until green.
What it is not: coding at random then testing to "validate" afterwards.
Reflex: red → green → refactor: the TDD cycle.
In practice — In practice — Make your code reliable with tests
- Install PHPUnit (via Composer) and write a first unit test of a function (input → expected, assertEquals…).
- Run with ./vendor/bin/phpunit, read the report; add edge cases and invalid inputs.
- Write an integration test (feature + database); under a framework, use the built-in test tooling.
- Rerun after each change (non-regression) and try TDD (test BEFORE the code).
Ready-to-say script — announce the green suite (30 s)
"This is Riad: our critical calculations are covered by PHPUnit — unit, edge cases and database integration. Each tweak reruns the suite: green, we ship; red, we fix before production."
Say it before each delivery; show the green suite.
Copy-paste template — PHPUnit test (tested code)
assertEquals(120.0, prixTTC(100, 0.20));
}
public function testCasLimites(): void {
$this->assertEquals(0.0, prixTTC(0, 0.20));
$this->assertEquals(100.0, prixTTC(100, 0.0));
$this->assertTrue(prixTTC(50, 0.20) > 50);
}
}
// Run: ./vendor/bin/phpunit PrixTest.php
First step
A single test on the critical calculation: immediate value, habit installed.
Risky logic
Like Riad: calculations, business rules, edge and invalid cases — where bugs cost dearly.
Under a framework
Laravel/Symfony test databases and simulated HTTP requests for integration.
Growing team
Green suite required before merge: non-regression becomes a collective ritual.
.env.testing outside Git) and keep personal data out of test datasets. Teaching content, examples with fictitious data.✓ Best practices
- Test the critical logic first.
- Rerun after each change.
- Try TDD on a small function.
✗ Common pitfalls
- Believing "it works" removes the need to test.
- Aiming for 100% coverage right away.
- Only testing by hand, once.
Key takeaways
- AUTOMATED TESTS (PHPUnit, PHP standard): code that TESTS code. ASSERTIONS (assertEquals…) if actual ≠ expected.
- LEVELS: UNIT (isolated), INTEGRATION (with database), FUNCTIONAL (like a user).
- BENEFITS: CONFIDENCE, NON-REGRESSION (rerun after each change — the major one), documentation, DESIGN.
- Frameworks integrate PHPUnit. TDD = tests BEFORE code. ESSENTIAL in production — separates prototype from PRO app.
48h challenge — apply it tomorrow
Write 2 PHPUnit tests on your most critical function (1 normal case + 1 edge case) and rerun after 1 tweak.
- I can explain PHPUnit, assertions, levels.
- I can run the suite and read the report.
- I have 2 green tests on my critical logic.
Frequently asked questions
Testing feels like wasted time when "it already works": why test?
Because "it works" only holds for the hand-checked cases, today. Tomorrow, each change risks a silent regression — without tests, you would have to re-check everything by hand, which you never do. Tests bring confidence (you know), non-regression (immediate warning), living documentation of behaviour, better design (the testable is better broken down) and early detection — infinitely cheaper than in production.
How to start without being overwhelmed — what to test first?
Progressively, not 100% right away: critical business logic (calculations, rules — where a bug costs dearly), complex/risky or often-breaking code, edge cases and invalid inputs (zero, extremes, empties — where bugs hide), reused code (a bug there spreads everywhere). Lower priority at first: trivial code (getters), pure display. Method: Arrange-Act-Assert — set up, act, verify.
What is the major benefit of automated tests?