4.5 · Automated testing with PHPUnit

Level 4 · Frameworks & API

4.5Automated testing with PHPUnit

Goal: Make your code reliable with automated tests (PHPUnit, assertions, levels): confidence, non-regression, better design — what separates a prototype from a professional application.
Estimated time: 11 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

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/phpunit runs 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.

Common misinterpretation: "Testing is wasting time: it works, I checked it by hand." No. Manual checking covers a few cases, today only — and every future tweak risks silently breaking what worked. Tests rerun in seconds and warn immediately: it is this non-regression that lets you refactor without fear. "It works" without tests means "it works for now".
The PHPUnit confidence loop Write → run → change → rerun Write Test: input →expected outputRun ./vendor/bin/phpunit,reportChange Tweak orrefactoringRerun Green = provennon-regression
Write → run → change → rerun
Text version of the diagram (SEO / screen readers / translation): 1) Riad writes a PHPUnit test on his critical logic: given input, expected output in an assertion. 2) He runs the suite and reads the report. 3) He changes his code with confidence. 4) He reruns: green, nothing broken — red, regression caught before production. Lesson: prove instead of believe.

🔎 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

  1. Install PHPUnit (via Composer) and write a first unit test of a function (input → expected, assertEquals…).
  2. Run with ./vendor/bin/phpunit, read the report; add edge cases and invalid inputs.
  3. Write an integration test (feature + database); under a framework, use the built-in test tooling.
  4. Rerun after each change (non-regression) and try TDD (test BEFORE the code).
You make your code reliable with automated tests (PHPUnit, assertions, levels): confidence, non-regression, better design — the pro threshold.

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.

Caution: Tests prove the tested, not everything: never expose secrets in tests (fictitious fixtures, .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.

Section exit checklist
  • I can explain PHPUnit, assertions, levels.
  • I can run the suite and read the report.
  • I have 2 green tests on my critical logic.
Self-rating 1-5: is my critical logic covered?did I rerun the suite after my last tweak?

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.

Check your understanding

What is the major benefit of automated tests?

Additional resources (optional, to go further)

Level 4 examFresh, harder questions to test what you've learned