Organizing Tests

Running ./vendor/bin/phpunit tests/CalculatorTest.php works fine for one file, but naming every test file on the command line stops being practical the moment a project has more than a handful of them. A phpunit.xml configuration file at the project root fixes that — it tells PHPUnit where your tests live (and a number of other settings), so running the whole suite is just ./vendor/bin/phpunit with no arguments at all.

The phpunit.xml Configuration File

PHPUnit automatically looks for a file named phpunit.xml (or phpunit.xml.dist, conventionally used when the file is meant to be committed and shared by the whole team) in the project root. A minimal, annotated example that points PHPUnit at a tests/ directory:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         colors="true">
    <testsuites>
        <testsuite name="default">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>
Attribute / elementWhat it does
bootstrapA PHP file loaded before any test runs — almost always Composer's autoloader
colors="true"Colorizes pass/fail output in the terminal
<testsuites>One or more named groups of tests
<directory>tests</directory>Every test class PHPUnit should discover and run, found anywhere under this folder

With this file in place, the entire suite runs with just:

./vendor/bin/phpunit

Splitting Tests into Named Suites

Larger projects often want to separate fast unit tests from slower integration tests, so a developer can run just the quick ones while coding and save the full suite for CI. A phpunit.xml can define more than one <testsuite>, each pointing at its own directory:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Integration">
            <directory>tests/Integration</directory>
        </testsuite>
    </testsuites>
</phpunit>

With named suites defined, you can run just one of them by name when you only want the fast feedback loop:

# Run only the fast unit suite while developing
./vendor/bin/phpunit --testsuite Unit

# Run everything, including the slower integration suite
./vendor/bin/phpunit

Mirroring src/ Inside tests/

A common, easy-to-navigate convention is to give your tests/ directory the exact same folder structure as your src/ directory, with each class's test file named after the class it covers, suffixed with Test. Finding the test for any given class (or the class for any given test) then becomes a matter of pattern-matching the path.

src/
  Services/
    PaymentService.php
  Models/
    Order.php

tests/
  Services/
    PaymentServiceTest.php
  Models/
    OrderTest.php

src/Services/PaymentService.php is tested by tests/Services/PaymentServiceTest.php — same relative path, same base filename, just with Test appended and living under tests/ instead of src/. As a codebase grows to dozens or hundreds of classes, this mirrored structure is what keeps "where's the test for this?" a five-second question instead of a search.

Next up: Best Practices wraps up the whole PHPUnit track with a checklist of habits worth carrying into every real project.